major-ai-skills
Version:
Installable agentic skills / AI agent skills (SKILL.md) for Claude Code, Cursor, Codex CLI, Gemini CLI & Antigravity - 402+ professional app, token-efficiency, and common-sense skills. SEO/GEO ready.
188 lines (148 loc) • 9.15 kB
Markdown
---
title: "QGIS Open-Source Desktop GIS AI Skill Guide (GPT & Codex)"
description: "Comprehensive operational skill specification for OpenAI GPT and Codex to automate, script, troubleshoot, and optimize QGIS Python plugins, custom QgsProcessingAlgorithm tools, GDAL/OGR bindings, and PyQGIS headless pipelines."
category: "Open Source Desktop GIS & Spatial Analysis"
tags: ["qgis", "pyqgis", "qgsprocessingalgorithm", "qgis-plugin", "gpt-codex", "gdal-scripting"]
---
# QGIS Open-Source Desktop GIS AI Skill Guide (GPT & Codex)
## Overview & Engine Architecture
QGIS offers an extensible Python 3 environment enabling developers to build custom **QGIS Plugins**, standalone **`QgsProcessingAlgorithm`** tools for the Processing Toolbox, and automated **GDAL/OGR** ETL routines. GPT/Codex acts as a Principal Geospatial Software Engineer and PyQGIS Tool Architect, delivering **custom Processing algorithms**, **GUI/dockable plugin interfaces (PyQt5/PyQt6)**, **vector feature iteration loops**, and **automated headless CI/CD testing suites**.
### Developer Architecture & PyQGIS Plugin Stack
```
┌─────────────────────────────────────────────────────────────┐
│ PyQGIS Developer Platform │
│ │
│ Plugin & Processing Framework │
│ ├── `QgsProcessingAlgorithm` (Custom Alg Engine Subclass) │
│ ├── `QgsProcessingParameterFeatureSource` (Input Sink) │
│ └── PyQt5 / PyQt6 Custom DockWidgets & Action Toolbars │
│ │
│ Spatial Core & Memory Vector Operations │
│ ├── `QgsFeature` / `QgsGeometry` Vector Intersection Engine│
│ ├── Direct GDAL/OGR Python C-Bindings (`osgeo.gdal`) │
│ └── Memory Layer In-Memory Processing (`memory:layer_name`)│
└─────────────────────────────────────────────────────────────┘
```
---
## Operational Capabilities & Agent Directives
1. **Custom `QgsProcessingAlgorithm` Development**: Author production-grade Python processing algorithms implementing `name()`, `displayName()`, `createInstance()`, `initAlgorithm()`, and `processAlgorithm()`.
2. **PyQt GUI Plugin Development**: Construct responsive GUI plugins utilizing `QAction`, `QgsDockWidget`, and `QgsMapTool` coordinate capture tools.
3. **High-Performance Geometry Processing**: Optimize feature reading and spatial filtering using `layer.getFeatures(QgsFeatureRequest().setFilterRect(...))` to minimize I/O overhead.
4. **Automated Unit Testing (`pytest-qgis`)**: Build headless pytest testing suites validating spatial geoprocessing algorithms inside CI/CD container environments.
---
## Production Python Code: Custom `QgsProcessingAlgorithm` for Spatial Attribute Normalization
Save this script as `NormalizeAttributesAlgorithm.py` inside your QGIS Python Processing scripts folder (`profiles/default/processing/scripts/`):
```python
# ==============================================================================
# QGIS Custom Processing Algorithm: Spatial Field Normalizer
# Normalizes numeric attribute fields to [0.0 - 1.0] range and exports layer.
# ==============================================================================
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (
QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSink,
QgsFeatureSink,
QgsField,
QgsFeature
)
from qgis.PyQt.QtCore import QVariant
class NormalizeAttributesAlgorithm(QgsProcessingAlgorithm):
INPUT = "INPUT"
FIELD = "FIELD"
OUTPUT = "OUTPUT"
def tr(self, string):
return QCoreApplication.translate("Processing", string)
def createInstance(self):
return NormalizeAttributesAlgorithm()
def name(self):
return "normalize_attribute_field"
def displayName(self):
return self.tr("Normalize Numeric Attribute (0.0 to 1.0)")
def group(self):
return self.tr("Custom Spatial Analytics")
def groupId(self):
return "custom_spatial"
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr("Input Vector Layer"),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
self.addParameter(
QgsProcessingParameterField(
self.FIELD,
self.tr("Numeric Field to Normalize"),
parentLayerParameterName=self.INPUT,
type=QgsProcessingParameterField.Numeric
)
)
self.addParameter(
QgsProcessingParameterFeatureSink(
self.OUTPUT,
self.tr("Normalized Output Layer")
)
)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
field_name = self.parameterAsString(parameters, self.FIELD, context)
# 1. Calculate Field Min and Max
values = [f[field_name] for f in source.getFeatures() if f[field_name] is not None]
if not values:
raise Exception("Selected field contains no valid numeric values.")
min_val = float(min(values))
max_val = float(max(values))
val_range = max_val - min_val if max_val != min_val else 1.0
# 2. Configure Output Feature Sink with New Normalized Field
fields = source.fields()
norm_field_name = f"{field_name}_norm"
fields.append(QgsField(norm_field_name, QVariant.Double))
(sink, dest_id) = self.parameterAsSink(
parameters,
self.OUTPUT,
context,
fields,
source.wkbType(),
source.sourceCrs()
)
# 3. Process Features and Normalize
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, feature in enumerate(source.getFeatures()):
if feedback.isCanceled():
break
val = feature[field_name]
norm_val = (float(val) - min_val) / val_range if val is not None else 0.0
new_feature = QgsFeature(fields)
new_feature.setGeometry(feature.geometry())
new_attrs = feature.attributes() + [norm_val]
new_feature.setAttributes(new_attrs)
sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
return {self.OUTPUT: dest_id}
```
---
## Technical Troubleshooting Matrix
| Issue & Failure Signature | Root Cause Analysis | Diagnostic & Resolution Pathway |
| :--- | :--- | :--- |
| **`AttributeError: 'NoneType' object has no attribute 'geometry'`** | Feature without geometry encountered in a layer marked with vector geometry type. | Guard feature processing with `if feature.hasGeometry():` or `if feature.geometry().isGeosValid():`. |
| **Plugin UI Freezes on Long Processing Loops** | Processing algorithm running on the main Qt GUI thread rather than QgsTask background worker. | Subclass `QgsTask` or execute heavy loops inside the standard `QgsProcessingAlgorithm` framework. |
| **`sink.addFeature` Fails / Output Layer Empty** | Destination CRS or WKB geometry type passed to `parameterAsSink` does not match the input features. | Ensure `source.wkbType()` and `source.sourceCrs()` are passed correctly during sink creation. |
| **PyQGIS Script Fails: `NameError: name 'processing' is not defined`** | Missing import of the processing module in standalone headless execution. | Add `import processing` and `from processing.core.Processing import Processing; Processing.initialize()`. |
---
## Command Line Syntax & Batch Processing
```bash
# Execute Custom QGIS Algorithm via qgis_process
qgis_process run custom_spatial:normalize_attribute_field -- INPUT="C:\Data\parcels.shp" FIELD="TaxValue" OUTPUT="C:\Data\parcels_norm.gpkg"
# Launch QGIS Python Console directly with script
qgis --code "C:\Scripts\startup_gis.py"
```
### Essential File Locations
- **Custom Processing Scripts**: `%APPDATA%\QGIS\QGIS3\profiles\default\processing\scripts\`
- **Installed User Plugins**: `%APPDATA%\QGIS\QGIS3\profiles\default\python\plugins\`
---
## Agent Operational Directive
> **MANDATORY**: In custom `QgsProcessingAlgorithm` implementations, always check `if feedback.isCanceled(): break` within feature iteration loops to allow users to cancel long-running geoprocessing jobs safely.