Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

Qgis Core Architecture

  • 8 installs
  • 29 repo stars
  • Updated July 8, 2026
  • openaec-foundation/qgis-claude-skill-package

Helps with ai & agent building tasks.

About

qgis-core-architecture is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.

  • qgis-core-architecture
  • AI & Agent Building
  • AI-coding skill

Qgis Core Architecture by the numbers

  • 8 all-time installs (skills.sh)
  • Ranked #12,339 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/qgis-claude-skill-package --skill qgis-core-architecture

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs8
repo stars29
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/qgis-claude-skill-package

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

qgis-core-architecture

Quick Reference

Core Python Modules (QGIS 3.x)

ModulePurpose
qgis.coreAll non-GUI functionality: layers, features, geometry, CRS, project, processing, data providers
qgis.guiGUI components: map canvas (QgsMapCanvas), layer tree view, attribute forms
qgis.analysisSpatial analysis: network analysis, raster calculator, interpolation
qgis.serverQGIS Server for OGC web services (WMS, WFS, WCS)
qgis._3d3D map visualization components

Additional utility modules:

  • qgis.utils -- Automatically imported in the QGIS Python console; provides helper functions
  • qgis.PyQt -- Re-exports PyQt5/PyQt6 classes for version-independent imports

Layer Type Hierarchy

ClassType EnumDescription
QgsVectorLayerVectorLayerPoints, lines, polygons with attributes
QgsRasterLayerRasterLayerGrid-based data (GeoTIFF, WMS, XYZ tiles)
QgsMeshLayerMeshLayerUnstructured mesh data (hydrodynamic models)
QgsPointCloudLayerPointCloudLayerLiDAR / point cloud data (LAS, LAZ, COPC, EPT)
QgsAnnotationLayerAnnotationLayerFreeform annotations on the map
QgsVectorTileLayerVectorTileLayerMapbox Vector Tiles (MVT)
QgsTiledSceneLayerTiledSceneLayer3D tiled scenes (3D Tiles, Cesium)

All layer types inherit from QgsMapLayer. Access the type via layer.type().

Project File Formats

FormatExtensionDescription
QGS.qgsXML-based project file; human-readable, larger file size
QGZ.qgzCompressed ZIP archive containing .qgs and auxiliary data; default since QGIS 3.2

Key Dependencies

ComponentRole
Qt5 / Qt6Application framework (Qt6 in QGIS 4.x)
SIPC++ to Python binding generator (tighter Qt integration than SWIG)
PyQt5 / PyQt6Python Qt bindings
GDAL/OGRRaster and vector data I/O
PROJCoordinate reference system transformations
GEOSGeometry operations

---

Critical Warnings

NEVER access QgsProject.instance() from background threads without proper synchronization. The project singleton is NOT thread-safe for writes.

NEVER modify GUI elements (map canvas, layer tree, dialogs) from background threads. ALWAYS use QgsTask and emit signals to communicate results back to the main thread.

NEVER create layers on a background thread and add them to the project directly. Create the data on the background thread, then add the layer on the main thread via signal.

NEVER assume a layer is valid without calling isValid(). A layer can be created without errors but still be invalid (wrong path, missing provider, auth failure).

NEVER access features or data provider methods on an invalid layer -- this leads to crashes or undefined behavior.

NEVER call QgsProject.instance().clear() without confirming that unsaved changes are acceptable -- this destroys all loaded layers and settings immediately.

NEVER rely on QgsProject.instance().mapLayersByName() returning a single result -- layer names are NOT unique. ALWAYS index into the returned list and handle empty lists.

NEVER use backslashes in paths even on Windows -- QGIS/Qt normalizes to forward slashes internally. Using backslashes in URIs causes provider failures.

NEVER call QgsApplication([], False) without first calling QgsApplication.setPrefixPath() -- providers will fail to load.

ALWAYS call qgs.exitQgis() at the end of standalone scripts to prevent memory leaks.

ALWAYS check layer validity immediately after creation with layer.isValid().

---

Decision Tree

Which Initialization Context?

Are you writing code inside the QGIS application?
├── YES (plugin or Python console)
│   └── QgsApplication is ALREADY initialized
│       ├── `iface` is available (QgisInterface)
│       ├── `QgsProject.instance()` is ready
│       └── Do NOT call QgsApplication() or initQgis()
├── NO (standalone script, no GUI)
│   └── MUST initialize QgsApplication manually:
│       1. QgsApplication.setPrefixPath(path, True)
│       2. qgs = QgsApplication([], False)
│       3. qgs.initQgis()
│       4. ... your code ...
│       5. qgs.exitQgis()
└── NO (standalone script, with GUI)
    └── Same as above but pass True to QgsApplication:
        1. QgsApplication.setPrefixPath(path, True)
        2. qgs = QgsApplication([], True)
        3. qgs.initQgis()
        4. ... your GUI code ...
        5. qgs.exitQgis()

Which Layer Class?

What kind of data are you loading?
├── Vector data (points, lines, polygons) → QgsVectorLayer
├── Raster/grid data (GeoTIFF, DEM) → QgsRasterLayer
├── Mesh data (hydrodynamic models) → QgsMeshLayer
├── Point cloud (LAS, LAZ, COPC) → QgsPointCloudLayer
├── Vector tiles (MVT) → QgsVectorTileLayer
├── 3D tiled scenes (Cesium 3D Tiles) → QgsTiledSceneLayer
└── Map annotations → QgsAnnotationLayer

Which Data Provider?

What is your data source?
├── Local vector file (.shp, .gpkg, .geojson, .fgb, .kml) → provider: "ogr"
├── Local raster file (.tif, .jp2, .vrt) → provider: "gdal"
├── PostgreSQL/PostGIS database → provider: "postgres"
├── SpatiaLite database → provider: "spatialite"
├── In-memory layer → provider: "memory"
├── Delimited text file (.csv with coordinates) → provider: "delimitedtext"
├── WFS web service → provider: "WFS"
├── WMS/WMTS web service → provider: "wms"
├── WCS web service → provider: "wcs"
├── GPX file → provider: "gpx"
└── Virtual layer (SQL over layers) → provider: "virtual"

---

Essential Patterns

Standalone Script Initialization (No GUI)

from qgis.core import QgsApplication, QgsVectorLayer, QgsProject

QgsApplication.setPrefixPath("/path/to/qgis/installation", True)
qgs = QgsApplication([], False)
qgs.initQgis()

# Load and work with layers
layer = QgsVectorLayer("data/airports.gpkg|layername=airports", "Airports", "ogr")
if not layer.isValid():
    raise RuntimeError("Layer failed to load")

QgsProject.instance().addMapLayer(layer)

# ALWAYS clean up
qgs.exitQgis()

Plugin Entry Point Pattern

class MyPlugin:
    def __init__(self, iface):
        self.iface = iface  # QgisInterface instance

    def initGui(self):
        # Register actions, menus, toolbars
        self.action = QAction("My Plugin", self.iface.mainWindow())
        self.action.triggered.connect(self.run)
        self.iface.addToolBarIcon(self.action)

    def unload(self):
        # ALWAYS clean up all registered elements
        self.iface.removeToolBarIcon(self.action)

    def run(self):
        # Plugin logic here
        layer = self.iface.activeLayer()
        if layer is not None and layer.isValid():
            # Process layer
            pass

def classFactory(iface):
    return MyPlugin(iface)

Project Operations

project = QgsProject.instance()

# Load project
success = project.read("/path/to/project.qgz")

# Load project skipping layer resolution (fast, for metadata access)
readflags = Qgis.ProjectReadFlags()
readflags |= Qgis.ProjectReadFlag.DontResolveLayers
project.read("/path/to/project.qgs", readflags)

# Save project
project.write()                            # Save to current location
project.write("/path/to/new_project.qgz")  # Save to new path

# Access all layers (returns dict of {id: layer})
all_layers = project.mapLayers()

# Find layers by name (returns list -- names are NOT unique)
layers = project.mapLayersByName("Airports")
if not layers:
    raise RuntimeError("Layer 'Airports' not found")
layer = layers[0]

Layer Creation and Validation

# Vector layer
vlayer = QgsVectorLayer("data/airports.shp", "Airports", "ogr")
if not vlayer.isValid():
    raise RuntimeError(f"Failed to load layer: {vlayer.name()}")
QgsProject.instance().addMapLayer(vlayer)

# Raster layer
rlayer = QgsRasterLayer("data/srtm.tif", "SRTM", "gdal")
if not rlayer.isValid():
    raise RuntimeError(f"Failed to load raster: {rlayer.name()}")
QgsProject.instance().addMapLayer(rlayer)

# Memory layer (for temporary data)
mem_layer = QgsVectorLayer("Point?crs=EPSG:4326", "Temp Points", "memory")

Background Processing with QgsTask

from qgis.core import QgsTask, QgsApplication

class HeavyProcessingTask(QgsTask):
    def __init__(self, description, layer_id):
        super().__init__(description, QgsTask.CanCancel)
        self.layer_id = layer_id
        self.result_data = None

    def run(self):
        # Runs on background thread -- NO GUI access here
        # NO QgsProject.instance() writes here
        self.result_data = self._process_data()
        return True

    def finished(self, result):
        # Runs on main thread -- safe to update GUI and project
        if result:
            # Add results to project here
            pass

task = HeavyProcessingTask("Processing data", layer.id())
QgsApplication.taskManager().addTask(task)

Signal/Slot Connections

# Connect to project layer changes
QgsProject.instance().layersAdded.connect(on_layers_added)
QgsProject.instance().layersRemoved.connect(on_layers_removed)

# Connect to map canvas render
iface.mapCanvas().renderComplete.connect(on_render_complete)

# ALWAYS disconnect when done (e.g., in plugin unload)
QgsProject.instance().layersAdded.disconnect(on_layers_added)
iface.mapCanvas().renderComplete.disconnect(on_render_complete)

---

Common Operations

Path Resolution

# Rewrite paths during loading (e.g., migrating between machines)
def my_load_preprocessor(path):
    return path.replace("c:/Users/Old/", "x:/New/")

preprocessor_id = QgsPathResolver.setPathPreprocessor(my_load_preprocessor)

# Remove preprocessor when no longer needed
QgsPathResolver.removePathPreprocessor(preprocessor_id)

Layer Tree Management

root = QgsProject.instance().layerTreeRoot()

# Create a group
group = root.addGroup("Analysis Results")

# Add layer to specific group (not root)
QgsProject.instance().addMapLayer(layer, False)  # False = don't add to root
group.addLayer(layer)

# Find group or layer node
my_group = root.findGroup("Analysis Results")
node = root.findLayer(layer.id())

Canvas-Project Bridge (Standalone GUI Apps)

from qgis.core import QgsProject
from qgis.gui import QgsLayerTreeMapCanvasBridge

bridge = QgsLayerTreeMapCanvasBridge(
    QgsProject.instance().layerTreeRoot(),
    canvas
)
project.read("/path/to/project.qgs")

---

QGIS 4.0 Migration Notes (Qt6)

QMetaType.Type Replaces QVariant.Type

# QGIS 3.x (Qt5) -- WILL BREAK in QGIS 4.x
from qgis.PyQt.QtCore import QVariant
field = QgsField("name", QVariant.String)

# QGIS 4.x (Qt6) -- Forward-compatible
from qgis.PyQt.QtCore import QMetaType
field = QgsField("name", QMetaType.Type.QString)
Qt5 (QVariant.Type)Qt6 (QMetaType.Type)
QVariant.StringQMetaType.Type.QString
QVariant.IntQMetaType.Type.Int
QVariant.DoubleQMetaType.Type.Double
QVariant.BoolQMetaType.Type.Bool
QVariant.DateQMetaType.Type.QDate
QVariant.DateTimeQMetaType.Type.QDateTime
QVariant.LongLongQMetaType.Type.LongLong

Other Qt6 Breaking Changes

  • QRegExp removed -- use QRegularExpression instead
  • exec_() methods renamed to exec() (dialogs, event loops)
  • Enum scoping changes -- unscoped enums become scoped
  • Use qgis.PyQt imports to abstract differences between PyQt5 and PyQt6

---

Reference Links

  • references/methods.md -- API signatures for QgsProject, QgsApplication, QgsMapLayer, QgsProviderRegistry
  • references/examples.md -- Working code examples verified against PyQGIS 3.44 documentation
  • references/anti-patterns.md -- What NOT to do, with WHY explanations

Official Sources

  • https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/intro.html
  • https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/loadproject.html
  • https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/loadlayer.html
  • https://qgis.org/pyqgis/3.44/

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.