Using Qgis Python Scripting in the NZ Rail Maps Project: 2 – Volumes Creation

The second and third aspects of Qgis scripting used in the NZ Rail Maps Project are connected with Volume maps creation. In the Volumes website, static map images are used. Each static image is manually composed, by positioning the map in the composer layout window, and combining the selection of different layers, rotation and zoom levels. Generally each map view is then produced in multiple actual images, with one image for a combined diagram view and where available, multiple images for each available generation of aerial photography.

Scripting for Volumes creation in the Qgis GUI is concerned with running a script in the GUI that automates the creation of Volumes map images by automatically selecting source layers in the GUI. These layers are grouped as “Diagram Layers”, “Base Aerial Layers” and “Current Aerial Layers”. Base Aerial Layers are those which are used to generate different base layers in the WebMaps site. Current Aerial Layers are not used in the WebMaps site because the user can select the Linz Basemaps background in the browser instead of a historical layer. Under the Base Aerial Layers group, generation ranges of usually five years are defined as sub groups and these are used to store the actual aerial mosaic tile images, further grouped by location and actual year. Under Current Aerial Layers, each sub group is for a single year and the majority of source layers are WMTS sourced live directly from the Linz website. The other key difference between Current Aerial Layers and Base Aerial Layers is that vector layer data is formatted differently in the former, with more layer data displayed than in the latter.

The steps needed to run the script are simple. Provided all the source layers have been properly organised in the GUI project with their names in the correct format, the user will input a variable called “Filename”. The input is typed into the Composer GUI as the label text for a “Filename” label that is displayed on the bottom information bar of each Volume image. The user will then run the script directly in the map canvas GUI.

The initial section of the script contains input variables and constants. These specify the path where the Volumes map images should be stored, the name of the composer layout used to generate the Volumes map images, and the names of key layer groups within the map canvas GUI. Most of the script is concerned with extracting the year portion of the filename input text and using that to select the appropriate subgroups within each top level layer group, as well as that top level group where necessary. If the user has put “0000” as the year then the Diagram Layers top level layer group is selected, otherwise both of the Base Aerial Layers and Current Aerial Layers are searched to find a match for the year specified. The last part of the script runs the layout exporter to create the actual volume image using the Filename parameter to create the actual filename to be stored on disk.


outputPath = “/mnt/share/mainpc/maps/Outputs/Volume 2 – NIMT/NIMT/”

layoutTitle = “Landscape 16:9 Header Nav Copyright Filename 2021”
diagramLayersNodeName = ‘Diagram Layers’
baseAerialLayersNodeName = ‘Base Aerial Layers’
currentAerialLayersNodeName = ‘Current Aerial Layers’
baseAerialStylesNodeName = ‘Base Aerial Infra Styles’
currentAerialStylesNodeName = ‘Current Aerial Infra Styles’

project = QgsProject.instance()
projectLayoutManager = project.layoutManager()
layout = projectLayoutManager.layoutByName(layoutTitle)

fileNameLabel = layout.itemById(‘Filename’)
fileNameText = fileNameLabel.text()

fileNameParts = fileNameText.split(‘-‘)
yearStr = fileNameParts[-1]
yearVal = int(yearStr)

root = project.layerTreeRoot()
root.findGroup(diagramLayersNodeName).setItemVisibilityChecked(False)
root.findGroup(baseAerialLayersNodeName).setItemVisibilityChecked(False)
root.findGroup(currentAerialLayersNodeName).setItemVisibilityChecked(False)

if yearVal == 0:
root.findGroup(diagramLayersNodeName).setItemVisibilityChecked(True)
else:
# search base and current aerials to find a matching year if non zero
# first search Base Aerial Layers
baseAerialNode = root.findGroup(baseAerialLayersNodeName)
for baseAerialChild in baseAerialNode.children():
baseAerialChildName = baseAerialChild.name()
baseAerialChildNameParts = baseAerialChildName.split(‘-‘)
if len(baseAerialChildNameParts) == 2:
baseAerialChildLowerVal = int(baseAerialChildNameParts[0])
baseAerialChildUpperVal = int(baseAerialChildNameParts[1])
if yearVal <= baseAerialChildUpperVal and yearVal >= baseAerialChildLowerVal:
baseAerialChild.setItemVisibilityChecked(True)
baseAerialNode.setItemVisibilityChecked(True)
else:
baseAerialChild.setItemVisibilityChecked(False)
#also search Current Aerial Layers
currentAerialNode = root.findGroup(currentAerialLayersNodeName)
for currentAerialChild in currentAerialNode.children():
currentAerialChildName = currentAerialChild.name()
if currentAerialChildName == currentAerialStylesNodeName:
continue # skip over current aerial infra styles node
# firstly split by space
currentAerialChildNamePartsBySpace = currentAerialChildName.split(‘ ‘)
currentAerialChildNameFirst = currentAerialChildNamePartsBySpace[0]
# split first part into year range if applicable
currentAerialChildNameFirstParts = currentAerialChildNameFirst.split(‘-‘)
if len(currentAerialChildNameFirstParts) == 1: # only one year specified
if yearVal == int(currentAerialChildNameFirstParts[0]):
currentAerialChild.setItemVisibilityChecked(True)
currentAerialNode.setItemVisibilityChecked(True)
else:
currentAerialChild.setItemVisibilityChecked(False)
if len(currentAerialChildNameFirstParts) == 2: # year range
currentAerialChildLowerVal = int(currentAerialChildNameFirstParts[0])
currentAerialChildUpperVal = int(currentAerialChildNameFirstParts[1])
if yearVal <= currentAerialChildUpperVal and yearVal >= currentAerialChildLowerVal:
currentAerialChild.setItemVisibilityChecked(True)
currentAerialNode.setItemVisibilityChecked(True)
else:
currentAerialChild.setItemVisibilityChecked(False)

imagePath = outputPath + fileNameText + ‘.jpg’
exporter = QgsLayoutExporter(layout)
exportSettings = exporter.ImageExportSettings()
exporter.exportToImage(imagePath,exportSettings)
print(‘Exported layout to ‘ + imagePath)