12

How do I 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)
4
  • Related post (for tif files): Export raster layer in python to specific folder. Might be helpful.
    – ArMoraer
    Commented Apr 16, 2016 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
    Commented Apr 16, 2016 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
    Commented Apr 19, 2016 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..) Commented Jul 20, 2016 at 21:25

4 Answers 4

19

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.

11
  • thank you for the suggestions! Could I run this as a standalone script and execute it in OSGeo4W shell?
    – brgionta
    Commented Apr 19, 2016 at 5:15
  • 2
    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. Commented Apr 19, 2016 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
    Commented Apr 20, 2016 at 8:03
  • Can you upload somewhere the whole script that is crashing in your computer? I'll have a look at it. Commented Apr 20, 2016 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
    Commented May 2, 2016 at 3:13
2

I had difficulty getting the composer to work and Germán Carrillo's answer is outdated.

Germán's answer also assumes that the entire layer can be rendered within a fixed time interval that is the same for each layer. In my use case, this couldn't be done reliably. Therefore, I used the following code:

prefix="probe_"
layers = []
for layer in QgsProject.instance().mapLayers().values():
    if layer.name().startswith("probe_"):
        layers.append(layer)

p = QgsProject().instance()
layout = QgsPrintLayout(p)
layout.initializeDefaults()
map = QgsLayoutItemMap(layout)
map.setRect(20, 20, 20, 20)
print(layers[0].extent())
map.setExtent(layers[0].extent())
layout.addLayoutItem(map)
map.attemptMove(QgsLayoutPoint(0, 0, QgsUnitTypes.LayoutInches))
map.attemptResize(QgsLayoutSize(8.5,  11, QgsUnitTypes.LayoutInches))
exporter = QgsLayoutExporter(layout)
settings = QgsLayoutExporter.ImageExportSettings()
settings.dpi      = 300
current_layer_idx = 0

def hide_layers(layers):
    root = QgsProject.instance().layerTreeRoot()
    for layer in layers:
        node = root.findLayer(layer.id())
        node.setItemVisibilityChecked(Qt.Unchecked)

def show_layer(layer):
    root = QgsProject.instance().layerTreeRoot()
    node = root.findLayer(layer.id())
    node.setItemVisibilityChecked(Qt.Checked)   

def prepare():
    global layer_name, out_name
    hide_layers(layers)
    show_layer(layers[current_layer_idx])
    layer_name = layers[current_layer_idx].name()
    out_name = layer_name + ".png"

def export():
    global current_layer_idx
    print("Saving ",out_name)
    ret = exporter.exportToImage(out_name, settings)
    assert ret==0
    current_layer_idx += 1

And manually entered prepare() and export() cyclicly as appropriate.

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.

1
  • 1
    Do you have a better link for how to do this?
    – raphael
    Commented Jan 9, 2017 at 22:43
1

I updated Germán Carillo’s response to PyQGIS 3 and added the prefix concept offered by Richard. In my case, I have over 50 pairs of point and vector layers. Each pair has a unique prefix to the layer name. I want to save a PNG file of each pair. Because of the number of PNG files to create, I prefer using the timer approach to avoid repeatedly typing “prepare” and “export”. Setting the timer to 1000 milliseconds if fine for my case. If it can’t be rendered in one second and exported in one second, then the timer value can be increased.

from PyQt5.QtCore import QTimer

filePath = 'C:\\myPath\\' # exported is a prefix for the file names
prefixes = ['1000', '2000', '3000', '4000', '5000'] 
count = 0

def prepareMap(): # Arrange layers
    layers = []
    layers = QgsProject.instance().mapLayers()

    # First, turn off all layers
    for layer in layers:
        QgsProject.instance().layerTreeRoot().findLayer(layer).setItemVisibilityChecked(False)

    # Second, get the layer to export
    exportLayers = []
    for layer in QgsProject.instance().mapLayers().values():
        if layer.name().startswith(prefixes[count]):
            QgsProject.instance().layerTreeRoot().findLayer(layer).setItemVisibilityChecked(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( filePath + prefixes[count] + ".png" )
    print("Map with layer",count,"exported!")
    if count < len(prefixes)-1:
        QTimer.singleShot(1000, prepareMap) # Wait a second and prepare next map
    count += 1

prepareMap() # Let's start the fun

Your Answer

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

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