| name | qgis-agents-map-generator |
| description | Use when generating complete maps from data: loading, styling, composing layouts, and exporting. Prevents poor cartographic choices and empty map exports. Covers the full map pipeline: data loading, symbology selection, labeling, layout composition, atlas generation, and export. Keywords: map generation, create map, export map, symbology, labeling, layout, atlas, cartography, map pipeline, PDF export, make a map, print map, style layer, beautiful map.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires QGIS 3.44+ / PyQGIS 3.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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()
vlayer = QgsVectorLayer("/path/to/data.gpkg|layername=buildings", "Buildings", "ogr")
assert vlayer.isValid(), f"Layer failed to load: {vlayer.error().summary()}"
project.addMapLayer(vlayer)
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
project_crs = QgsCoordinateReferenceSystem("EPSG:28992")
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
)
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'))
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()
layout.setName("Generated Map")
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)
project.layoutManager().addLayout(layout)
Step 6: Cartographic Elements
from qgis.core import (
QgsLayoutItemLabel, QgsLayoutItemLegend,
QgsLayoutItemScaleBar, QgsLayoutItemPicture
)
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 = QgsLayoutItemLegend(layout)
legend.setLinkedMap(map_item)
legend.attemptMove(QgsLayoutPoint(15, 210, QgsUnitTypes.LayoutMillimeters))
layout.addLayoutItem(legend)
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 = 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 = 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_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}"
img_settings = QgsLayoutExporter.ImageExportSettings()
img_settings.dpi = 300
result = exporter.exportToImage("/path/to/output.png", img_settings)
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 |
from qgis.core import QgsGradientColorRamp
from qgis.PyQt.QtGui import QColor
ramp = QgsGradientColorRamp(QColor('#ffffb2'), QColor('#bd0026'))
style = QgsStyle.defaultStyle()
ramp = style.colorRamp('Spectral')
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 |
page = layout.pageCollection().page(0)
page.setPageSize(QgsLayoutSize(420, 297, QgsUnitTypes.LayoutMillimeters))
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:
- Title — descriptive, top of layout
- Legend — linked to map item, shows all visible layers
- Scale bar — linked to map item, uses projected CRS
- North arrow — indicates orientation
- Source attribution — data source credits
- 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\"")
map_item.setAtlasDriven(True)
map_item.setAtlasScalingMode(QgsLayoutItemMap.AtlasScalingMode.Auto)
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
doc = QDomDocument()
layout.writeXml(doc, QgsReadWriteContext())
with open("/path/to/template.qpt", "w") as f:
f.write(doc.toString())
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:
Reference Links
Official Sources