mcpField
Q
qgis-impl-vector-analysis

qgis-impl-vector-analysis

github.com/impertio-studioData & databases3948 installsUpdated 31w ago
Repository

About qgis-impl-vector-analysis

A reference skill for PyQGIS vector analysis, aimed at developers writing spatial data workflows. It combines API patterns with warnings about common geometry, CRS, and output-handling pitfalls.

Key features

  1. 01Spatial feature filtering and indexing
  2. 02Geometry operations and overlays
  3. 03Attribute and location-based joins
  4. 04Writing vector data to GeoPackage
  5. 05CRS and geometry safety guidance

Use cases

  1. 01Filtering features by area or expression
  2. 02Building buffers and overlay results
  3. 03Joining attributes across vector layers
  4. 04Saving analysis outputs to disk

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/impertio-studio/qgis-claude-skill-package --skill qgis-impl-vector-analysis

After installing, type/qgis-impl-vector-analysisin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
anti-patterns.md
examples.md
methods.md

Details

From the project README

qgis-impl-vector-analysis

Quick Reference

Core Classes
ClassPurposeImport
QgsFeatureRequestFilter features by rect, expression, or FIDqgis.core
QgsSpatialIndexIn-memory R-tree for fast spatial lookupsqgis.core
QgsGeometryGeometry operations (buffer, intersects, contains)qgis.core
QgsVectorFileWriterWrite vector layers to disk (GPKG, SHP, GeoJSON)qgis.core
QgsCoordinateTransformContextCRS transformation context for outputqgis.core
processingRun QGIS Processing algorithmsimport processing
Key Processing Algorithm IDs
AlgorithmPurpose
native:bufferCreate buffer zones around features
native:clipClip input layer by overlay layer
native:intersectionGeometric intersection of two layers
native:unionGeometric union of two layers
native:differenceGeometric difference (A minus B)
native:symmetricaldifferenceFeatures in A or B but not both
native:dissolveMerge features by attribute value
native:joinattributestableJoin attributes by matching field values
native:joinattributesbylocationSpatial join based on geometry relationship
native:extractbyexpressionExtract features matching expression
native:extractbylocationExtract features by spatial relationship
native:fieldcalculatorCalculate field values with expressions

Critical Warnings

NEVER pass layers with different CRS to overlay operations without reprojecting first. Processing algorithms reproject automatically, but PyQGIS geometry methods do NOT. ALWAYS verify CRS match before direct QgsGeometry operations.

NEVER call QgsGeometry methods on NULL geometries. ALWAYS check feature.hasGeometry() before ANY geometry operation. NULL geometries cause silent failures or crashes.

NEVER forget to call del writer after using QgsVectorFileWriter.create(). The file is NOT written to disk until the writer object is destroyed.

NEVER use 'OUTPUT': 'memory:' for large datasets in batch processing. Memory layers consume RAM and are lost when QGIS closes. ALWAYS write to GeoPackage for persistence.

NEVER assume processing.run() modifies the input layer. It ALWAYS creates a new output layer. Access results via result['OUTPUT'].

ALWAYS use native:fixgeometries before overlay operations when input data comes from external sources. Invalid geometries cause overlay algorithms to fail silently or produce incomplete results.

ALWAYS initialize the Processing framework before calling processing.run() in standalone scripts:

from qgis.core import QgsApplication
import processing
from processing.core.Processing import Processing
Processing.initialize()

Decision Tree: Which Overlay Operation to Use

What is your goal?
|
+-- Keep ONLY the area where both layers overlap
|   --> native:intersection
|
+-- Combine ALL areas from both layers into one
|   --> native:union
|
+-- Remove areas of layer B from layer A
|   +-- Keep remainder of A only
|   |   --> native:difference
|   +-- Keep remainder of BOTH A and B (exclude overlap)
|       --> native:symmetricaldifference
|
+-- Cut layer A to the boundary of layer B
|   --> native:clip
|
+-- Transfer attributes from one layer to another
    +-- Based on matching field values
    |   --> native:joinattributestable
    +-- Based on spatial relationship
    |   --> native:joinattributesbylocation
    +-- Based on nearest feature
        --> native:joinattributesbynearest
Clip vs Intersection
Aspectnative:clipnative:intersection
Output geometrySame as inputMay split/merge at overlay boundaries
Overlay attributesNOT includedIncluded in output
Use caseTrim to study areaCombine data from both layers
PerformanceFasterSlower (attribute handling)

Essential Patterns

Pattern 1: Spatial Query with Feature Request
from qgis.core import QgsFeatureRequest, QgsRectangle

# Filter by bounding rectangle with exact geometry test
area = QgsRectangle(100.0, -1.0, 101.0, 0.0)
request = QgsFeatureRequest().setFilterRect(area)
request.setFlags(QgsFeatureRequest.ExactIntersect)
request.setLimit(100)

for feature in layer.getFeatures(request):
    print(feature.id(), feature.geometry().asWkt())
Pattern 2: Spatial Index for Fast Lookups
from qgis.core import QgsSpatialIndex, QgsPointXY

index = QgsSpatialIndex(layer.getFeatures())

# Nearest neighbor: returns feature IDs
nearest_ids = index.nearestNeighbor(QgsPointXY(15.5, 47.1), 5)

# Bounding box intersection: returns feature IDs
bbox = QgsRectangle(14.0, 46.0, 17.0, 49.0)
intersecting_ids = index.intersects(bbox)

# Retrieve actual features by ID
for fid in nearest_ids:
    feature = layer.getFeature(fid)
Pattern 3: Buffer Analysis via Processing
import processing

result = processing.run("native:buffer", {
    'INPUT': layer,
    'DISTANCE': 100,
    'SEGMENTS': 5,
    'END_CAP_STYLE': 0,   # 0=Round, 1=Flat, 2=Square
    'JOIN_STYLE': 0,       # 0=Round, 1=Miter, 2=Bevel
    'MITER_LIMIT': 2,
    'DISSOLVE': False,
    'OUTPUT': 'memory:'
})
buffered_layer = result['OUTPUT']
Pattern 4: Buffer via QgsGeometry (Single Feature)
geom = feature.geometry()
if not geom.isNull():
    buffered = geom.buffer(100.0, 5)  # distance, segments
Pattern 5: Overlay Operation (Intersection)
import processing

result = processing.run("native:intersection", {
    'INPUT': layer_a,
    'OVERLAY': layer_b,
    'INPUT_FIELDS': [],
    'OVERLAY_FIELDS': [],
    'OVERLAY_FIELDS_PREFIX': '',
    'OUTPUT': 'memory:'
})
intersection_layer = result['OUTPUT']
Pattern 6: Attribute Join by Field Value
result = processing.run("native:joinattributestable", {
    'INPUT': layer_a,
    'FIELD': 'id',
    'INPUT_2': layer_b,
    'FIELD_2': 'foreign_id',
    'FIELDS_TO_COPY': [],
    'METHOD': 0,               # 0=one-to-many, 1=first match only
    'DISCARD_NONMATCHING': False,
    'PREFIX': '',
    'OUTPUT': 'memory:'
})
Pattern 7: Spatial Join by Location
result = processing.run("native:joinattributesbylocation", {
    'INPUT': target_layer,
    'PREDICATE': [0],          # 0=intersects, 1=contains, 2=equals,
                               # 3=touches, 4=overlaps, 5=within, 6=crosses
    'JOIN': join_layer,
    'JOIN_FIELDS': [],
    'METHOD': 0,               # 0=one-to-many, 1=one-to-first, 2=largest overlap
    'DISCARD_NONMATCHING': False,
    'PREFIX': '',
    'OUTPUT': 'memory:'
})
Pattern 8: Dissolve by Attribute
result = processing.run("native:dissolve", {
    'INPUT': layer,
    'FIELD': ['province'],     # Dissolve field(s); empty list = dissolve all
    'SEPARATE_DISJOINT': False,
    'OUTPUT': 'memory:'
})

Common Operations

Field Calculation (Edit Buffer)
with edit(layer):
    field_idx = layer.fields().indexOf('area_m2')
    for feature in layer.getFeatures():
        if feature.hasGeometry():
            area = feature.geometry().area()
            layer.changeAttributeValue(feature.id(), field_idx, area)
Field Calculation (Processing)
result = processing.run("native:fieldcalculator", {
    'INPUT': layer,
    'FIELD_NAME': 'area_m2',
    'FIELD_TYPE': 0,           # 0=Float, 1=Integer, 2=String, 3=Date
    'FIELD_LENGTH': 10,
    'FIELD_PRECISION': 3,
    'FORMULA': '$area',
    'OUTPUT': 'memory:'
})
Feature Selection
# Select by expression
layer.selectByExpression('"type" = \'highway\'')

# Iterate selected features
for feature in layer.selectedFeatures():
    print(feature.id())

# Clear selection
layer.removeSelection()
Extract Features by Expression
result = processing.run("native:extractbyexpression", {
    'INPUT': layer,
    'EXPRESSION': '"population" > 50000',
    'OUTPUT': 'memory:'
})
Extract Features by Location
result = processing.run("native:extractbylocation", {
    'INPUT': layer,
    'PREDICATE': [0],          # 0=intersects
    'INTERSECT': reference_layer,
    'OUTPUT': 'memory:'
})
Write Output to GeoPackage
from qgis.core import QgsVectorFileWriter, QgsCoordinateTransformContext

save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "GPKG"
save_options.fileEncoding = "UTF-8"

error = QgsVectorFileWriter.writeAsVectorFormatV3(
    layer,
    "/path/to/output.gpkg",
    QgsCoordinateTransformContext(),
    save_options
)
Write Features Individually
from qgis.core import QgsVectorFileWriter, QgsWkbTypes, QgsCoordinateTransformContext

save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "GPKG"

writer = QgsVectorFileWriter.create(
    "/path/to/output.gpkg",
    layer.fields(),
    QgsWkbTypes.Polygon,
    layer.crs(),
    QgsCoordinateTransformContext(),
    save_options
)
for feature in layer.getFeatures():
    writer.addFeature(feature)
del writer  # ALWAYS delete to flush and close the file
Aggregate with Statistics
result = processing.run("native:aggregate", {
    'INPUT': layer,
    'GROUP_BY': '"province"',
    'AGGREGATES': [
        {'aggregate': 'sum', 'delimiter': ',', 'input': '"population"',
         'length': 10, 'name': 'total_pop', 'precision': 0, 'type': 6}
    ],
    'OUTPUT': 'memory:'
})

Reference Links

Official Sources

Comments

Markdown supported