0

I would like to automatically create an image file with a 2x2 map layout in PyQGIS with just a polygon vector file as input. The attribute table input file could look like this:

id attr1 attr2 attr3 attr4
1 10 3 -4 5
2 20 3 3 5
3 15 2 1 4
4 30 -5 -4 2

Now the fields should be mapped from top left to bottom right like in the example below. All should be symbolized with a graduated style, however with one symbol classification for attr1 and a common symbol classification for attr2 to attr4 respecting min and max of all three fields. As the input files will always have the same structure but values will change, a Python script to automate the image creation would be ideal.

example of the 2x2 map print layout

I started the code with loading the shapefile and initializing the layout. However, even with reading the QGIS documentation, I just can't figure out how to add the four map items and define the symbolization.

from qgis.core import *
from qgis.gui import *

# Provide the path to your shapefile
shapefile_path = 'input.shp'

# Create a QgsVectorLayer to load the shapefile
layer = QgsVectorLayer(shapefile_path, 'Shapefile', 'ogr')

# Check if the layer was loaded successfully
if not layer.isValid():
    print('Layer failed to load!')
    
# Define the attribute fields to be used
attribute_fields = layer.fields().names()

# Create a print layout
project = QgsProject.instance()
layout = QgsPrintLayout(project)
layout.initializeDefaults()

1 Answer 1

0

This should get you pretty close. There is quite a bit of code to get to this point, so I'm not going to explain it step by step. Hopefully you can study it and work out what is going on. This script could definitely be streamlined/ cleaned up a bit, but should do what you want. You will also need to work out which lines need to be edited (file paths, heading and label text etc.) if you want to adapt anything for different cases.

path = 'path\\to\\input.shp'

layer1 = QgsVectorLayer(path, 'Layer_1', 'ogr')
layer2 = QgsVectorLayer(path, 'Layer_2', 'ogr')
layer3 = QgsVectorLayer(path, 'Layer_3', 'ogr')
layer4 = QgsVectorLayer(path, 'Layer_4', 'ogr')

def apply_graduated_renderer1(lyr, color_ramp, attr, classes):
    default_style = QgsStyle().defaultStyle()
    color_ramp = default_style.colorRamp(color_ramp)
    renderer = QgsGraduatedSymbolRenderer()
    renderer.setClassAttribute(attr)
    renderer.setClassificationMethod(QgsClassificationEqualInterval())
    renderer.updateClasses(lyr, classes)
    renderer.updateColorRamp(color_ramp)
    lyr.setRenderer(renderer)
    lyr.triggerRepaint()


def apply_graduated_renderer2(lyr, attr):
    range_list = []
    color1 = QColor('#0571b0')
    color2 = QColor('#92c5de')
    color3 = QColor('#f7f7f7')
    color4 = QColor('#f4a582')
    color5 = QColor('#ca0020')
    symbol1 = QgsFillSymbol()
    symbol1.setColor(color1)
    range1 = QgsRendererRange(-5, -3, symbol1, '-5 - -3')
    range_list.append(range1)
    symbol2 = QgsFillSymbol()
    symbol2.setColor(color2)
    range2 = QgsRendererRange(-3, -1, symbol2, '-3 - -1')
    range_list.append(range2)
    symbol3 = QgsFillSymbol()
    symbol3.setColor(color3)
    range3 = QgsRendererRange(-1, 1, symbol3, '-1 - 1')
    range_list.append(range3)
    symbol4 = QgsFillSymbol()
    symbol4.setColor(color4)
    range4 = QgsRendererRange(1, 3, symbol4, '1 - 3')
    range_list.append(range4)
    symbol5 = QgsFillSymbol()
    symbol5.setColor(color5)
    range5 = QgsRendererRange(3, 5, symbol5, '3 - 5')
    range_list.append(range5)
    renderer = QgsGraduatedSymbolRenderer(attr, range_list)
    renderer.setMode(QgsGraduatedSymbolRenderer.Custom)
    lyr.setRenderer(renderer)
    lyr.triggerRepaint()


def create_layout():
    project = QgsProject.instance()
    layout = QgsPrintLayout(project)
    layout.initializeDefaults()
    layout.setName('Test Layout')
    page = layout.pageCollection().pages()[0]
    page.setPageSize('A4', orientation=QgsLayoutItemPage.Portrait)
    # Add layout title
    label1 = QgsLayoutItemLabel(layout)
    label1.setText('Layout Heading')
    label1.setFont(QFont('Arial', 22))
    label1.setHAlign(Qt.AlignCenter)
    label1.setVAlign(Qt.AlignCenter)
    label1.adjustSizeToText()
    label1.attemptMove(QgsLayoutPoint(80, 5, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(label1)
    # Add map 1 title
    label2 = QgsLayoutItemLabel(layout)
    label2.setText('plot 1 heading')
    label2.setFont(QFont('Arial', 17))
    label2.setHAlign(Qt.AlignCenter)
    label2.setVAlign(Qt.AlignCenter)
    label2.adjustSizeToText()
    label2.attemptMove(QgsLayoutPoint(35, 30, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(label2)
    # Apply renderer 1 using attr1 field and add map to layout
    apply_graduated_renderer1(layer1, 'Greens', 'attr1', 4)
    map1 = QgsLayoutItemMap(layout)
    map1.attemptMove(QgsLayoutPoint(10, 40, QgsUnitTypes.LayoutMillimeters))
    map1.attemptResize(QgsLayoutSize(90, 90, QgsUnitTypes.LayoutMillimeters))
    map1.setExtent(layer1.extent().scaled(1.1))
    map1.setLayers([layer1])
    map1.setFrameEnabled(True)
    layout.addLayoutItem(map1)
    # Add map 2 title
    label3 = QgsLayoutItemLabel(layout)
    label3.setText('plot 2 heading')
    label3.setFont(QFont('Arial', 17))
    label3.setHAlign(Qt.AlignCenter)
    label3.setVAlign(Qt.AlignCenter)
    label3.adjustSizeToText()
    label3.attemptMove(QgsLayoutPoint(135, 30, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(label3)
    # Apply renderer 2 using attr2 field and add map to layout
    apply_graduated_renderer2(layer2, 'attr2')
    map2 = QgsLayoutItemMap(layout)
    map2.attemptMove(QgsLayoutPoint(110, 40, QgsUnitTypes.LayoutMillimeters))
    map2.attemptResize(QgsLayoutSize(90, 90, QgsUnitTypes.LayoutMillimeters))
    map2.setExtent(layer2.extent().scaled(1.1))
    map2.setLayers([layer2])
    map2.setFrameEnabled(True)
    layout.addLayoutItem(map2)
    # Add map 3 title
    label4 = QgsLayoutItemLabel(layout)
    label4.setText('plot 3 heading')
    label4.setFont(QFont('Arial', 17))
    label4.setHAlign(Qt.AlignCenter)
    label4.setVAlign(Qt.AlignCenter)
    label4.adjustSizeToText()
    label4.attemptMove(QgsLayoutPoint(35, 130, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(label4)
    # Apply renderer 2 using attr3 field and add map to layout
    apply_graduated_renderer2(layer3, 'attr3')
    map3 = QgsLayoutItemMap(layout)
    map3.attemptMove(QgsLayoutPoint(10, 140, QgsUnitTypes.LayoutMillimeters))
    map3.attemptResize(QgsLayoutSize(90, 90, QgsUnitTypes.LayoutMillimeters))
    map3.setExtent(layer3.extent().scaled(1.1))
    map3.setLayers([layer3])
    map3.setFrameEnabled(True)
    layout.addLayoutItem(map3)
    # Add map 4 title
    label5 = QgsLayoutItemLabel(layout)
    label5.setText('plot 4 heading')
    label5.setFont(QFont('Arial', 17))
    label5.setHAlign(Qt.AlignCenter)
    label5.setVAlign(Qt.AlignCenter)
    label5.adjustSizeToText()
    label5.attemptMove(QgsLayoutPoint(135, 130, QgsUnitTypes.LayoutMillimeters))
    layout.addLayoutItem(label5)
    # Apply renderer 2 using attr4 field and add map to layout
    apply_graduated_renderer2(layer4, 'attr4')
    map4 = QgsLayoutItemMap(layout)
    map4.attemptMove(QgsLayoutPoint(110, 140, QgsUnitTypes.LayoutMillimeters))
    map4.attemptResize(QgsLayoutSize(90, 90, QgsUnitTypes.LayoutMillimeters))
    map4.setExtent(layer4.extent().scaled(1.1))
    map4.setLayers([layer4])
    map4.setFrameEnabled(True)
    layout.addLayoutItem(map4)
    # Add legend for plot 1
    legend1 = QgsLayoutItemLegend(layout)
    legend1.setTitle('Plot 1 Legend')
    legend1.setAutoUpdateModel(False)
    root = legend1.model().rootGroup()
    root.removeAllChildren()
    ltl1 = root.addLayer(layer1)
    QgsLegendRenderer.setNodeLegendStyle(ltl1, QgsLegendStyle.Hidden)
    legend1.attemptMove(QgsLayoutPoint(35, 220, QgsUnitTypes.LayoutMillimeters))
    legend1.adjustBoxSize()
    layout.addLayoutItem(legend1)
    # Add legend for plot 2
    legend2 = QgsLayoutItemLegend(layout)
    legend2.setTitle('Plot 2-4 Legend')
    legend2.setAutoUpdateModel(False)
    root = legend2.model().rootGroup()
    root.removeAllChildren()
    ltl2 = root.addLayer(layer2)
    QgsLegendRenderer.setNodeLegendStyle(ltl2, QgsLegendStyle.Hidden)
    legend2.attemptMove(QgsLayoutPoint(135, 220, QgsUnitTypes.LayoutMillimeters))
    legend2.adjustBoxSize()
    layout.addLayoutItem(legend2)
    
    # Optionally, add the layout to the project's layout manager...
    project.layoutManager().addLayout(layout)
    # Or export the layout directly to a PDF or image...
    pdf_export_path = 'path\\to\\test_layout.pdf'
    image_export_path = 'path\\to\\test_layout.jpg'
    exporter = QgsLayoutExporter(layout)
    exporter.exportToPdf(pdf_export_path, QgsLayoutExporter.PdfExportSettings())
    exporter.exportToImage(image_export_path, QgsLayoutExporter.ImageExportSettings())
        
create_layout()

This is the resulting layout using a test layer similar to yours with the same attributes exported as an image:

enter image description here

Your Answer

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

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