1

I want to generate multiple maps with one script. I define a dictionary map_configurations with names for each map and the wanted layers.

I try to loop through the function create_map but my selected layers do not make it in the map.

What am I missing?

The legend shows all of the layers in the dictionary. But in the maps themself on the first layer is displayed.

The complete code is below.

#import aller erforderlichen Klassen für die Karte   
from qgis.core import (
    QgsProject,
    QgsLayoutManager,
    QgsPrintLayout,
    QgsLayoutItemMap,
    QgsLayoutItemLegend,
    QgsLayoutSize,
    QgsLayoutExporter,
    QgsRectangle,
    QgsLayoutPoint,
    QgsUnitTypes,
    QgsLayoutMeasurement,
    QgsLayoutItemPicture,
    QgsLayoutItemScaleBar,
    QgsLayerTreeLayer,
    QgsUnitTypes, 
    QgsLayoutPoint,
    QgsLayoutItem
)

from qgis.utils import iface
from qgis.PyQt.QtGui import (
QColor,
QFont
)
from qgis.PyQt.QtWidgets import QFileDialog
import os #imports OS GUI functions

# === CONFIGURATION: Define all maps to be created; creating dictonaries with name and included maps
map_configurations = [ #always included © OpenStreetMap contributors
    {
        "name": "karte1",
        "layers": ["BISKO-Sektor", "OSM Standard"]  # Replace with actual layer names
    },
     {
         "name": "karte2", 
         "layers": ["Gepuffert", "OSM Standard"]  # Replace with actual layer names
     },
     {
         "name": "karte3",
         "layers": ["gross", "OSM Standard"]  # Replace with actual layer names
     },
     # {
     #     "name": "Karte Baublock Waermedichte",
     #     "layers": ["Gemeindegrenze", "Wärmeverbrauchsdichte in MWh/ha a", "© OpenStreetMap contributor"]
     # },
     # {
    #     "name": "MapN",
    #     "layers": ["LayerX", "LayerY", "OSM Standard"]
    # }
]

# === Prompt for export folder ===
export_folder = QFileDialog.getExistingDirectory(None, "Select folder to save PNG maps")
if not export_folder:
    print("Export cancelled by user.")
    exit()

# === Get current project ===
project = QgsProject.instance()
all_layers = project.mapLayers().values() #loads all layers from propject

print("Verfügbare Layer im Projekt:") #gets available layers in the project
for l in all_layers:
    print("-", l.name())
    
# # === Frage, ob das Skript fortgesetzt werden soll, Prüfung ob alle Layer da sind ===
# response = QMessageBox.question(
#     None,
#     "Skript fortsetzen?",
#     "Möchten Sie das Skript fortsetzen?",
#     QMessageBox.Yes | QMessageBox.No,
#     QMessageBox.No
# )

# if response != QMessageBox.Yes:
#     print("Skript wurde vom Benutzer gestoppt.")
#     # Skript einfach beenden, ohne QGIS zu schließen
#     raise Exception("Benutzerabbruch.")
    
# === Store current extent (same for all maps) ===
canvas = iface.mapCanvas()
current_extent = canvas.extent()
center_x = current_extent.center().x()
center_y = current_extent.center().y()

gui_aspect_ratio = current_extent.width() / current_extent.height()
a4_aspect_ratio = 297 / 210         #sets for A4 format

if gui_aspect_ratio > a4_aspect_ratio:
    new_width = current_extent.width()
    new_height = new_width / a4_aspect_ratio
else:
    new_height = current_extent.height()
    new_width = new_height * a4_aspect_ratio

adjusted_extent = QgsRectangle(
    center_x - new_width / 2,
    center_y - new_height / 2,
    center_x + new_width / 2,
    center_y + new_height / 2
)

# === Function to create individual map ===
def create_map(map_config):
    global export_folder
    map_name = map_config["name"]
    target_layer_names = map_config["layers"]
    
    print(f"Creating {map_name}...")
    print(f"Export folder in function: {export_folder}")
    
    print("Funktion create_map erfolgreich definiert")
    
    # Filter layers for this map
    selected_layers = [layer for layer in all_layers if layer.name() in target_layer_names]
    if not selected_layers:
        print(f"Warning: No matching layers found for {map_name}")
        return False


    # Create or replace the layout
    layout_manager = project.layoutManager()
    layout_name = f"{map_name}"
    for l in layout_manager.printLayouts():
        if l.name() == layout_name:
            layout_manager.removeLayout(l)
    
    layout = QgsPrintLayout(project)
    layout.initializeDefaults()
    layout.setName(layout_name)
    layout_manager.addLayout(layout)
    
    # Configure page and map
    page = layout.pageCollection().page(0)
    page.setPageSize(QgsLayoutSize(297, 210, QgsUnitTypes.LayoutMillimeters))  # A4 landscape
    
    map_item = QgsLayoutItemMap(layout)
    map_item.setLayers(selected_layers) # only layers 
    map_item.attemptMove(QgsLayoutPoint(0, 0, QgsUnitTypes.LayoutMillimeters))
    map_item.attemptResize(QgsLayoutSize(297, 210, QgsUnitTypes.LayoutMillimeters))
    map_item.setExtent(adjusted_extent)
    layout.addLayoutItem(map_item)
    
        # === LEGEND ===
    legend = QgsLayoutItemLegend(layout)
    legend.setLinkedMap(map_item)
    legend.setResizeToContents(True)
    legend.setBackgroundEnabled(True)
    legend.setBackgroundColor(QColor(255, 255, 255, 255))
    legend.setFrameEnabled(True)
    legend.setFrameStrokeWidth(QgsLayoutMeasurement(0.4, QgsUnitTypes.LayoutMillimeters))
    legend.setBoxSpace(4.0)

    # # Add only wanted layer in the legend
    # for layer in selected_layers:
    #     legend.model().rootGroup().addLayer(layer)

    # legend.setResizeToContents(True)
    # legend.refresh()

    # Positionieren: unten rechts
    legend.setReferencePoint(QgsLayoutItem.LowerRight)
    legend.attemptMove(QgsLayoutPoint(297, 210, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(legend)

    #  Add 4mm margin around legend content
    legend.setBoxSpace(4.0)
    
    # make sure legend has its final size
    legend.setResizeToContents(True)
    legend.refresh()

    #switch the legend's reference point to its own lower right corner
    legend.setReferencePoint(QgsLayoutItem.LowerRight)

    # after you set up and add the legend, then…
    page = layout.pageCollection().page(0)
    page_size = page.pageSize()  # QgsLayoutSize(297, 210, LayoutMillimeters)

    # bottom‑right of map (same as bottom‑right of page)
    map_br_x = page_size.width()   # 297 mm
    map_br_y = page_size.height()  # 210 mm

    # make legend size final…
    legend.setResizeToContents(True)
    legend.refresh()

    # anchor legend's LowerRight to that point
    legend.setReferencePoint(QgsLayoutItem.LowerRight)
    legend.attemptMove(QgsLayoutPoint(map_br_x, map_br_y, QgsUnitTypes.LayoutMillimeters))

    
    layout.addLayoutItem(legend) #adds the legend in the bottom right corner
    
    # Add north arrow
    north_arrow_path = "C:/Users/Benjamin/AppData/Roaming/QGIS/QGIS3/profiles/default/resource_sharing/collections/north_arrow (Kapildev's Repository)/svg/north_06.svg"
    north_arrow = QgsLayoutItemPicture(layout)
    north_arrow.setPicturePath(north_arrow_path)
    north_arrow.attemptMove(QgsLayoutPoint(297 - 11, 2, QgsUnitTypes.LayoutMillimeters))
    north_arrow.attemptResize(QgsLayoutSize(18, 18, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(north_arrow)
    
    # Add scale bar
    scale_bar = QgsLayoutItemScaleBar(layout)
    scale_bar.setStyle('Single Box')
    scale_bar.setUnits(QgsUnitTypes.DistanceKilometers)
    scale_bar.setUnitLabel("km")
    scale_bar.setFont(QFont("Arial", 9))

    # QGIS versions earlier than 3.40, löschen wenn 3.40 oder älter
    scale_bar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeFitWidth)

    # QGIS version 3.40 or later, hier mit 3.36 geschrieben
    # scale_bar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeMode.FitWidth)

    scale_bar.setNumberOfSegments(3)
    scale_bar.setNumberOfSegmentsLeft(0) 
    scale_bar.setMinimumBarWidth(30)
    scale_bar.setMaximumBarWidth(50)
       
    scale_bar.attemptMove(QgsLayoutPoint(5, 195, QgsUnitTypes.LayoutMillimeters))

    layout.addLayoutItem(scale_bar)
    scale_bar.setLinkedMap(map_item)
    scale_bar.refresh()
    
    # Refresh layout items
    layout.refresh()
    map_item.refresh()
    legend.refresh()
    
    # Export PNG
    file_path = os.path.join(export_folder, f"{map_name}.png")
    print(f"Exporting {map_name} to: {file_path}")
    print(f"Folder exists: {os.path.exists(export_folder)}")


    if os.path.exists(file_path):
        try:
            os.remove(file_path)
            print(f"Alte Datei gelöscht: {file_path}")
        except Exception as e:
            print(f"Fehler beim Löschen der alten Datei: {e}")
       
    
    exporter = QgsLayoutExporter(layout)
    settings = QgsLayoutExporter.ImageExportSettings()
    settings.dpi = 100  # Set 100 DPI
    result = exporter.exportToImage(file_path, settings)
    
    print(f"Export result code for {map_name}: {result}")
    
    if result == QgsLayoutExporter.Success:
        print(f"{map_name} successfully exported to: {file_path}")
        return True
    else:
        print(f"Export failed for {map_name}!")
        return False
    

# === Create all maps ===
successful_exports = 0
total_maps = len(map_configurations)

for map_config in map_configurations: #iterates through collection of map configurations and attempts to create a map for each one, keeping track of how many succeed. 
    if create_map(map_config):
        successful_exports += 1

# === Summary ===
print(f"\n=== Layout Creation Summary ===")
print(f"Successfully created: {successful_exports}/{total_maps} maps")


# Open layout designer for last created layout
if map_configurations and successful_exports > 0:
    layout_manager = project.layoutManager()
    last_layout_name = map_configurations[-1]['name']  
    last_layout = layout_manager.layoutByName(last_layout_name)
    if last_layout:
        print(f"Opening layout designer for: {last_layout_name}")
        iface.openLayoutDesigner(last_layout)
    else:
        print(f"Warning: Could not find layout '{last_layout_name}' to open")

1 Answer 1

3

Found the issue. All layers were created correctly, but the order was not set properly thus the OSM layer overlaps the others.

Below the correct code (legend is still off).

#import aller erforderlichen Klassen für die Karte   
from qgis.core import (
    QgsProject,
    QgsLayoutManager,
    QgsPrintLayout,
    QgsLayoutItemMap,
    QgsLayoutItemLegend,
    QgsLayoutSize,
    QgsLayoutExporter,
    QgsRectangle,
    QgsLayoutPoint,
    QgsUnitTypes,
    QgsLayoutMeasurement,
    QgsLayoutItemPicture,
    QgsLayoutItemScaleBar,
    QgsLayerTreeLayer,
    QgsUnitTypes, 
    QgsLayoutPoint,
    QgsLayoutItem
)

from qgis.utils import iface
from qgis.PyQt.QtGui import (
QColor,
QFont
)
from qgis.PyQt.QtWidgets import QFileDialog
import os #imports OS GUI functions

# === CONFIGURATION: Define all maps to be created; creating dictonaries with name and included maps
map_configurations = [ #always included © OpenStreetMap contributors
    {
        "name": "karte1",
        "layers": ["Projektgebiet", "OSM Standard"]  # Replace with actual layer names
    },
     {
         "name": "karte2", 
         "layers": ["BISKO", "OSM Standard"]  # Replace with actual layer names
     },
     {
         "name": "karte3",
         "layers": ["20müll — BISKO-Sektor", "Projektgebiet", "OSM Standard"]  # Replace with actual layer names
     }
     # {
     #     "name": "Karte Baublock Waermedichte",
     #     "layers": ["20müll — BISKO-Sektor", "Wärmeverbrauchsdichte in MWh/ha a", "© OpenStreetMap contributor"]
     # },
     # {
    #     "name": "MapN",
    #     "layers": ["LayerX", "LayerY", "OSM Standard"]
    # }
]

# === Prompt for export folder ===
export_folder = QFileDialog.getExistingDirectory(None, "Select folder to save PNG maps")
if not export_folder:
    print("Export cancelled by user.")

# === Get current project ===
project = QgsProject.instance()
all_layers = project.mapLayers().values() #loads all layers from propject

print("-"*30)
print("Verfügbare Layer im Projekt:") #gets available layers in the project
for l in all_layers:
    print("-", l.name())
print("-"*30)

# # === Frage, ob das Skript fortgesetzt werden soll, Prüfung ob alle Layer da sind ===
# response = QMessageBox.question(
#     None,
#     "Skript fortsetzen?",
#     "Möchten Sie das Skript fortsetzen?",
#     QMessageBox.Yes | QMessageBox.No,
#     QMessageBox.No
# )

# if response != QMessageBox.Yes:
#     print("Skript wurde vom Benutzer gestoppt.")
#     # Skript einfach beenden, ohne QGIS zu schließen
#     raise Exception("Benutzerabbruch.")
    
# === Store current extent (same for all maps) ===
canvas = iface.mapCanvas()
current_extent = canvas.extent()
center_x = current_extent.center().x()
center_y = current_extent.center().y()

gui_aspect_ratio = current_extent.width() / current_extent.height()
a4_aspect_ratio = 297 / 210         #sets for A4 format

if gui_aspect_ratio > a4_aspect_ratio:
    new_width = current_extent.width()
    new_height = new_width / a4_aspect_ratio
else:
    new_height = current_extent.height()
    new_width = new_height * a4_aspect_ratio

adjusted_extent = QgsRectangle(
    center_x - new_width / 2,
    center_y - new_height / 2,
    center_x + new_width / 2,
    center_y + new_height / 2
)

# === Function to create individual map ===
def create_map(map_config):
    global export_folder
    map_name = map_config["name"]
    target_layer_names = map_config["layers"] #target_layer gets the correct list for each loop   
    
    # Filter layers for this map
    
    # Get the current GUI layer order (top to bottom)
    layer_tree_root = QgsProject.instance().layerTreeRoot()
    gui_layer_order = [node.layer() for node in layer_tree_root.findLayers() if node.isVisible()]

    # Filter and order layers: include only target layers, in GUI order
    selected_layers = [layer for layer in gui_layer_order if layer and layer.name() in target_layer_names]

    # Ensure "OSM Standard" is last (bottom-most)
    osm_layer = next((layer for layer in selected_layers if layer.name() == "OSM Standard"), None)
    if osm_layer:
        selected_layers = [layer for layer in selected_layers if layer.name() != "OSM Standard"]
        selected_layers.append(osm_layer)

    
    print("-"*60)
    print("Folgende Layer enthalten in selected_layers ", selected_layers)
    print("-"*60)
    
    #Check for missing layers, DEBUGGING 
    for layer_name in target_layer_names:
        if not any(layer.name() == layer_name for layer in all_layers):
            print(f"Error: Layer '{layer_name}' not found in project for {map_name}")
        else:
            print(f"Layer '{layer_name}' found in project")
    
    #layer are assigned correctly, type here is list
    if not selected_layers:
        print(f"Warning: No matching layers found for {map_name}")
        return False

    # Create or replace the layout
    layout_manager = project.layoutManager()
    layout_name = f"{map_name}"
    for l in layout_manager.printLayouts():
        if l.name() == layout_name:
            layout_manager.removeLayout(l)
            
    layout = QgsPrintLayout(project)
    layout.initializeDefaults() #creates default canvas
    layout.setName(layout_name)
    layout_manager.addLayout(layout)
    
    # Configure page and map
    page = layout.pageCollection().page(0)
    page.setPageSize(QgsLayoutSize(297, 210, QgsUnitTypes.LayoutMillimeters))  # A4 landscape
    
    map_item = QgsLayoutItemMap(layout)
   # map_item.setLayers(selected_layers) # only layers of dictionary, selected layers is a list
    
    map_item.attemptMove(QgsLayoutPoint(0, 0, QgsUnitTypes.LayoutMillimeters))
    map_item.attemptResize(QgsLayoutSize(297, 210, QgsUnitTypes.LayoutMillimeters))
    map_item.setExtent(adjusted_extent) #set extent
    
    map_item.setLayers(selected_layers) # only layers of dictionary, selected layers is a list
    layout.addLayoutItem(map_item)
    map_item.refresh()
    
    
        # === LEGEND ===
    legend = QgsLayoutItemLegend(layout)
    legend.setLinkedMap(map_item)
    legend.setResizeToContents(True)
    legend.setBackgroundEnabled(True)
    legend.setBackgroundColor(QColor(255, 255, 255, 255))
    legend.setFrameEnabled(True)
    legend.setFrameStrokeWidth(QgsLayoutMeasurement(0.4, QgsUnitTypes.LayoutMillimeters))
    legend.setBoxSpace(4.0)

    # === Safe way to include only selected layers ===
    legend_model = legend.model()
    root_group = legend_model.rootGroup()
    # root_group.clear()  # Correctly clear existing content

    # for layer in selected_layers:
    #     root_group.addLayer(layer)

    legend.setResizeToContents(True)
    legend.refresh()


    # Positionieren: unten rechts
    legend.setReferencePoint(QgsLayoutItem.LowerRight)
    legend.attemptMove(QgsLayoutPoint(297, 210, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(legend)

    #  Add 4mm margin around legend content
    legend.setBoxSpace(4.0)
    
    # make sure legend has its final size
    legend.setResizeToContents(True)
    legend.refresh()

    #switch the legend's reference point to its own lower right corner
    legend.setReferencePoint(QgsLayoutItem.LowerRight)

    # after you set up and add the legend, then…
    page = layout.pageCollection().page(0)
    page_size = page.pageSize()  # QgsLayoutSize(297, 210, LayoutMillimeters)

    # bottom‑right of map (same as bottom‑right of page)
    map_br_x = page_size.width()   # 297 mm
    map_br_y = page_size.height()  # 210 mm

    # make legend size final…
    legend.setResizeToContents(True)
    legend.refresh()

    # anchor legend's LowerRight to that point
    legend.setReferencePoint(QgsLayoutItem.LowerRight)
    legend.attemptMove(QgsLayoutPoint(map_br_x, map_br_y, QgsUnitTypes.LayoutMillimeters))
    
    layout.addLayoutItem(legend) #adds the legend in the bottom right corner
    
    # Add north arrow
    north_arrow_path = "C:/Users/Benjamin/AppData/Roaming/QGIS/QGIS3/profiles/default/resource_sharing/collections/north_arrow (Kapildev's Repository)/svg/north_06.svg"
    north_arrow = QgsLayoutItemPicture(layout)
    north_arrow.setPicturePath(north_arrow_path)
    north_arrow.attemptMove(QgsLayoutPoint(297 - 11, 2, QgsUnitTypes.LayoutMillimeters))
    north_arrow.attemptResize(QgsLayoutSize(18, 18, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(north_arrow)
    
    # Add scale bar
    scale_bar = QgsLayoutItemScaleBar(layout)
    scale_bar.setStyle('Single Box')
    scale_bar.setUnits(QgsUnitTypes.DistanceKilometers)
    scale_bar.setUnitLabel("km")
    scale_bar.setFont(QFont("Arial", 9))

    # QGIS versions earlier than 3.40, löschen wenn 3.40 oder älter
    scale_bar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeFitWidth)

    # QGIS version 3.40 or later, hier mit 3.36 geschrieben
    # scale_bar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeMode.FitWidth)

    scale_bar.setNumberOfSegments(3)
    scale_bar.setNumberOfSegmentsLeft(0) 
    scale_bar.setMinimumBarWidth(30)
    scale_bar.setMaximumBarWidth(50)
    scale_bar.attemptMove(QgsLayoutPoint(5, 195, QgsUnitTypes.LayoutMillimeters))

    layout.addLayoutItem(scale_bar)  #adds the Scalebar to the Layout
    scale_bar.setLinkedMap(map_item)
    scale_bar.refresh()
    
    # Refresh layout items
    layout.refresh()
    map_item.refresh()
    legend.refresh()
    
    # Export PNG for each map with respective layers
    file_path = os.path.join(export_folder, f"{map_name}.png")
    
    if os.path.exists(file_path):
        try:
            os.remove(file_path)
            print(f"Alte Datei gelöscht: {file_path}")
        except Exception as e:
            print(f"Fehler beim Löschen der alten Datei: {e}")
       
    exporter = QgsLayoutExporter(layout)
    settings = QgsLayoutExporter.ImageExportSettings()
    settings.dpi = 100  # Set 100 DPI
    result = exporter.exportToImage(file_path, settings)
    
    print(f"Export result code for {map_name}: {result}")
    
    if result == QgsLayoutExporter.Success:
        print(f"{map_name} successfully exported to: {file_path}")
        return True
    else:
        print(f"Export failed for {map_name}!")
        return False


# === Create all maps ===
successful_exports = 0
total_maps = len(map_configurations)

for map_config in map_configurations: #iterates through collection of map configurations and attempts to create a map for each one, keeping track of how many succeed. 
    if create_map(map_config):
        successful_exports += 1
#correct entries of the dictionary are 

# === Summary ===
print(f"\n=== Layout Creation Summary ===")
print(f"Successfully created: {successful_exports}/{total_maps} maps")


# Open layout designer for last created layout
if map_configurations and successful_exports > 0:
    layout_manager = project.layoutManager()
    last_layout_name = map_configurations[-1]['name']  
    last_layout = layout_manager.layoutByName(last_layout_name)
    if last_layout:
        print(f"Opening layout designer for: {last_layout_name}")
        iface.openLayoutDesigner(last_layout)
    else:
        print(f"Warning: Could not find layout '{last_layout_name}' to open") ´´´

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.