11

I have a QGIS composer/print view opened with all elements adjusted as I wish. Now I need to print/save/export this as a PNG or PDF file from Python console/Python script.

I do not want to change anything in current layout. Most examples which I found (for example: this), change a map position or size in output PDF comparing to what I see in my current composer view. I wish to receive exactly the same result as I would get when I click on Print --> Export as Image.

How can I do that? Atlas is not a solution for me.

7

I recommend using the following script by Tim Sutton as a base, it worked well for me:- http://kartoza.com/how-to-create-a-qgis-pdf-report-with-a-few-lines-of-python/

Just pass an empty dict to the composition.loadFromTemplate() function instead of the 'substitution map' he uses, since you're not interested in that functionality.

Also, since you asked about "export as image" rather than "export as PDF", you have to do a bit more work than use the exportAsPDF() member function.

Below is a modified version of Tim's code that should do the trick, working from an external Python script. Set the DPI, and project+composer file variables as required.

(If you are using the Python console within QGIS rather than doing this as an external script, you can get the current map canvas using the qgis.iface and don't need to do use all the project loading etc 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

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'

# Set output DPI
dpi = 300

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)
ms = canvas.mapSettings())
composition = QgsComposition(ms)
composition.loadFromTemplate(document, {})
# 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()

dpmm = dpi / 25.4
width = int(dpmm * composition.paperWidth())
height = int(dpmm * composition.paperHeight())

# create output image and initialize it
image = QImage(QSize(width, height), QImage.Format_ARGB32)
image.setDotsPerMeterX(dpmm * 1000)
image.setDotsPerMeterY(dpmm * 1000)
image.fill(0)

# render the composition
imagePainter = QPainter(image)
composition.renderPage(imagePainter, 0)
imagePainter.end()

image.save("out.png", "png")

QgsProject.instance().clear()
QgsApplication.exitQgis()
  • I'm interested in using your code. However, I'm getting a blank map...i've posted a question about it here if you happen to have any ideas: gis.stackexchange.com/questions/216376/… – user25976 Nov 3 '16 at 0:42
  • I'm interested in using this as well. I think I'm quite close, but I get 'AttributeError: 'QgsComposerItem' object has no attribute 'setMapCanvas'' I'm at a loss as to where to go from there. – jamierob Nov 13 '16 at 0:10
  • How can I find the code in the link in this answer? I've tried on two different browsers and can't seem to find the "example" the post refers to beyond python generate_pdf.py – raphael Dec 13 '16 at 22:24
3

You can program it by yourself or reuse a method from the Maps Printer plugin. I'll describe the second option because it's simpler.

You need to have the Maps Printer plugin installed, open the QGIS project with your configured composer, open the QGIS Python console and run this code snippet (you would need to adjust it with your own settings in the Your settings section).

# Your settings
composerTitle = 'my composer' # Name of the composer you want to export
folder = '/path/to/export_folder/'
extension = '.png' # Any extension supported by the plugin

mp = qgis.utils.plugins['MapsPrinter']

for composer in iface.activeComposers():
    title = composer.composerWindow().windowTitle()
    if title == composerTitle:
        mp.exportCompo( composer, folder, title, extension )
        break

After running it, you'll end up with a new file in /path/to/export_folder/my composer.png. You could also use '.pdf' as extension to export the composer as PDF.

3

Maybe also have a look at my other answer as new more up-to-date tools have arisen since this one.


I've come to the code below which is unfortunately not fully functional. This is based on the solution above and on theses others questions:

How to programmatically export a composition as image?
How can I read the settings for the QgsPaperItem from XML?
Saving Map Canvas as PNG with transparent background programmatically with QGIS?

My code is able to extract the .qpt from a .qgs file and sucessfully load a composer from template. It also print a composer to a .png file and display correctly labels and shapes stored in the composer.

However it fail to load all the elements related to the actual Map & Layers (label containing expression from layer are not drawn either). I think I missed a bit as to how the project must be loaded and linked to the composer.

Some people in the comment of the original article from Tim Sutton mentioned that they were stuck at the same stage under Windows ( it's my case). This is really frustrating because I feel like the answer is really really near. Dear Internet please help!

Also this is my first attempts at python so i hope you will be kind ;)

#This python code aim to programmatically export the first composer stored in a qgs file using PyQgis API v 2.10
#Version 0.4 (non functional) WTFPL MarHoff 2015 - This code is mostly a "frankenstein" stub made with a lot of other snippets. Feel welcome to improve!
#Credits to gis.stackexchange community : drnextgis,ndawson,patdevelop,dakcarto,ahoi, underdark & Tim Sutton from kartoza
#More informations and feedback can be found at https://gis.stackexchange.com/questions/144792/

#This script assume your environement is setup for PyGis as a stand-alone script. Some nice hints for windows users : https://gis.stackexchange.com/a/130102/17548

import sys
from PyQt4.QtCore import *
from PyQt4.QtXml import *
from qgis.core import *
from qgis.gui import *

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

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

# Name of the .qgs file without extension
project_name = 'myproject'

#Important : The code is assuming that the .py file is in the same folder as the project
folderPath = QString(sys.path[0])+'/'
projectPath = QString(folderPath+project_name+'.qgs')
templatePath = QString(folderPath+project_name+'_firstcomposer.qpt')
imagePath = QString(folderPath+project_name+'.png')

#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)
composerAsElement = projectAsDocument.elementsByTagName("Composer").at(0).toElement()

#This block store the composer into a template file
templateFile = QFile(templatePath)
templateFile.open(QIODevice.WriteOnly)
out = QTextStream(templateFile)
#I need next line cause UTF-8 is somewhat tricky in my setup, comment out if needed
out.setCodec("UTF-8")
param = QString
composerAsElement.save(out,2)
templateFile.close()

#And this block load back the composer into a QDomDocument
#Nb: This is ugly as hell, i guess there is a way to convert a QDomElement to a QDomDocument but every attemps failed on my side...
composerAsDocument = QDomDocument()
composerAsDocument.setContent(templateFile)

#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, {})


#And lets print in our .png
image = composition.printPageAsRaster(0)
image.save(imagePath,'png')

#Some cleanup maybe?
QgsProject.instance().clear()
QgsApplication.exitQgis()



I dropped theses lines from the previous code as they seemed to do nothing at all. They spawned no error but didn't do any better.

# 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()

and thoses are removed too because they seemed unnecessary when using printPageAsRaster()

dpmm = dpi / 25.4
width = int(dpmm * composition.paperWidth())
height = int(dpmm * composition.paperHeight())    

# create output image and initialize it
image = QImage(QSize(width, height), QImage.Format_ARGB32)
image.setDotsPerMeterX(dpmm * 1000)
image.setDotsPerMeterY(dpmm * 1000)
image.fill(0)    

# render the composition
imagePainter = QPainter(image)
composition.renderPage(imagePainter, 0)
imagePainter.end()
  • I just set up a VM with Unbuntu 14.04 and this code work like a breeze. So the issue is linked to the Windows version of PyQgis (tested on Window10 with Osgeo4w64 install), I will look if a ticket is already open in the bug tracker. – MarHoff Sep 28 '15 at 15:13
1

Another more up-to date option would be to look at theses 2 set of tools developed by the QGIS community. As Map-sprinter is actively supported you should expect that the scripts will be updated with upcoming versions of QGIS.

Both tool provide a GUI but underlying scripts are written in Python

Export composers to files - https://github.com/DelazJ/MapsPrinter
Export composers from multiple projects - https://github.com/gacarrillor/QGIS-Resources

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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