6

I would like to export specific layers as a png from a QGIS .qgs map. From the table of contents the script would first export the "boundary, climits, and Div1_Irrig_1956_0" layers as one png. The standalone script would then iterate to export "boundary, climits, and Div1_Irrig_1976_1" layers as the next png and so on. I am working with the script below to begin with but am only getting one png with all of the layers exported.

enter image description here

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
from qgis.gui import *
from qgis.utils import iface

import sys, os
import glob

qgs = QgsApplication(sys.argv, True)
qgis_prefix = "C:\\OSGeo4W\\apps\\qgis"
QgsApplication.setPrefixPath(qgis_prefix, True)
qgs.initQgis()

#layers = glob.glob((configData['destination_folder'])+"\\*.shp")
layers = glob.glob(r"E:\IrrigatedLands\FC_qgis\*.shp")

for layer in layers:
    print layer
    irrig = QgsVectorLayer(layer, "testlayer_shp", "ogr")
    print irrig.isValid()
    layerset = []
    QgsMapLayerRegistry.instance().addMapLayer(irrig)
    layerset.append(irrig.id())

    # create image
    imageType = "png"
    pathToFile = "C:\\Users\\James\\Desktop\\"
    name = "render"
    img = QImage(QSize(800, 600), QImage.Format_ARGB32_Premultiplied)

    # set image's background color
    color = QColor(255, 255, 255)
    img.fill(color.rgb())

    # create painter
    p = QPainter()
    p.begin(img)
    p.setRenderHint(QPainter.Antialiasing)

    render = QgsMapRenderer()

    # set layer set
    layer_set = [irrig.id()]  # add ID of every layer
    print layer_set
    render.setLayerSet(layer_set)

    # set extent
    rect = QgsRectangle(render.fullExtent())
    rect.scale(1.1)
    render.setExtent(rect)

    # set output size
    render.setOutputSize(img.size(), img.logicalDpiX())

    # do the rendering
    render.render(p)

    p.end()

    img.save(pathToFile + name + "." + imageType ,imageType)
  • Related post (for tif files): Export raster layer in python to specific folder. Might be helpful. – ArMoraer Apr 16 '16 at 8:17
  • Is the file name identical in each iteration through the loop? You need to call img.save with different names, perhaps derived from the layer name or an index from 1 to however many. – Spacedman Apr 16 '16 at 12:38
  • the file name does not have to be identical for each export. How could you extract the layer name, i.e. Div1_Irrig_1956_0, as the exported file name within a standalone script? – brgionta Apr 19 '16 at 5:18
  • Did any of your layers happen to be layers with point geometries in them (question of my own because I am having some issues..) – GreyEyedPallas Jul 20 '16 at 21:25
12

In order to solve this question, we need to use timers or something that delays the execution of the script, so the canvas can reflect the layer arrangement at the time the map is exported. In other words, if you don't use timers you'll end up with 3 PNG images with the same content because everything will happen too fast.

In the QGIS map, set the map extent you want to export before running the following script in the QGIS Python Console (adjust the path ):

from PyQt4.QtCore import QTimer

fileName = '/tmp/exported' # exported is a prefix for the file names
boundaryLayer = QgsMapLayerRegistry.instance().mapLayersByName('boundary')[0]
climitsLayer = QgsMapLayerRegistry.instance().mapLayersByName('climits')[0]
otherLayers = ['Div1_Irrig_1956_0', 'Div1_Irrig_1956_1', 'Div1_Irrig_1956_2']
count = 0

iface.legendInterface().setLayerVisible(boundaryLayer, True)
iface.legendInterface().setLayerVisible(climitsLayer, True)

def prepareMap(): # Arrange layers
    iface.actionHideAllLayers().trigger() # make all layers invisible
    iface.legendInterface().setLayerVisible(QgsMapLayerRegistry.instance().mapLayersByName( otherLayers[count] )[0], True)
    QTimer.singleShot(1000, exportMap) # Wait a second and export the map

def exportMap(): # Save the map as a PNG
    global count # We need this because we'll modify its value
    iface.mapCanvas().saveAsImage( fileName + "_" + str(count) + ".png" )
    print "Map with layer",count,"exported!"
    if count < len(otherLayers)-1:
        QTimer.singleShot(1000, prepareMap) # Wait a second and prepare next map
    count += 1

prepareMap() # Let's start the fun

After the execution of the script, you'll end up with 3 (different) PNG images in /tmp/.

If you need to iterate over more layers, you just need to add their names to the otherLayers list, the script will do the rest for you.

----------------------------------------------------------------

EDIT: How to run this as a standalone script (outside of QGIS)?

The following script can be run outside of QGIS. Just make sure you adjust the file paths to your own directory structure and that you use a QGIS prefix that works for your own environment (see this answer for details):

from qgis.core import QgsApplication, QgsMapLayerRegistry, QgsVectorLayer, QgsProject
from qgis.gui import QgsMapCanvas, QgsMapCanvasLayer, QgsLayerTreeMapCanvasBridge
from PyQt4.QtCore import QTimer, QSize

qgisApp = QgsApplication([], True)
qgisApp.setPrefixPath("/usr", True)
qgisApp.initQgis()

# Required variables with your shapefile paths and names
pngsPath = '/tmp/'
boundaryLayer = QgsVectorLayer('/docs/geodata/colombia/colombia_wgs84.shp', 'boundary', 'ogr')
climitsLayer = QgsVectorLayer('/docs/geodata/colombia/colombia-geofabrik/railways.shp', 'climits', 'ogr')
otherLayers = {'Div1_Irrig_1956_0': QgsVectorLayer('/docs/geodata/colombia/colombia-geofabrik/points.shp', 'Div1_Irrig_1956_0', 'ogr'), 
    'Div1_Irrig_1956_1':QgsVectorLayer('/docs/geodata/colombia/colombia-geofabrik/places.shp', 'Div1_Irrig_1956_1', 'ogr'), 
    'Div1_Irrig_1956_2': QgsVectorLayer('/docs/geodata/colombia/colombia-geofabrik/natural.shp', 'Div1_Irrig_1956_2', 'ogr')}
count = 0    

canvas = QgsMapCanvas()
canvas.resize(QSize(500, 500)) # You can adjust this values to alter image dimensions
canvas.show()

# Add layers to map canvas taking the order into account
QgsMapLayerRegistry.instance().addMapLayer( boundaryLayer)
QgsMapLayerRegistry.instance().addMapLayers( otherLayers.values() )
QgsMapLayerRegistry.instance().addMapLayer( climitsLayer )
layerSet = [QgsMapCanvasLayer(climitsLayer)]
layerSet.extend([QgsMapCanvasLayer(l) for l in otherLayers.values() ])
layerSet.append(QgsMapCanvasLayer(boundaryLayer))
canvas.setLayerSet( layerSet )

# Link Layer Tree Root and Canvas
root = QgsProject.instance().layerTreeRoot()
bridge = QgsLayerTreeMapCanvasBridge(root, canvas) 

def prepareMap(): # Arrange layers
    for lyr in otherLayers.values(): # make all layers invisible
        root.findLayer( lyr.id() ).setVisible(0) # Unchecked
    root.findLayer( otherLayers.values()[count].id() ).setVisible(2) # Checked
    canvas.zoomToFullExtent()
    QTimer.singleShot(1000, exportMap) # Wait a second and export the map

def exportMap(): # Save the map as a PNG
    global count # We need this because we'll modify its value
    canvas.saveAsImage( pngsPath + otherLayers.keys()[count] + ".png" )
    print "Map with layer",otherLayers.keys()[count],"exported!"
    if count < len(otherLayers)-1:
        QTimer.singleShot(1000, prepareMap) # Wait a second and prepare next map
    else: # Time to close everything
        qgisApp.exitQgis()
        qgisApp.exit() 
    count += 1

prepareMap() # Let's start the fun
qgisApp.exec_()

Again, if you need to iterate over more layers, just add them to the otherLayers dictionary, the script will do the rest.

The resulting PNG image file names will correspond to your layers.

  • thank you for the suggestions! Could I run this as a standalone script and execute it in OSGeo4W shell? – brgionta Apr 19 '16 at 5:15
  • 1
    Mmm, you've changed your question title making the post to become a different question. Please be clear when writing a question or you'll make people to waste precious time. Since the new question is still interesting, I've edited my answer, appending a standalone script. See above. – Germán Carrillo Apr 19 '16 at 17:10
  • The script renders all of the correct layers when showing the canvas window and exports the individual Div1_Irrig... layers with the boundary layer as separate .pngs but the climits layer is missing in all of the .pngs and the Irrig layers aren't fully rendered. Also there is a fatal error when running the script, causing python to crash. I encountered the same issue when working with the qgisApp.initQgis() lines. In my example script above I was able to resolve the issue without python crashing. I used my Qgis initialization lines with your script but the code still crashed!? – brgionta Apr 20 '16 at 8:03
  • Can you upload somewhere the whole script that is crashing in your computer? I'll have a look at it. – Germán Carrillo Apr 20 '16 at 11:51
  • 1
    The PNGs look correct. I had to set my PATH and PYTHONPATH variables within the OSGeo4W shell rather than defining them in the control panel environment variables. Thank you again! – brgionta May 2 '16 at 3:13
1

This actually doesn't require scripting. You can use print composer and atlas serial printing for that. It allows to iterate both over geographical features or also over layers or visibility preset groups. See http://www.qgis.org/en/site/forusers/visualchangelog212/index.html#feature-data-defined-control-over-map-layers-and-style-presets for more details.

  • Do you have a better link for how to do this? – raphael Jan 9 '17 at 22:43

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.