5

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:

  1. Load one raster layer
  2. Select feature in existing vector layer and zoom to it
  3. Apply Local Histogram Stretch
  4. 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?

11
  • Where is the complete PyQGIS code for your question?
    – xunilk
    Commented Nov 23, 2017 at 14:37
  • @xunilk - added it
    – s6hebern
    Commented Nov 23, 2017 at 15:12
  • Did you use time.sleep() directly before the line exportMap(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))?
    – Joseph
    Commented Nov 24, 2017 at 10:12
  • Yes, I did. When using the GUI, it usually takes 1-2 seconds, and I used 10 for testing. Same result, so I searched around and found QTimer Class, but I don't know how to use it properly.
    – s6hebern
    Commented Nov 24, 2017 at 11:53
  • There is a signal which is fired once the mapcomposer is rendert. If you use this to connect to you export function you will get a good result
    – LaughU
    Commented Nov 24, 2017 at 22:51

1 Answer 1

2

I am using QGIS 2.18.13 and I encountered the same problem. The solution for me was to connect my map creation function when the map was finished refreshing.

My condensed code looks this:

def load_layer(self, temp_layer):
    # loads my layers which I want to display
    QgsMapLayerRegistry.instance().addMapLayer(temp_layer) # for demo
    self.iface.mapCanvas().refreshAllLayers()
    self.iface.mapCanvas().mapCanvasRefreshed.connect(self.mapcreater)

def mapcreater(self, templet):
    self.iface.mapCanvas().mapCanvasRefreshed.disconnect(self.mapcreater) # importen else you could end up in a loop
    template_file = file(templet, 'rt')
    template_content = template_file.read()
    template_file.close()
    prepared_name = "my_name.pdf" 
    maprender = self.iface.mapCanvas().mapRenderer()
    composition = QgsComposition(maprender)
    docu = QDomDocument()
    docu.setContent(template_content)
    composition.loadFromTemplate(docu)
    composition.setUseAdvancedEffects(False)
    map_report = composition.getComposerMapById(0)
    map_report.zoomToExtent(maprender.extent())

    self.iface.mapCanvas().refreshAllLayers()
    composition.refreshItems()
    composition.exportAsPDF(os.path.join("C:\my_folder\", prepared_name)) 

The worflow would be:

  • Disable all layers you dont want to show
  • Load all layers you want to show
  • Refresh the layers with iface.mapCanvas().refreshAllLayers()
  • Connect the mapCanvasRefreshed signal to you map creation function
  • Inside your map creation function disconnect the signal again
  • Create the map and maybe do some clean up of layers and visablity
1
  • Great stuff, I'll adjust my script and report back with the results
    – s6hebern
    Commented Nov 27, 2017 at 9:51

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.