SuperAPI AI Directory

qgis-agents-map-generator

Agents & models

View repository

Updated 10-15 22:18

About this skill

qgis-agents-map-generator

Quick Reference

Map Generation Pipeline

Step Action Key Classes
1. Data Loading Load vector/raster layers, validate QgsVectorLayer, QgsRasterLayer
2. CRS Setup Set project CRS, reproject if needed QgsCoordinateReferenceSystem, QgsProject
3. Symbology Apply renderer matching data type QgsCategorizedSymbolRenderer, QgsGraduatedSymbolRenderer
4. Labeling Configure label placement and format QgsPalLayerSettings, QgsTextFormat
5. Layout Create print layout with map items QgsPrintLayout, QgsLayoutItemMap
6. Cartographic Elements Add legend, scale bar, north arrow, title QgsLayoutItemLegend, QgsLayoutItemScaleBar
7. Export Export to PDF/PNG/SVG QgsLayoutExporter

Export Format Decision

Format When to Use
PDF Print-ready output, multi-page documents, atlas exports
PNG Web display, raster-only output, fixed resolution
SVG Editable vector output, post-processing in Illustrator/Inkscape

Critical Warnings

NEVER export a layout without first setting the map extent — this produces blank or mispositioned maps. ALWAYS call map_item.zoomToExtent() or map_item.setExtent() before export.

ALWAYS call layout.initializeDefaults() after creating a new QgsPrintLayout — without this, the layout has no pages and export fails silently.

NEVER export without checking the result code from QgsLayoutExporter — result 0 means success; any other value indicates a specific failure (memory, file, print, SVG layer, or iterator error).

ALWAYS call layer.triggerRepaint() after changing symbology or labeling — without this, the map canvas and layout exports render stale styles.

NEVER use QgsRandomColorRamp for thematic maps — random colors destroy visual hierarchy and make maps unreadable. ALWAYS select a color ramp matching the data type (sequential, diverging, or qualitative).

ALWAYS link the legend, scale bar, and overview items to the correct map item via setLinkedMap() — unlinked items display incorrect or empty content.

ALWAYS verify layer.isValid() immediately after loading — invalid layers produce empty maps without error messages.


Map Generation Pipeline

This skill orchestrates the FULL pipeline from raw data to exported map. Follow these steps in order.

Step 1: Data Loading

from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsProject

project = QgsProject.instance()

# Vector layer
vlayer = QgsVectorLayer("/path/to/data.gpkg|layername=buildings", "Buildings", "ogr")
assert vlayer.isValid(), f"Layer failed to load: {vlayer.error().summary()}"
project.addMapLayer(vlayer)

# Raster layer
rlayer = QgsRasterLayer("/path/to/dem.tif", "DEM")
assert rlayer.isValid(), f"Raster failed to load: {rlayer.error().summary()}"
project.addMapLayer(rlayer)

Step 2: CRS Setup

from qgis.core import QgsCoordinateReferenceSystem

# Set project CRS: ALWAYS use a projected CRS for maps with scale bars
project_crs = QgsCoordinateReferenceSystem("EPSG:28992")  # Example: Dutch RD New
project.setCrs(project_crs)

CRS Selection Rules:

  • ALWAYS use a projected CRS (meters/feet) for maps with scale bars or area measurements
  • NEVER use EPSG:4326 (geographic) for print maps — degree-based coordinates distort scale bars
  • Use UTM zones for maps covering < 6 degrees of longitude
  • Use national grid systems (e.g., EPSG:28992, EPSG:27700) for country-specific maps

Step 3: Symbology

Select renderer based on data characteristics. See the Symbology Decision Tree below.

from qgis.core import (
    QgsMarkerSymbol, QgsLineSymbol, QgsFillSymbol,
    QgsCategorizedSymbolRenderer, QgsGraduatedSymbolRenderer,
    QgsRuleBasedRenderer, QgsRendererCategory, QgsRendererRange,
    QgsClassificationRange, QgsStyle
)

# Apply renderer to layer
layer.setRenderer(renderer)
layer.triggerRepaint()

Step 4: Labeling

from qgis.core import QgsPalLayerSettings, QgsVectorLayerSimpleLabeling, QgsTextFormat
from qgis.PyQt.QtGui import QColor

settings = QgsPalLayerSettings()
settings.fieldName = 'name'
settings.isExpression = False
settings.enabled = True

text_format = QgsTextFormat()
text_format.setSize(10)
text_format.setColor(QColor('black'))

# ALWAYS add a buffer (halo) for readability
buffer_settings = text_format.buffer()
buffer_settings.setEnabled(True)
buffer_settings.setSize(1.0)
buffer_settings.setColor(QColor('white'))

settings.setFormat(text_format)

labeling = QgsVectorLayerSimpleLabeling(settings)
layer.setLabeling(labeling)
layer.setLabelsEnabled(True)
layer.triggerRepaint()

Step 5: Layout Creation

from qgis.core import (
    QgsPrintLayout, QgsLayoutItemMap, QgsLayoutSize,
    QgsLayoutPoint, QgsUnitTypes
)

layout = QgsPrintLayout(project)
layout.initializeDefaults()  # CRITICAL: creates default A4 page
layout.setName("Generated Map")

# Add map item
map_item = QgsLayoutItemMap(layout)
map_item.attemptResize(QgsLayoutSize(260, 180, QgsUnitTypes.LayoutMillimeters))
map_item.attemptMove(QgsLayoutPoint(15, 25, QgsUnitTypes.LayoutMillimeters))
map_item.zoomToExtent(vlayer.extent())
layout.addLayoutItem(map_item)

# Register layout with project
project.layoutManager().addLayout(layout)

Step 6: Cartographic Elements

from qgis.core import (
    QgsLayoutItemLabel, QgsLayoutItemLegend,
    QgsLayoutItemScaleBar, QgsLayoutItemPicture
)

# Title
title = QgsLayoutItemLabel(layout)
title.setText("Map Title")
title.setFont(QFont("Arial", 18))
title.adjustSizeToText()
title.attemptMove(QgsLayoutPoint(15, 5, QgsUnitTypes.LayoutMillimeters))
layout.addLayoutItem(title)

# Legend: ALWAYS link to map item
legend = QgsLayoutItemLegend(layout)
legend.setLinkedMap(map_item)
legend.attemptMove(QgsLayoutPoint(15, 210, QgsUnitTypes.LayoutMillimeters))
layout.addLayoutItem(legend)

# Scale bar: ALWAYS link to map item
scalebar = QgsLayoutItemScaleBar(layout)
scalebar.setStyle("Single Box")
scalebar.setLinkedMap(map_item)
scalebar.applyDefaultSize()
scalebar.attemptMove(QgsLayoutPoint(120, 210, QgsUnitTypes.LayoutMillimeters))
layout.addLayoutItem(scalebar)

# North arrow
north_arrow = QgsLayoutItemPicture(layout)
north_arrow.setPicturePath(":/images/sketchy/sketchy-north.svg")
north_arrow.attemptResize(QgsLayoutSize(15, 15, QgsUnitTypes.LayoutMillimeters))
north_arrow.attemptMove(QgsLayoutPoint(265, 25, QgsUnitTypes.LayoutMillimeters))
layout.addLayoutItem(north_arrow)

# Source attribution
source = QgsLayoutItemLabel(layout)
source.setText("Source: [data source attribution]")
source.adjustSizeToText()
source.attemptMove(QgsLayoutPoint(15, 202, QgsUnitTypes.LayoutMillimeters))
layout.addLayoutItem(source)

Step 7: Export

from qgis.core import QgsLayoutExporter

exporter = QgsLayoutExporter(layout)

# PDF export (most common)
pdf_settings = QgsLayoutExporter.PdfExportSettings()
pdf_settings.dpi = 300
result = exporter.exportToPdf("/path/to/output.pdf", pdf_settings)
assert result == QgsLayoutExporter.ExportResult.Success, f"Export failed: {result}"

# PNG export
img_settings = QgsLayoutExporter.ImageExportSettings()
img_settings.dpi = 300
result = exporter.exportToImage("/path/to/output.png", img_settings)

# SVG export
svg_settings = QgsLayoutExporter.SvgExportSettings()
svg_settings.dpi = 300
result = exporter.exportToSvg("/path/to/output.svg", svg_settings)

Symbology Decision Tree

Follow this tree to select the correct renderer:

Is the data CATEGORICAL (text/classes)?
├── YES → Are there ≤ 20 unique values?
│   ├── YES → Use QgsCategorizedSymbolRenderer
│   │         Color ramp: QUALITATIVE (distinct hues)
│   └── NO  → Use QgsRuleBasedRenderer with grouped categories
│             Or simplify categories before rendering
└── NO → Is the data NUMERIC?
    ├── YES → Is the data SEQUENTIAL (low-to-high)?
    │   ├── YES → Use QgsGraduatedSymbolRenderer
    │   │         Color ramp: SEQUENTIAL (light-to-dark single hue)
    │   └── NO  → Is there a meaningful MIDPOINT (e.g., zero, average)?
    │       ├── YES → Use QgsGraduatedSymbolRenderer
    │       │         Color ramp: DIVERGING (two hues from midpoint)
    │       └── NO  → Use QgsGraduatedSymbolRenderer
    │                 Color ramp: SEQUENTIAL
    └── NO → Use QgsSingleSymbolRenderer (uniform style)

Color Ramp Selection

Data Type Ramp Type Example Use PyQGIS Class
Sequential numeric Sequential Population density, elevation QgsGradientColorRamp
Diverging numeric Diverging Temperature anomaly, profit/loss QgsGradientColorRamp (two-tone)
Categorical Qualitative Land use types, building classes QgsPresetSchemeColorRamp
Continuous surface Sequential DEM hillshade, slope QgsGradientColorRamp
# Sequential color ramp (light yellow to dark red)
from qgis.core import QgsGradientColorRamp
from qgis.PyQt.QtGui import QColor
ramp = QgsGradientColorRamp(QColor('#ffffb2'), QColor('#bd0026'))

# Use named style ramps from QGIS
style = QgsStyle.defaultStyle()
ramp = style.colorRamp('Spectral')  # or 'Blues', 'RdYlGn', 'Viridis', etc.

Geometry-Specific Symbol Creation

Geometry Class Example
Point QgsMarkerSymbol QgsMarkerSymbol.createSimple({'name': 'circle', 'color': 'red', 'size': '3'})
Line QgsLineSymbol QgsLineSymbol.createSimple({'color': 'blue', 'width': '0.5'})
Polygon QgsFillSymbol QgsFillSymbol.createSimple({'color': '#aaffaa', 'outline_color': 'black', 'outline_width': '0.3'})

Layout Guide

Standard Page Sizes

Size Width x Height (mm) Use Case
A4 Portrait 210 x 297 Reports, standard prints
A4 Landscape 297 x 210 Wide-area maps
A3 Portrait 297 x 420 Detailed maps
A3 Landscape 420 x 297 Panoramic maps
A0 841 x 1189 Wall maps, posters
# Set page size
page = layout.pageCollection().page(0)
page.setPageSize(QgsLayoutSize(420, 297, QgsUnitTypes.LayoutMillimeters))  # A3 Landscape

Layout Margin Guidelines

Element Recommended Margin (mm)
Page margins 10-15 on all sides
Map frame to edge 15 minimum
Title above map 5-10 gap
Legend below/beside map 5 gap from map edge
Scale bar Bottom of map area
North arrow Top-right corner of map

Cartographic Checklist

Every generated map MUST include:

  1. Title — descriptive, top of layout
  2. Legend — linked to map item, shows all visible layers
  3. Scale bar — linked to map item, uses projected CRS
  4. North arrow — indicates orientation
  5. Source attribution — data source credits
  6. Date — when the map was produced

Atlas Generation

Use atlas for batch-generating maps per feature (e.g., one map per municipality).

atlas = layout.atlas()
atlas.setCoverageLayer(coverage_layer)
atlas.setEnabled(True)
atlas.setFilenameExpression("'map_' || \"name\"")

# Optional: filter features
# atlas.setFilterFeatures(True)
# atlas.setFilterExpression('"status" = \'active\'')

# Map item MUST be controlled by atlas
map_item.setAtlasDriven(True)
map_item.setAtlasScalingMode(QgsLayoutItemMap.AtlasScalingMode.Auto)

# Export all atlas pages to individual PDFs
pdf_settings = QgsLayoutExporter.PdfExportSettings()
pdf_settings.dpi = 300
result = QgsLayoutExporter.exportToPdfs(
    atlas, "/path/to/atlas_output/", pdf_settings
)

Atlas Expression Variables:

  • @atlas_featurenumber — current feature index (1-based)
  • @atlas_totalfeatures — total number of features
  • @atlas_feature — current feature object
  • @atlas_featureid — current feature ID
  • @atlas_pagename — current page name

Export Guide

DPI Selection

Purpose DPI File Size
Screen/web preview 96 Small
Standard print 150 Medium
High-quality print 300 Large
Publication quality 600 Very large

Export Result Codes

Code Constant Meaning
0 Success Export completed
1 Canceled User canceled
2 MemoryError Insufficient memory (reduce DPI or map size)
3 FileError Cannot write to output path
4 PrintError Print rendering failed
5 SvgLayerError SVG layer export failed
6 IteratorError Atlas iteration failed

Template Save/Load for Reuse

from qgis.core import QgsReadWriteContext
from qgis.PyQt.QtXml import QDomDocument

# Save layout as .qpt template
doc = QDomDocument()
layout.writeXml(doc, QgsReadWriteContext())
with open("/path/to/template.qpt", "w") as f:
    f.write(doc.toString())

# Load layout from .qpt template
with open("/path/to/template.qpt", "r") as f:
    content = f.read()
doc = QDomDocument()
doc.setContent(content)
new_layout = QgsPrintLayout(project)
new_layout.readXml(doc.documentElement(), doc, QgsReadWriteContext())
new_layout.setName("From Template")
project.layoutManager().addLayout(new_layout)

MCP Integration

For runtime map generation via Claude Code, use MCP servers that expose QGIS functionality:

  • qgis-mcp-server — Provides tool-based access to QGIS project manipulation, layer management, and export
  • Processing MCP — Exposes QGIS Processing algorithms as MCP tools

When an MCP server is available, the map generation pipeline steps remain identical — the MCP server wraps the same PyQGIS calls described above. ALWAYS validate layer loading and export results through the MCP response.


Validation Checklist

Before delivering a generated map, verify ALL items:

  • All layers loaded successfully (isValid() == True)
  • Project CRS is a projected system (not geographic/EPSG:4326)
  • Symbology matches the data type (categorical/graduated/single)
  • Color ramp matches data semantics (sequential/diverging/qualitative)
  • Labels have buffer/halo enabled for readability
  • Layout has title, legend, scale bar, north arrow, source attribution
  • Legend is linked to the map item
  • Scale bar is linked to the map item
  • Map extent is set before export (not empty/default)
  • Export result code is Success (0)
  • Output file exists and has non-zero size
  • DPI matches intended use (300 for print, 96 for screen)

Reference Links

Official Sources