0

I'm trying to automate the creation of images using a combination of QGIS map documents and templates as discussed here using a standalone script. When complete, the script will regenerate a map using a new boundary set at run time, turning off layers that aren't used in a particular composer, and turning on the ones that are.

I'm able to generate images, adding various map elements (map items, legend, scale bar, etc.), but my test script isn't working when I need to change which layers are visible and adjust the extent accordingly. Everything seems to work except setting the visibility of the layer itself. Right now, only the layers that are checked whenever the qgis project is saved appear in the exported map. Based on the feedback here and here , I've tried adding a timer between each setVisible() call, but that still doesn't seem to work. I've also tried calling the setVisible() method inside a map and it works, it's only when it's run external from QGIS that it doesn't seem to affect layer visibility.

import sys 
from qgis.core import (QgsProject, QgsComposition, QgsApplication, 
                      QgsMapLayerRegistry)
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge 
from PyQt4.QtCore import QFileInfo, QSize 
from PyQt4.QtXml import QDomDocument 
from PyQt4.QtGui import QImage, QPainter 
import os 

os.chdir("/home/project_name") 
gui_flag = True 
app = QgsApplication(sys.argv, True) 
QgsApplication.setPrefixPath("/usr", True) 
QgsApplication.initQgis() 

project_path = 'report_maps.qgs' 
template_path = 'location_overview.qpt' 

canvas = QgsMapCanvas() 
canvas.resize(QSize(1450, 850)) 
QgsProject.instance().read(QFileInfo(project_path)) 
root = QgsProject.instance().layerTreeRoot() 
bridge = QgsLayerTreeMapCanvasBridge(root, canvas) 
bridge.setCanvasLayers() 
registry = QgsMapLayerRegistry.instance() 

template_file = file(template_path) 
template_content = template_file.read() 
template_file.close() 
document = QDomDocument() 
document.setContent(template_content) 

map_settings = canvas.mapSettings()
composition = QgsComposition(map_settings) 
composition.loadFromTemplate(document) 


#create list of all layers currently in the map 
map_layers = [lyr for  lyr in registry.mapLayers() if root.findLayer(lyr)] 
#these layers are common for all maps
basemap = ["streets_labels", "streets_carto", "sca_parcels",
            "boundary", "boundary_mask"]  

#this section sets up an inset map that shows the project location
#in context of the region
inset_layers = ["boundary", "tiger_place_2016", "streets_carto_inset"] 

# #this loop is to activate layers shown in inset map 
inset_visible = []
for map_lyr in map_layers: 
    lyr = root.findLayer(map_lyr) 
    if lyr.layerName() in inset_layers:
        inset_visible.append(map_lyr)
        lyr.setVisible(2) 
    else: 
        lyr.setVisible(0) 

all_cities = registry.mapLayersByName("tiger_place_2016")[0] 
ids_all_cities = [f.id() for f in all_cities.getFeatures()]
canvas.zoomToFeatureIds(all_cities, ids_all_cities)
canvas.zoomScale(canvas.scale() * 1.2)
map_settings.setLayers(inset_visible)
inset_map = composition.getComposerItemById("inset_map") 
inset_map.setMapCanvas(canvas)
inset_map.setNewExtent(canvas.extent())

#lock the inset layers style and extent to switch to larger map 
inset_map.setLayerSet(inset_visible)
inset_map.setKeepLayerSet(True) 

#this section sets up the thematic layers for this section which zooms
#in on the specific project boundary     
visible_layers = []
for map_lyr in map_layers: 
    lyr = root.findLayer(map_lyr) 
    if lyr.layerName() in basemap + ["bldg_2014"]: 
        visible_layers.append(map_lyr)
        lyr.setVisible(2) 
    else: 
        lyr.setVisible(0)

boundary = registry.mapLayersByName("boundary")[0] 
ids = [f.id() for f in boundary.getFeatures()]
canvas.zoomToFeatureIds(boundary, ids)
canvas.zoomScale(canvas.scale() * 1.2)
map_settings.setLayers(visible_layers)

map_item = composition.getComposerItemById('main_map') 
map_item.setMapCanvas(canvas) 
map_item.setNewExtent(canvas.extent())
composition.refreshItems() 

#set up the image for export     
dpi = 300 
dpmm = dpi / 25.4 
width = int(dpmm * composition.paperWidth()) 
height = int(dpmm * composition.paperHeight()) 

image = QImage(QSize(width, height), QImage.Format_ARGB32) 
image.setDotsPerMeterX(dpmm * 1000) 
image.setDotsPerMeterY(dpmm * 1000) 
image.fill(0) 

imagePainter = QPainter(image) 
composition.renderPage(imagePainter, 0) 
imagePainter.end() 
image.save("project_output.jpg", "jpg") 

QgsProject.instance().clear() 
QgsApplication.exitQgis()     
1

You set the QgsComposition to use the mapSettings before you set the layer visibility. Either move the line:

composition = QgsComposition(canvas.mapSettings())

below the section where you set the visibilty or use mapSettings.setLayers() to manually add the layers you want to the exported image.

  • Thanks for the suggestion. This doesn't seem to fix it. I placed both the line where I instantiate the composition object and the line where I call loadFromTemplate() directly after the loop where I set the layer visibility and it still only shows layers that are already visible in the saved project file. I also tried adding the layers manually using mapSettings.setLayers() and it didn't seem to work. – neighdough Sep 6 '18 at 22:20
  • Does the style have scale dependant rendering? – firefly-orange Sep 7 '18 at 7:14
  • 1
    Oh, and make sure you call mapSettings.setLayers() before instantiating the composition object – firefly-orange Sep 7 '18 at 10:02
  • This last comment put me on the right path. I still instantiate the composition object before setting the visibility on the layers, but the trick is to set visibility and to add the layer to the layerSet. I've corrected the code above to reflect the changes.Thanks for the help! – neighdough Sep 7 '18 at 19:30

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.