I'm trying to export multiple layers in a single image file using PyQgis (QGIS 2.18), however so far only manage to render one layer at a time, not both together. If I try to render both, only the last one added in the script gets plotted.
Here's a sketch showing what I'm trying to achieve:
I tried using canvas.setLayerSet to add both layers to the canvas, assuming the following command can merge multiple layers:
# set the map canvas layer set
canvas.setLayerSet([QgsMapCanvasLayer(layer)])
However, this has no effect, and only the last layer added in the script gets rendered in the PNG file when running:
# Try to add multiple layers to canvas
canvas.setLayerSet([QgsMapCanvasLayer(lyr_perimeter),QgsMapCanvasLayer(lyr_blgs)])
What would be the correct way to force multiple layers to be rendered together?
So far I've tried:
- Rendering only one layer at a time --> both work individually (cf. image above)
- Rendering both within QGIS 2.18 GUI --> both can be viewed together (cf. image above)
- Using the simple rendering approach as described in the documentation, however this raises
AttributeError: 'QgsVectorLayer' object has no attribute 'getLayerID'
when using getLayerID (I assume this function is only available in QGIS 3.0) - Previously posted a related question, but this question narrows the problem to two layers, specifies new attempts, and provides a (hopefully) clearer minimal script.
Here's the the minimal script being used:
#!/usr/bin/env python2
#############################
#Import modules for PyQGIS commands
#############################
from qgis.core import *
import os.path
import sys
from qgis.gui import * #QgsMapCanvas, QgsLayerTreeMapCanvasBridge
# from PyQt4.QtCore import QFileInfo, QObject
from PyQt4.QtCore import * #Required for NameError QSize missing, so import all for PNG export
from PyQt4.QtXml import QDomDocument
from PyQt4.QtGui import * #Required for PNG export (NameError QImage is not defined, see https://gis.stackexchange.com/questions/88660/nameerror-name-qimage-is-not-defined)
from inputs import connection_data #loads from frontend.py the DB connection data
#############################
#Database connection data to be used in queries below
#############################
#here are input variables for connection#
#############################
#Various PyQgis specifications
#############################
gui_flag = True #Pass true to indicate using a GUI for a custom application (as opposed to standalone script)
app = QgsApplication(sys.argv, gui_flag)
app.initQgis()
# Set output DPI
dpi = 300
#############################
#Define paths for input and output files
#############################
my_path = os.path.abspath(os.path.dirname(__file__)) #gets absolute path to script
root_path = os.path.join(my_path, '../../media/maps/qgis') #goes to relative upper dir
project_path = '/prjs/autoexport_empty.qgs'
result_path = '../../media/maps/tmp'
result_path = os.path.join(my_path, '../../media/maps/tmp')
#############################
#Main function inputs to be passed
#############################
scenario_id = str(sys.argv[1]) #1315
cityobjectgroup_id = str(sys.argv[2]) #27787
def make_pdf():
canvas = QgsMapCanvas()
# Load our project
QgsProject.instance().read(QFileInfo(root_path + project_path))
# Load layers here if they are not already in the project
#for layer in QgsMapLayerRegistry.instance().mapLayers().values():
#if layer.name() == 'data':
#lyr = layer
#break
#lyr.setSubsetString(filter_expression)
uri = QgsDataSourceURI()
# set host name, port, database name, username and password
uri.setConnection("{}".format(host), "{}".format(port), "{}".format(dbname), "{}".format(user), "{}".format(password))
##########################################################
#Add first layer to image
##########################################################
# PERIMETER
# Set database schema, table name, geometry column and optionally a subset (WHERE clause)
uri.setDataSource(schema_name2, "geo_girec", "geom", "numero = '2800152'")
# Define primary key of view (https://gis.stackexchange.com/questions/151686/selecting-column-like-primary-key-in-view-from-postgis-using-pyqgis)
uri.setKeyColumn('numero')
# Load layer to PyQgis environment
print "loading PERIMETER layer"
lyr_perimeter = QgsVectorLayer(uri.uri(False), "perimeter", "postgres")
# Check if layer was correctly loaded to PyQgis and if so, process its style
if not lyr_perimeter.isValid():
print "Layer 'lyr_perimeter' failed to load! Probably not existent."
else:
# Add layer to the registry
QgsMapLayerRegistry.instance().addMapLayer(lyr_perimeter)
print "QgsMapLayerRegistry correctly added map layer lyr_perimeter"
# Set canvas extent
canvas.setExtent(lyr_perimeter .extent())
# Load style file corresponding to the layer
lyr_perimeter.loadNamedStyle(root_path + '/layerStyles/perimeter_style' + '.qml')
print "Checking root path:"
print root_path + '/layerStyles/perimeter_style' + '.qml'
lyr_perimeter.triggerRepaint()
##########################################################
#Add second layer to image
##########################################################
# BUILDINGS
# Set database schema, table name, geometry column and optionally a subset (WHERE clause)
uri.setDataSource(schema_name, "building_view", "geometry", "scenario_id = " + scenario_id + " AND cityobjectgroup_id = " + cityobjectgroup_id)
# Define primary key of view (https://gis.stackexchange.com/questions/151686/selecting-column-like-primary-key-in-view-from-postgis-using-pyqgis)
uri.setKeyColumn('id,scenario_id,cityobjectgroup_id')
# Load layer to PyQgis environment
print "loading BUILDING_VIEW layer"
lyr_blgs = QgsVectorLayer(uri.uri(False), "buildings", "postgres")
# Check if layer was correctly loaded to PyQgis and if so, process its style
if not lyr_blgs.isValid():
print "Layer 'building_view' failed to load! Probably not existent."
else:
# Add layer to the registry
QgsMapLayerRegistry.instance().addMapLayer(lyr_blgs)
print "QgsMapLayerRegistry correctly added map layer lyr_blgs"
# Set canvas extent
canvas.setExtent(lyr_blgs.extent())
# Try to add multiple layers to canvas --> this has no effect! only lyr_blgs gets printed to PNG
canvas.setLayerSet([QgsMapCanvasLayer(lyr_perimeter),QgsMapCanvasLayer(lyr_blgs)])
# Load style file corresponding to the layer
lyr_blgs.loadNamedStyle(root_path + '/layerStyles/citygml/2.5d-height_label-floorstotal' + '.qml') #previously from sty object
print "Checking root path:"
print root_path + '/layerStyles/citygml/2.5d-height_label-floorstotal' + '.qml'
lyr_blgs.triggerRepaint()
##########################################################
#Render map to PNG
##########################################################
# Bridge used in standalone script: http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/loadproject.html; has to be created before further layers are loaded
bridge = QgsLayerTreeMapCanvasBridge(
QgsProject.instance().layerTreeRoot(), canvas)
bridge.setCanvasLayers()
# Load print composer template
template_path = root_path + '/printComposers/autoexport_Palettes' + '.qpt' #previously from sty object sty['qpt']
template_file = file(template_path)
template_content = template_file.read()
template_file.close()
print "Finished loading print composer template!"
document = QDomDocument()
document.setContent(template_content)
composition = QgsComposition(canvas.mapSettings())
# You can use this to replace any string like this [key]
# in the template with a new value. e.g. to replace
# [date] pass a map like this {'date': '1 Jan 2012'}
# substitution_map = {
# 'title': 'the title of my map'}
composition.loadFromTemplate(document, {})
# You must set the id in the print composer template
map_item = composition.getComposerItemById("myMap") # to verify if this is the right idea, otherwise try also 'main' or 'map 0'
map_item.setMapCanvas(canvas)
map_item.zoomToExtent(canvas.extent()) # or use directly extent of building_view "layer"
# You must set the id in the template
#legend_item = composition.getComposerItemById('Legend')
#legend_item.updateLegend()
composition.refreshItems()
# Export to png
dpmm = dpi / 25.4
width = int(dpmm * composition.paperWidth())
height = int(dpmm * composition.paperHeight())
# create output image and initialize it
image = QImage(QSize(width, height), QImage.Format_ARGB32)
image.setDotsPerMeterX(dpmm * 1000)
image.setDotsPerMeterY(dpmm * 1000)
image.fill(0)
# render the composition
imagePainter = QPainter(image)
composition.renderPage(imagePainter, 0)
imagePainter.end()
# Save image as PNG file
image.save(result_path + '/' + "myPNG_" + scenario_id + '.png', "png")
QgsProject.instance().clear()
'QgsVectorLayer' object has no attribute 'getLayerID'
about this, try usinglyr_perimeter.id()
instead ofgetLayerId()
.lst = [lyr_perimeter.getLayerID()]
withlst = [QgsMapCanvasLayer(lyr_perimeter)]
. But withlyr_subsectors2.id()
it now works fine. Therefore, to add multiple layers, one must simply list all layer IDs like:lst = [lyr_blgs.id(),lyr_perimeter.id()]
, loosely based on the doc which says to# add ID of every layer
. If you wish to post your comment as answer, amending the documentation, I'll accept it as it solved my problem.