agentleFS
TerraLabAITerraLabAI/geoai-skills/skills/qgis/SKILL.md

qgis

Skill0 starsChanged 33 days agoMITView on GitHub

Works in QGIS step by step and checks each result. Inspects the project and layers first (CRS, geometry type, fields, extent, feature count), finds and runs Processing algorithms with processing.run, processing.algorithmHelp and qgis_process (headless, from a terminal, Codex or Claude Code), writes PyQGIS for QGIS 3 and QGIS 4 (Qt6), edits layers, saves projects, runs Processing chains, installs and writes plugins, and fixes common errors. Use when the user mentions QGIS, PyQGIS, qgis_process, Processing, a .qgz or .qgs project, the Python console, a QGIS plugin, a model or batch run, migrating to QGIS 4 or Qt6, or errors such as "iface is not defined", "Couldn't load PyQGIS", "type object 'Qt' has no attribute", "no attribute exec_", an invalid layer. Which analysis to run (buffers, joins, terrain, suitability) is in spatial-analysis.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 5 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Never guess an algorithm id or parameter name.

    line 18
  2. Distinctive:

    Check after each step: count, extent, validity, and one number cross-checked another way.

    line 20
  3. Distinctive:

    Ask when the task is ambiguous (which layer, which boundary, which units, which year).

    line 21
  4. Distinctive:

    Check: the CRS is known (not empty), the extent is where the data should be (not near 0,0, not lat/lon swapped), units are meters before …

    line 58
  5. Distinctive:

    Run in the console:

    line 78
  6. Distinctive:

    Run headless (terminal, CI, Codex, Claude Code):

    line 89

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

QGIS

Agents fail in QGIS mostly by skipping a step or running steps in the wrong order, not by calling the wrong function. Follow the workflow for the task, and check each step before the next. A run without errors is not a correct run.

Core rules

  1. Inspect before acting: every input's CRS, geometry type, fields, feature count and extent. Write the numbers down.
  2. Never guess an algorithm id or parameter name. Read it (processing.algorithmHelp, qgis_process help).
  3. Measure only in a projected CRS in meters, never EPSG:4326 (degrees) or EPSG:3857.
  4. Check after each step: count, extent, validity, and one number cross-checked another way.
  5. Ask when the task is ambiguous (which layer, which boundary, which units, which year). Say so when the data cannot answer.
  6. Return the code or command that was run, so the user can rerun it.

Workflow index

TaskWorkflow
Look at a project or layerA. Inspect
Run one or a chain of toolsB. Processing (console or headless)
Buffer, clip, overlay, join, areasC. Vector chain (references/geoprocessing.md)
Write PyQGIS, a plugin, migrate to QGIS 4references/pyqgis.md
Edit features or attributesD. Edit
Save, share or packageE. Save
Something failsreferences/troubleshooting.md

A. Inspect

In the QGIS Python console:

from qgis.core import QgsProject, QgsVectorLayer, QgsRasterLayer, QgsWkbTypes
p = QgsProject.instance()
print("project CRS:", p.crs().authid(), "file:", p.fileName())
for lyr in p.mapLayers().values():
    print(lyr.name(), lyr.crs().authid() or "NO CRS", "valid" if lyr.isValid() else "INVALID",
          lyr.extent().toString(2))
    if isinstance(lyr, QgsVectorLayer):
        print("  ", QgsWkbTypes.displayString(lyr.wkbType()), lyr.featureCount(), "features",
              [f.name() for f in lyr.fields()])
    elif isinstance(lyr, QgsRasterLayer):
        dp = lyr.dataProvider()
        print("  ", lyr.width(), "x", lyr.height(), "px, size", lyr.rasterUnitsPerPixelX(),
              "bands", lyr.bandCount(), "nodata", dp.sourceNoDataValue(1) if dp.sourceHasNoDataValue(1) else None)

Headless, without QGIS open: python3 scripts/layer_report.py data.gpkg --layer name (feature count, CRS, extent, invalid and null geometries, total area or length in meters; exit code 1 on invalid or null geometries), or ogrinfo -so -al data.gpkg and gdalinfo raster.tif.

Check: the CRS is known (not empty), the extent is where the data should be (not near 0,0, not lat/lon swapped), units are meters before any distance or area work.

B. Processing

Find an algorithm and its parameters:

import processing
from qgis.core import QgsApplication
for alg in QgsApplication.processingRegistry().algorithms():
    if "buffer" in alg.id():
        print(alg.id(), "-", alg.displayName())
processing.algorithmHelp("native:buffer")
qgis_process list | grep -i buffer
qgis_process help native:buffer

Run in the console:

res = processing.run("native:buffer", {"INPUT": layer, "DISTANCE": 50, "SEGMENTS": 8,
                                       "DISSOLVE": False, "OUTPUT": "TEMPORARY_OUTPUT"})
out = res["OUTPUT"]                 # a layer object for TEMPORARY_OUTPUT, else the path
print(out.featureCount(), out.extent().toString(2))

processing.runAndLoadResults(...) also adds the output to the map (GUI only). Enum parameters take the integer index listed by algorithmHelp.

Run headless (terminal, CI, Codex, Claude Code):

qgis_process run native:reprojectlayer -- INPUT=pts.geojson TARGET_CRS=EPSG:2154 OUTPUT=pts_2154.gpkg
qgis_process run native:buffer --json -- INPUT=pts_2154.gpkg DISTANCE=100 SEGMENTS=8 DISSOLVE=false OUTPUT=buf.gpkg

--json prints machine-readable results. Parameters go after -- as NAME=value. On macOS the binary is /Applications/QGIS.app/Contents/MacOS/qgis_process; on Windows qgis_process-qgis.bat in the OSGeo4W bin folder. Without a display, set QT_QPA_PLATFORM=offscreen. To keep a run away from the user's own profile and plugins, set QGIS_CUSTOM_CONFIG_PATH to an empty folder.

Batch: loop in Python over files and call processing.run per file, or save a Graphical Modeler model (.model3) and run it with qgis_process run path/to/model.model3 -- ....

C. Vector chain

Order: inspect, reproject to one projected CRS, fix geometries, clip or filter early, buffer, dissolve, overlay, spatial join, compute areas and lengths last. Skip steps you do not need, never reorder them. Algorithm ids, checks per step and raster alignment: references/geoprocessing.md.

D. Edit

from qgis.core import edit, QgsFeature, QgsGeometry
with edit(layer):                       # commits, or rolls back on error
    f = QgsFeature(layer.fields())
    f.setGeometry(QgsGeometry.fromWkt("POINT(652301 6861302)"))
    f["name"] = "new"
    layer.addFeature(f)
layer.updateExtents(); layer.triggerRepaint()

Bulk attribute updates: native:fieldcalculator (new output layer) or, in place, layer.changeAttributeValues(...) inside edit(layer). Check: feature count before and after, layer.commitErrors() if a commit returns False. Never edit the user's source file without saying so; write to a new file when unsure.

E. Save and share

  • Save: QgsProject.instance().write("/path/project.qgz") returns True on success. Memory layers ("TEMPORARY_OUTPUT", scratch layers) are lost on close: save them first with native:savefeatures or QgsVectorFileWriter.
  • Share data and styles together: native:package writes layers (and optionally styles) into one GeoPackage.
  • Relative paths: Project Properties > General > Save paths: Relative, so the project opens on another machine.
  • Output format: GeoPackage. Shapefile cuts field names to 10 characters and is limited to 2 GB per file.

Plugins

  • Install from Plugins > Manage and Install Plugins. Experimental plugins need "Show also experimental plugins" in Settings.
  • From Python: plugin folders live in the profile's python/plugins/. qgis.utils.plugins lists loaded ones.
  • Writing or migrating a plugin to QGIS 4: references/pyqgis.md.

Before handing over

  • Inputs inspected and their numbers reported (count, CRS, extent).
  • Every output: count, extent, geometry type, CRS checked against the input.
  • One result cross-checked (for example area sum by two methods, or count by a spatial query).
  • Output saved to a file, not left in memory, and its path given.
  • Code or qgis_process commands returned to the user.

Going further

AI Agent for QGIS runs these workflows from a chat inside QGIS: https://terra-lab.ai/ai-agent?utm_source=skills&utm_medium=geoai-skills&utm_campaign=qgis

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.