1

I am trying to export a GeoTIFF using a script in the QGIS Python Console, but the exported GeoTIFF is missing most layers. The image being exported is part of a larger canvas, too big to export as a single file so I am slicing it into multiple images.

Notes:

  1. I am using a rectangular layer (layer_name) to set the bounds for the exported image
  2. I am printing out the visible layer names, to make sure they are present
  3. In every other respect the script works (image bounds, size, scale & DPI)
  4. I am using QGIS 3.26

Here is my code:

from qgis.core import (
    QgsProject,
    QgsMapSettings,
    QgsCoordinateTransform,
    QgsLayerTreeLayer,
)

from PyQt5.QtCore import QSize
from PyQt5.QtGui import QImage, QPainter
import os

def get_fully_visible_layers(node, visible_layers):
    # Recursively collect visible layers (where layer and parent group is visible)
    if isinstance(node, QgsLayerTreeLayer):
        if node.isVisible():
            # Check all parents for visibility
            parent = node.parent()
            while parent:
                if not parent.isVisible():
                    return
                parent = parent.parent()
            visible_layers.append(node.layer())
    else:
        for child in node.children():
            get_fully_visible_layers(child, visible_layers)

# -------- Config --------
layer_name  = r"layer_name"  # Name of the bounding rectangle layer
output_path = r"C:\Users\USERNAME\Desktop\Map\exported_map.tif"  # Change path accordingly
dpi         = 96

# -------- Get bounding layer --------
layer_list = QgsProject.instance().mapLayersByName(layer_name)

if not layer_list:
    raise Exception(f"Layer '{layer_name}' not found.")

bbox_layer = layer_list[0]

# -------- Get the Extent --------
layer_extent = bbox_layer.extent()

# -------- CRS Transform (if required) --------
canvas      = iface.mapCanvas()
project_crs = QgsProject.instance().crs()
layer_crs   = bbox_layer.crs()

if layer_crs != project_crs:
    xform  = QgsCoordinateTransform(layer_crs, project_crs, QgsProject.instance())
    extent = xform.transform(layer_extent)
else:
    extent = layer_extent

# -------- Calculate image size based on current canvas scale --------
# Calculation of map_units_per_pixel for CRS is in meters (e.g. EPSG:3857)
scale = round(canvas.scale())
map_units_per_pixel = (scale * 0.0254) / dpi

# Compute image dimensions
image_width  = int(extent.width() / map_units_per_pixel)
image_height = int(extent.height() / map_units_per_pixel)

# -------- Map settings --------
map_settings = QgsMapSettings()
   
# Collect all visible layers, even inside groups
layer_tree_root = QgsProject.instance().layerTreeRoot()
visible_layers  = []
get_fully_visible_layers(layer_tree_root, visible_layers)

# Match order of visible_layers to layerTree.layerOrder()
draw_order     = layer_tree_root.layerOrder()
visible_layers = [layer for layer in draw_order if layer in visible_layers]

# Print visible layers list
print("Rendering layers:")
for lyr in visible_layers:
    print(f"- {lyr.name()}")
    
# Apply map settings
map_settings.setLayers(visible_layers)
map_settings.setExtent(extent)
map_settings.setOutputSize(QSize(image_width, image_height))
map_settings.setOutputDpi(dpi)

# -------- Render and save image --------
image = QImage(image_width, image_height, QImage.Format_ARGB32_Premultiplied)
image.fill(0)

painter = QPainter(image)
render  = QgsMapRendererParallelJob(map_settings)
render.start()
render.waitForFinished()
render.renderedImage().save(output_path, "tif")
painter.end()

print(f"------------------------------")
print(f"Extent Layer  - {layer_name}")
print(f"Map Scale     - 1:{scale}")
print(f"Image Size    - {image_width} x {image_height}")
print(f"DPI           - {dpi}")
print(f"------------------------------")
print(f"GeoTIFF saved - {output_path}")
print(f"------------------------------")

What I would like to achieve, but automatically via a script, is exactly what is generated when you use the menu Project -> Import/Export -> Export Map to Image, and on that dialog by selecting Calculate from: [select a layer] to calculate the extent.

Is there anything in the script that may be preventing all layers from rendering? I note that doing it via the menu takes a little longer so clearly not all layers are rendering with the script. Does anyone have any success in exporting a GeoTIFF image with all layers showing?

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.