Yet another PyQgis question... sorry, but I'm an absolute beginner with this. I have a script running nicely when I execute it through the QGIS console (either step by step or using "Run Selected" in the editor). However, when I just click "Run" in the editor, I get different results. What I do is:
- Load one raster layer
- Select feature in existing vector layer and zoom to it
- Apply Local Histogram Stretch
- Create map and export it
With the step by step solution, I assume the script is "waiting" for each action to finish, which I can see as the map content is constantly changing. This is not the case when I run the script from the editor. Why? Is there some workaround?
time.sleep() did not do the trick, and I can't get my head around QTimer Class
I'm using QGIS 2.14. This is my full code (the main part happens near the end, when I trigger the stretch):
import os, time
from PyQt4.QtCore import *
from PyQt4.QtXml import *
from PyQt4.QtGui import *
from qgis import *
image_path = PATH_TO_IMAGE
export_path = EXPORT_PATH
template = PATH_TO_TEMPLATE
def exportMap(tempFile, canvas, bbox, outName):
template_file = file(tempFile)
template_content = template_file.read()
template_file.close()
document = QDomDocument()
document.setContent(template_content)
composer = QgsComposition(canvas.mapSettings())
composer.loadFromTemplate(document)
map_item = composer.getComposerItemById('main_map')
map_item.setMapCanvas(canvas)
bbox.scale(1.2)
map_item.zoomToExtent(bbox)
canvas.refresh()
map_item.updateCachedImage()
legend_item = composer.getComposerItemById('legend')
legend_item.updateLegend()
composer.refreshItems()
composer.update()
composer.exportAsPDF(outName)
# get map canvas
canvas = iface.mapCanvas()
li = iface.legendInterface()
# list all layers
layers = li.layers()
# get specific layer by displayed name
lakes = [l for l in layers if l.name() == 'lakes'][0]
iface.legendInterface().setLayerVisible(lakes, True)
# load raster
image = image_path
raster = QgsRasterLayer(image, QFileInfo(image).baseName())
iface.addRasterLayer(image)
canvas.refresh()
# make sure vector layer is on top
root = QgsProject.instance().layerTreeRoot()
for ch in root.children():
if ch.layerName() == lakes.name():
clone = ch.clone()
root.insertChildNode(0, clone)
root.removeChildNode(ch)
for feat in lakes.getFeatures():
outname = abbrev[feat.attribute('name')] + '_' + QFileInfo(image).baseName() + '.pdf'
outfile = os.path.join(export_path, outname)
# select single feature based on attribute "name"
expr = QgsExpression(""" \"name\" = '{0}' """.format(feat.attribute('name')))
selection = [l.id() for l in lakes.getFeatures(QgsFeatureRequest(expr))]
lakes.setSelectedFeatures(selection)
# zoom to selected feature
canvas.zoomToSelected(lakes)
# bounding box of selected feature
bbox = lakes.boundingBoxOfSelected()
# deselect
lakes.setSelectedFeatures([])
# apply local stretch to active raster layer
iface.setActiveLayer(raster)
iface.mainWindow().findChild(QAction, 'mActionLocalCumulativeCutStretch').trigger()
exportMap(template, canvas, bbox, outfile)
# remove raster
for ch in root.children():
if ch.layerName() == raster.name():
root.removeChildNode(ch)
With the help of @LaughU and this post, I finally got it to work. My workaround now looks like this:
- Load all layers I want to use
- print a useless line!, which is very important because I'm loading a raster layer that needs time to render
- use a more or less useless function that does nothing, but is connected to the
mapCanvas().mapCanvasRefreshed
signal- call my export-function (which shall take some arguments I need to set) afterwards
So my code now looks like this:
canvas = iface.mapCanvas()
li = iface.legendInterface()
# list all layers
layers = li.layers()
myLayer = [l for l in layers if l.name() == 'myName'][0]
# dummy function
def dummyAction():
pass
# load raster
image = 'PATH_TO_RASTER'
raster = QgsRasterLayer(image, QFileInfo(image).baseName())
iface.addRasterLayer(image)
### LOAD ALL ADDITIONAL LAYERS, THEN: ###
print 'This is a useless line, but forces the map renderer to wait!'
# apply local stretch to active raster layer
iface.setActiveLayer(raster)
iface.mainWindow().findChild(QAction, 'mActionLocalCumulativeCutStretch').trigger()
### SET UP COMPOSER, THEN: ###
canvas.refresh()
canvas.mapCanvasRefreshed.connect(dummyAction)
### DO THE ACTUAL EXPORT ###
Why is this such a pain?
time.sleep()
directly before the lineexportMap(template, canvas, bbox, outfile)
? And if so, did you give it a decent time to wait (e.g. to wait 10 seconds:time.sleep(10)
)?