0

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:

sketch of multiple layers individually or jointly plotted

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()
  • 1
    'QgsVectorLayer' object has no attribute 'getLayerID' about this, try using lyr_perimeter.id() instead of getLayerId(). – xlDias May 11 '18 at 14:10
  • Thank you! I had previously (unsuccesfully) tried replacing lst = [lyr_perimeter.getLayerID()] with lst = [QgsMapCanvasLayer(lyr_perimeter)]. But with lyr_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. – sc28 May 11 '18 at 15:33
1

Following the documentation, you will only have to change layer.getLayerId() to layer.id():

# set layer set
lst = [layer.id()]  # add ID of every layer
render.setLayerSet(lst)

I figured this out by "accident", but the id() function is described here, as for the getLayerId() function, I could not find any docs. I proposed a change to the "simple rendering" documentation to fix this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.