Friday, 14 August 2015

qgis - Iterating over layers and export them as PNG images with PyQGIS in a standalone script?


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)

Answer




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.



No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...