Зачекайте, коли полотно закінчить візуалізацію, перш ніж зберегти зображення


11

Я намагаюся написати сценарій, який збереже візуалізацію декількох шарів за допомогою композитора карт. Проблема, з якою я стикаюся, полягає в тому, що скрипт зберігається до того, як qgis закінчить візуалізацію всіх шарів.

На підставі кількох інших відповідей ( 1 , 2 , 3 ) я спробував використати iface.mapCanvas.mapCanvasRefreshed.connect()і поставити збереження зображення всередині функції, але я все ще стикаюся з тією ж проблемою - зображення не містять усіх шарів.

Код, який я використовую, а також зображення того, як виглядає головне вікно та візуалізація, перераховані нижче.

Я помітив, що якщо я відкрию вікно консолі та відмежую три print layerListрядки, програма чекатиме завершення візуалізації, перш ніж зберегти зображення. Я не впевнений, чи це пов’язано із збільшенням часу на обробку, чи зміниться спосіб виконання програми.

Як я правильно це реалізую, щоб усі шари були включені до зображення?

from qgis.core import *
from qgis.utils import *
from qgis.gui import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os.path

##StackExchange Version=name
##Map_Save_Folder=folder
##Map_Save_Name=string roadmap

# Create save file location
mapName = "%s.png" %Map_Save_Name
outfile = os.path.join(Map_Save_Folder,mapName)
pdfName = "%s.pdf" %Map_Save_Name
outPDF = os.path.join(Map_Save_Folder,pdfName)

# Create point and line layers for later
URIstrP = "Point?crs=EPSG:3035"
layerP = QgsVectorLayer(URIstrP,"pointsPath","memory")
provP = layerP.dataProvider()
URIstrL = "LineString?crs=EPSG:3035"
layerL = QgsVectorLayer(URIstrL,"linePath","memory")
provL = layerL.dataProvider()

# Add points to point layer
feat1 = QgsFeature()
feat2 = QgsFeature()
feat3 = QgsFeature()
feat1.setGeometry(QgsGeometry.fromPoint(QgsPoint(5200000,2600000)))
feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(5300000,2800000)))
provP.addFeatures([feat1, feat2])

# Add line to line layer
feat3.setGeometry(QgsGeometry.fromPolyline([feat1.geometry().asPoint(),feat2.geometry().asPoint()]))
provL.addFeatures([feat3])

# Set symbology for line layer
symReg = QgsSymbolLayerV2Registry.instance()
metaRegL = symReg.symbolLayerMetadata("SimpleLine")
symLayL = QgsSymbolV2.defaultSymbol(layerL.geometryType())
metaL = metaRegL.createSymbolLayer({'width':'1','color':'0,0,0'})
symLayL.deleteSymbolLayer(0)
symLayL.appendSymbolLayer(metaL)
symRendL = QgsSingleSymbolRendererV2(symLayL)
layerL.setRendererV2(symRendL)

# Set symbology for point layer
metaRegP = symReg.symbolLayerMetadata("SimpleMarker")
symLayP = QgsSymbolV2.defaultSymbol(layerP.geometryType())
metaP = metaRegP.createSymbolLayer({'size':'3','color':'0,0,0'})
symLayP.deleteSymbolLayer(0)
symLayP.appendSymbolLayer(metaP)
symRendP = QgsSingleSymbolRendererV2(symLayP)
layerP.setRendererV2(symRendP)

# Load the layers
QgsMapLayerRegistry.instance().addMapLayer(layerP)
QgsMapLayerRegistry.instance().addMapLayer(layerL)
iface.mapCanvas().refresh()


# --------------------- Using Map Composer -----------------
def custFunc():
    mapComp.exportAsPDF(outPDF)
    mapImage.save(outfile,"png")
    mapCanv.mapCanvasRefreshed.disconnect(custFunc)
    return

layerList = []
for layer in QgsMapLayerRegistry.instance().mapLayers().values():
    layerList.append(layer.id())
#print layerList
#print layerList
#print layerList

mapCanv = iface.mapCanvas()
bound = layerP.extent()
bound.scale(1.25)
mapCanv.setExtent(bound)

mapRend = mapCanv.mapRenderer()
mapComp = QgsComposition(mapRend)
mapComp.setPaperSize(250,250)
mapComp.setPlotStyle(QgsComposition.Print)

x, y = 0, 0
w, h = mapComp.paperWidth(), mapComp.paperHeight()

composerMap = QgsComposerMap(mapComp, x, y, w, h)
composerMap.zoomToExtent(bound)
mapComp.addItem(composerMap)
#mapComp.exportAsPDF(outPDF)

mapRend.setLayerSet(layerList)
mapRend.setExtent(bound)

dpmm = dpmm = mapComp.printResolution() / 25.4
mapImage = QImage(QSize(int(dpmm*w),int(dpmm*h)), QImage.Format_ARGB32)
mapImage.setDotsPerMeterX(dpmm * 1000)
mapImage.setDotsPerMeterY(dpmm * 1000)

mapPaint = QPainter()
mapPaint.begin(mapImage)

mapRend.render(mapPaint)

mapComp.renderPage(mapPaint,0)
mapPaint.end()
mapCanv.mapCanvasRefreshed.connect(custFunc)
#mapImage.save(outfile,"png")

Як це виглядає в головному вікні QGIS (на ньому відображається випадкова растрова карта): введіть тут опис зображення

Що збережено: введіть тут опис зображення

В якості додаткової інформації я використовую QGIS 2.18.7 в Windows 7


Я також посилався на кілька веб-сторінок, 1 і 2 . Я намагався додати їх для публікації, але мій представник недостатньо високий
EastWest

У вашій другий останньому рядку, спробуйте замінити mapCanv.mapCanvasRefreshed.connect(custFunc)з mapCanv.renderComplete.connect(custFunc)?
Йосип

@Joseph На жаль, це, схоже, не змінило значення. Я все одно отримую той же результат, що і вище
EastWest

Можливо, спробуйте зафіксувати функції, які ви додали до шару? (тобто layerP .commitChanges()). Хоча я не бачу, чому це має допомогти, оскільки ви лише зберігаєте зображення, але варто спробувати, я думаю. Інакше, сподіваємось, інші можуть порадити :)
Йосип

@Joseph Я намагався commitChanges(), але не пощастило, на жаль. Дякую за пропозицію.
EastWest

Відповіді:


5

Тут виникають різні питання

Візуалізація на екрані та надання зображення

Сигнал mapCanvasRefreshedвипромінюється неодноразово, коли полотно виводиться на екран. Для екранного відображення це дає швидший зворотний зв'язок, який може бути приємним для користувача, щоб побачити щось, що відбувається або допомогти у навігації.

Для візуалізації поза екраном, як збереження у файлі, це не є надійним (оскільки повне зображення ви матимете лише тоді, коли візуалізація пройшла досить швидко).

Що можна зробити: нам не потрібно полотно на карті, щоб зробити ваше зображення. Ми можемо просто скопіювати зображення QgsMapSettingsз полотна карти. Ці параметри - це параметри, які надсилаються рендеріру і визначають, що саме та як саме потрібно перетворити з усіх постачальників даних на растрове зображення.

Полотно реєстру шару проти карти

Шари, додані до реєстру, не закінчуються на полотні одразу, а лише в наступному циклі циклу подій. Тому вам краще зробити одну з наступних двох речей

  • Почніть відображення зображення в таймері. QTimer.singleShot(10, render_image)

  • Запустити QApplication.processEvents()після додавання шару. Це працює, але це небезпечний заклик до використання (іноді призводить до дивних збоїв), і тому його слід уникати.

Покажіть мені код

Наступний код робить це (трохи відрегульований від QFieldSync , загляньте туди, якщо вас зацікавило більше налаштування)

from PyQt4.QtGui import QImage, QPainter

def render_image():
    size = iface.mapCanvas().size()
    image = QImage(size, QImage.Format_RGB32)

    painter = QPainter(image)
    settings = iface.mapCanvas().mapSettings()

    # You can fine tune the settings here for different
    # dpi, extent, antialiasing...
    # Just make sure the size of the target image matches

    # You can also add additional layers. In the case here,
    # this helps to add layers that haven't been added to the
    # canvas yet
    layers = settings.layers()
    settings.setLayers([layerP.id(), layerL.id()] + layers)

    job = QgsMapRendererCustomPainterJob(settings, painter)
    job.renderSynchronously()
    painter.end()
    image.save('/tmp/image.png')

# If you don't want to add additional layers manually to the
# mapSettings() you can also do this:
# Give QGIS give a tiny bit of time to bring the layers from 
# the registry to the canvas (the 10 ms do not matter, the important
# part is that it's posted to the event loop)

# QTimer.singleShot(10, render_image)

1
Будь-яка ідея про renderCompleteсигнал не працює?
Йосип

Дякую за всю інформацію! На жаль, я спробував вставити запропонований код у свій сценарій, повністю замінивши розділ композитора карт, і все ще стикаюся з тією ж проблемою. Збережене зображення не включає шари ліній чи точок, лише попередньо завантажений растр.
EastWest

Я думаю, що сигнал, який випромінюється між завданням, закінчений, і зображення виводиться на екран. З нею painterвипромінюється параметр, за яким ви все ще можете намалювати додаткові речі, які опиняться на кінцевому зображенні (і з якого ви, ймовірно, могли також взяти остаточне зображення, щоб цей підхід працював).
Маттіас Кун

1
Це виглядає як рішення. Дякуємо за всю вашу допомогу. Одне швидке зауваження, якщо хтось інший виявить це - щоб змусити QTimer працювати належним чином, залишити дужки після render_image, інакше python видає попередження. Слід прочитатиQTimer.singleShot(10, render_image)
EastWest

На жаль, звичайно. Зафіксовано в коді вище
Маттіас Кун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.