Otra pregunta más sobre PyQgis... lo siento, pero soy un absoluto principiante con esto. Tengo un script que funciona bien cuando lo ejecuto a través de la consola de QGIS (ya sea paso a paso o usando "Run Selected" en el editor). Sin embargo, cuando simplemente hago clic en "Ejecutar" en el editor, obtengo resultados diferentes. Lo que hago es
- Cargar una capa de trama
- Seleccione la característica en la capa vectorial existente y haga zoom sobre ella
- Aplicar el estiramiento del histograma local
- Crear un mapa y exportarlo
Con la solución paso a paso, asumo que el script está "esperando" a que termine cada acción, lo cual puedo ver ya que el contenido del mapa cambia constantemente. Esto no ocurre cuando ejecuto el script desde el editor. ¿Por qué? ¿Hay alguna solución?
time.sleep() no hizo el truco, y no puedo conseguir mi cabeza alrededor Clase QTimer
Estoy usando QGIS 2.14. Este es mi código completo (la parte principal ocurre cerca del final, cuando disparo el tramo):
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)
Con la ayuda de @LaughU y este puesto Finalmente conseguí que funcionara. Mi solución ahora se parece a esto:
- Cargar todas las capas que quiero utilizar
- ¡imprimir una línea inútil! lo cual es muy importante porque estoy cargando una capa rasterizada que necesita tiempo para renderizar
- utilizar una función más o menos inútil que no hace nada, pero que está conectada al
mapCanvas().mapCanvasRefreshed
señal- llamar a mi función de exportación (que tomará algunos argumentos que necesito establecer) después
Así que mi código ahora se ve así:
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 ###
¿Por qué es tan molesto?
0 votos
¿Dónde está el código completo de PyQGIS para su pregunta?
0 votos
@xunilk - añadido
0 votos
¿Usaste
time.sleep()
directamente antes de la líneaexportMap(template, canvas, bbox, outfile)
? Y si es así, ¿le diste un tiempo de espera decente (por ejemplo, esperar 10 segundos:time.sleep(10)
)?0 votos
Sí, así es. Cuando se utiliza la interfaz gráfica de usuario, por lo general, tarda entre 1 y 2 segundos, y utilicé 10 para hacer la prueba. El mismo resultado, así que busqué por ahí y encontré Clase QTimer pero no sé cómo usarlo correctamente.
0 votos
Hay una señal que se dispara una vez que el mapcomposer se renderiza. Si utiliza esto para conectarse a su función de exportación obtendrá un buen resultado
0 votos
Esto puede ayudar: gis.stackexchange.com/questions/189735/ (utiliza QTimer)
0 votos
@LaughU - ¿Sabes qué señal es esta y cómo puedo cogerla?
0 votos
@XIY - Gracias, yo también encontré este y traté de hacerlo así usando
QTimer.singleShot(1000, exportMap(template, canvas, bbox, outfile))
pero no funcionó y recibí el mensaje de errorTypeError: arguments did not match any overloaded call: QTimer.singleShot(int, QObject, SLOT()): argument 2 has unexpected type 'NoneType' QTimer.singleShot(int, callable): argument 2 has unexpected type 'NoneType'
0 votos
@s6hebern ver mi respuesta para una explicación. p.d. por favor no me digas que estudiaste en un pueblo del suroeste de Alemania que empieza con T...
0 votos
@s6hebern
QTimer.singleShot()
espera una función invocable como segundo argumento que luego se llamará a sí misma, por ejemplo, siexportMap()
no aceptó ningún argumento,QTimer.singleShot(1000, exportMap)
sería la sintaxis correcta - tenga en cuenta que no está llamando a la función, sólo la suministra asingleShot()
. No se puede suministrar una función y sus argumentos de esta manera, por lo que tendría que utilizarpartial
. por ejemplofrom functools import partial
y luegoQTimer.singleShot(1000, partial(exportMap, template, canvas, bbox, outfile))
.partial()
devuelve una función invocable quesingleShot()
puede utilizar más tarde.0 votos
¡Una vez explicado esto, la solución de @LaughU parece mucho más bonita si te funciona!