
Qgis Errors Projections
- 8 installs
- 29 repo stars
- Updated July 8, 2026
- openaec-foundation/qgis-claude-skill-package
Helps with ai & agent building tasks.
About
qgis-errors-projections is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- qgis-errors-projections
- AI & Agent Building
- AI-coding skill
Qgis Errors Projections 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-errors-projectionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 29 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/qgis-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
qgis-errors-projections
Quick Reference
CRS Error Decision Table
| Symptom | Likely Cause | Jump To |
|---|---|---|
| Layer appears in wrong continent/ocean | Wrong EPSG code assigned | ERR-001 |
| Layer offset by ~100-200 meters | Missing datum transformation | ERR-003 |
| Layer appears stretched/squashed | Geographic CRS used for projected operations | ERR-001 |
| Coordinates swapped (X/Y flipped) | lat/lon vs lon/lat confusion | ERR-002 |
| Layer invisible (but valid) | CRS mismatch hidden by OTF reprojection | ERR-004 |
| Geometry operations return wrong values | Silent CRS corruption from wrong assignment | ERR-005 |
| Transform produces NaN or inf | QgsCoordinateTransform without context | ERR-006 |
| All CRS operations fail in standalone script | Missing srs.db (prefix path not set) | ERR-007 |
Critical Warnings
NEVER assign a CRS to "fix" misplaced data -- assigning a CRS changes interpretation, NOT coordinates. Use QgsCoordinateTransform to reproject.
NEVER create QgsCoordinateTransform without a QgsCoordinateTransformContext -- the bare constructor ignores datum transformation preferences and silently degrades accuracy.
NEVER assume QgsPointXY(x, y) matches the axis order of the EPSG definition -- QGIS ALWAYS uses lon/lat (x/y) order internally, regardless of the EPSG standard axis order.
NEVER ignore crs.isValid() == False -- an invalid CRS propagates silently through ALL downstream operations, producing wrong results without errors.
ALWAYS use QgsProject.instance().transformContext() for coordinate transforms -- it contains user-configured datum transformation preferences.
ALWAYS verify CRS validity immediately after creation with crs.isValid().
ALWAYS check crs.isGeographic() before performing distance/area calculations -- geographic CRS units are degrees, not meters.
---
Error Catalog
ERR-001: Wrong EPSG Code Selected
Symptoms:
- Layer appears in the wrong geographic location (wrong continent, ocean, or hemisphere)
- Layer appears extremely stretched or compressed
- Coordinates have unexpected magnitude (e.g., values in millions when expecting degrees)
Root Cause: The wrong EPSG code was assigned to the layer. Common confusions:
- EPSG:4326 (WGS 84, degrees) vs EPSG:3857 (Web Mercator, meters)
- EPSG:32632 (UTM zone 32N) vs EPSG:32633 (UTM zone 33N) -- wrong UTM zone
- EPSG:28992 (Amersfoort / RD New) vs EPSG:4326 -- national CRS vs global
Reproduction:
from qgis.core import QgsVectorLayer, QgsCoordinateReferenceSystem
# Data is actually in EPSG:28992 (Dutch RD New, meters)
# But loaded with wrong CRS
layer = QgsVectorLayer("points.shp", "points", "ogr")
layer.setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))
# Layer now interprets meter coordinates as degrees -- appears near 0,0 (Gulf of Guinea)Fix Pattern:
from qgis.core import QgsCoordinateReferenceSystem
# Step 1: Identify the correct CRS by examining coordinate ranges
layer = QgsVectorLayer("points.shp", "points", "ogr")
extent = layer.extent()
print(f"X range: {extent.xMinimum()} to {extent.xMaximum()}")
print(f"Y range: {extent.yMinimum()} to {extent.yMaximum()}")
# Step 2: Match coordinate ranges to CRS
# Degrees (-180 to 180, -90 to 90) → geographic CRS (EPSG:4326)
# Large numbers (100000-300000, 300000-700000) → Dutch RD (EPSG:28992)
# Large numbers (166000-834000, 0-9400000) → UTM zones
# Step 3: Assign the CORRECT CRS
correct_crs = QgsCoordinateReferenceSystem("EPSG:28992")
if correct_crs.isValid():
layer.setCrs(correct_crs)Diagnostic Table: Coordinate Range to CRS
| X Range | Y Range | Likely CRS |
|---|---|---|
| -180 to 180 | -90 to 90 | EPSG:4326 (WGS 84) |
| -20026376 to 20026376 | -20048966 to 20048966 | EPSG:3857 (Web Mercator) |
| 100000 to 300000 | 300000 to 700000 | EPSG:28992 (Dutch RD New) |
| 166000 to 834000 | 0 to 9400000 | EPSG:326xx (UTM North) |
| 166000 to 834000 | 1100000 to 10000000 | EPSG:327xx (UTM South) |
---
ERR-002: Lat/Lon vs Lon/Lat Coordinate Order Confusion
Symptoms:
- Points appear reflected across the line y=x (mirrored along diagonal)
- A point expected at Amsterdam (lon=4.9, lat=52.4) appears near Somalia (lat=4.9, lon=52.4)
- WMS 1.3.0 service returns data in unexpected positions
Root Cause: The EPSG database defines EPSG:4326 with axis order latitude, longitude (north, east). However, QGIS internally ALWAYS uses longitude, latitude (x, y) order. Confusion arises when:
- Importing CSV data where columns are labeled lat/lon
- Parsing WMS 1.3.0 responses (which follow the EPSG axis order)
- Reading GeoJSON (which specifies lon/lat per RFC 7946)
Reproduction:
from qgis.core import QgsPointXY
# Amsterdam: latitude=52.37, longitude=4.90
# WRONG -- putting latitude in x parameter
wrong_point = QgsPointXY(52.37, 4.90) # Places point near Somalia
# CORRECT -- QgsPointXY(x=longitude, y=latitude)
correct_point = QgsPointXY(4.90, 52.37) # Places point in AmsterdamFix Pattern:
from qgis.core import QgsPointXY, QgsVectorLayer, QgsFeature, QgsGeometry
# When importing from CSV with lat/lon columns:
# ALWAYS map: x=longitude_column, y=latitude_column
uri = "file:///path/to/data.csv?delimiter=,&xField=longitude&yField=latitude&crs=EPSG:4326"
layer = QgsVectorLayer(uri, "csv_points", "delimitedtext")
# When constructing points programmatically:
# ALWAYS use QgsPointXY(longitude, latitude) for EPSG:4326
lat, lon = 52.37, 4.90 # From external source
point = QgsPointXY(lon, lat) # x=lon, y=latRule: In QGIS, QgsPointXY(x, y) ALWAYS means QgsPointXY(easting/longitude, northing/latitude), regardless of the EPSG axis order definition.
---
ERR-003: Missing Datum Transformation
Symptoms:
- Coordinates are offset by 50-200 meters after transformation
- QGIS shows a datum transformation warning dialog
- Transformed points do not align with reference data
Root Cause: Transforming between CRS that use different geodetic datums (e.g., ED50 to WGS 84, NAD27 to NAD83) requires a datum transformation. Without the correct transformation grid files (Proj grids), QGIS uses a fallback that introduces positional errors of 50-200+ meters.
Reproduction:
from qgis.core import (
QgsCoordinateReferenceSystem, QgsCoordinateTransform,
QgsCoordinateTransformContext, QgsPointXY
)
# Transform from ED50 to WGS 84 WITHOUT project context
crs_ed50 = QgsCoordinateReferenceSystem("EPSG:4230")
crs_wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
# WRONG -- bare context without datum transformation preferences
bare_context = QgsCoordinateTransformContext()
xform = QgsCoordinateTransform(crs_ed50, crs_wgs84, bare_context)
result = xform.transform(QgsPointXY(5.0, 52.0))
# Result may be off by 50-200 meters due to missing gridFix Pattern:
from qgis.core import (
QgsCoordinateReferenceSystem, QgsCoordinateTransform,
QgsProject, QgsPointXY
)
crs_ed50 = QgsCoordinateReferenceSystem("EPSG:4230")
crs_wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
# CORRECT -- use project transform context (includes user datum preferences)
context = QgsProject.instance().transformContext()
xform = QgsCoordinateTransform(crs_ed50, crs_wgs84, context)
# Verify the transform is valid
if not xform.isValid():
raise RuntimeError("Coordinate transform is invalid -- check CRS definitions")
result = xform.transform(QgsPointXY(5.0, 52.0))
# For high-accuracy work, verify grid availability
if not xform.isShortCircuited():
print("Full datum transformation pipeline is active")---
ERR-004: On-the-Fly Reprojection Hiding CRS Mismatches
Symptoms:
- Layers appear correctly aligned on the map canvas
- But spatial operations (intersect, buffer, distance) produce wrong results
- Exported data has unexpected coordinate values
- Geoprocessing results are offset or geometrically incorrect
Root Cause: QGIS on-the-fly (OTF) reprojection reprojects layers for DISPLAY only. The underlying data retains its original CRS. Spatial operations use the actual stored coordinates, not the display coordinates. When two layers have different CRS, they look aligned but their raw coordinates are in different reference frames.
Reproduction:
from qgis.core import QgsVectorLayer, QgsProject
import processing
# Layer A in EPSG:4326 (degrees)
layer_a = QgsVectorLayer("parcels_wgs84.gpkg", "parcels", "ogr")
# Layer B in EPSG:28992 (meters)
layer_b = QgsVectorLayer("buildings_rd.gpkg", "buildings", "ogr")
QgsProject.instance().addMapLayer(layer_a)
QgsProject.instance().addMapLayer(layer_b)
# Layers LOOK aligned on canvas due to OTF reprojection
# But intersection uses raw coordinates -- produces WRONG results
result = processing.run("native:intersection", {
'INPUT': layer_a,
'OVERLAY': layer_b,
'OUTPUT': 'memory:'
})
# Result is empty or wrong because coordinates are in different CRSFix Pattern:
from qgis.core import QgsCoordinateReferenceSystem
import processing
# ALWAYS reproject to a common CRS before geoprocessing
target_crs = QgsCoordinateReferenceSystem("EPSG:28992")
reprojected_a = processing.run("native:reprojectlayer", {
'INPUT': layer_a,
'TARGET_CRS': target_crs,
'OUTPUT': 'memory:'
})['OUTPUT']
# Now run intersection with matching CRS
result = processing.run("native:intersection", {
'INPUT': reprojected_a,
'OVERLAY': layer_b,
'OUTPUT': 'memory:'
})Rule: ALWAYS ensure all input layers share the same CRS before running geoprocessing operations. OTF reprojection is for visualization only.
---
ERR-005: Silent Data Corruption from Wrong CRS Assignment
Symptoms:
- Data appears correct visually (because OTF compensates)
- Exported/saved data has wrong coordinate values
- Area/distance calculations return absurd values
- Other GIS software cannot read the data correctly
Root Cause: Calling layer.setCrs() changes how QGIS interprets the coordinates but does NOT modify the actual coordinate values. If the assigned CRS does not match the actual data, every operation that uses the CRS metadata produces wrong results.
Reproduction:
from qgis.core import QgsVectorLayer, QgsCoordinateReferenceSystem
# Layer contains UTM coordinates (EPSG:32632, values like 500000, 5500000)
layer = QgsVectorLayer("data_utm32.gpkg", "data", "ogr")
# WRONG -- assigning WGS 84 to UTM data
layer.setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))
# QGIS now thinks 500000 is a longitude value
# All downstream operations are silently corruptedFix Pattern:
from qgis.core import (
QgsVectorLayer, QgsCoordinateReferenceSystem,
QgsCoordinateTransform, QgsProject
)
import processing
# To ACTUALLY change coordinates from one CRS to another, use reprojection:
layer = QgsVectorLayer("data_utm32.gpkg", "data", "ogr")
# Option 1: Processing algorithm
reprojected = processing.run("native:reprojectlayer", {
'INPUT': layer,
'TARGET_CRS': QgsCoordinateReferenceSystem("EPSG:4326"),
'OUTPUT': 'memory:'
})['OUTPUT']
# Option 2: Manual transform on individual geometries
source_crs = QgsCoordinateReferenceSystem("EPSG:32632")
dest_crs = QgsCoordinateReferenceSystem("EPSG:4326")
xform = QgsCoordinateTransform(source_crs, dest_crs, QgsProject.instance().transformContext())
for feature in layer.getFeatures():
geom = feature.geometry()
geom.transform(xform) # Modifies geometry in-place
# Use the transformed geometryRule: setCrs() = change label. QgsCoordinateTransform = change coordinates. NEVER use setCrs() as a substitute for reprojection.
---
ERR-006: QgsCoordinateTransform Without Proper Context
Symptoms:
- Transform produces coordinates with reduced accuracy
- No error or warning raised
- Results differ from authoritative transformation services
Root Cause: Creating QgsCoordinateTransform(crs1, crs2) without a QgsCoordinateTransformContext uses an empty context that ignores user-configured datum transformation pipelines. This falls back to a default transformation that may be less accurate.
Reproduction:
from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform
crs1 = QgsCoordinateReferenceSystem("EPSG:4230") # ED50
crs2 = QgsCoordinateReferenceSystem("EPSG:4326") # WGS 84
# WRONG -- no context, uses fallback transformation
xform = QgsCoordinateTransform(crs1, crs2)Fix Pattern:
from qgis.core import (
QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject
)
crs1 = QgsCoordinateReferenceSystem("EPSG:4230")
crs2 = QgsCoordinateReferenceSystem("EPSG:4326")
# CORRECT -- with project transform context
xform = QgsCoordinateTransform(crs1, crs2, QgsProject.instance().transformContext())
# For standalone scripts without a project:
from qgis.core import QgsCoordinateTransformContext
context = QgsCoordinateTransformContext()
# Optionally add specific datum operations:
# context.addCoordinateOperation(crs1, crs2, "proj_pipeline_string")
xform = QgsCoordinateTransform(crs1, crs2, context)---
ERR-007: Missing srs.db in Standalone Scripts
Symptoms:
- ALL CRS objects return
isValid() == False QgsCoordinateReferenceSystem("EPSG:4326")creates an invalid CRS- Works fine inside QGIS but fails in standalone Python scripts
Root Cause: Standalone scripts must initialize QgsApplication with the correct prefix path so QGIS can locate the srs.db SQLite database containing CRS definitions.
Fix Pattern:
from qgis.core import QgsApplication, QgsCoordinateReferenceSystem
# ALWAYS initialize QgsApplication before any CRS operations
qgs = QgsApplication([], False)
qgs.setPrefixPath("/path/to/qgis", True) # Platform-dependent path
qgs.initQgis()
# Now CRS operations work
crs = QgsCoordinateReferenceSystem("EPSG:4326")
assert crs.isValid(), "CRS creation failed -- check prefix path"
# Common prefix paths:
# Windows: "C:/Program Files/QGIS 3.x/apps/qgis"
# Linux: "/usr"
# macOS: "/Applications/QGIS.app/Contents/MacOS"
# ALWAYS clean up
qgs.exitQgis()---
Diagnostic Flowchart
START: Layer appears in wrong location or operations produce wrong results
|
+--> Is the layer valid? (layer.isValid())
| |
| +--> NO --> Fix data source path/provider first (not a CRS issue)
| |
| +--> YES --> Continue
|
+--> Is the CRS valid? (layer.crs().isValid())
| |
| +--> NO --> ERR-007 if standalone script, else assign correct CRS
| |
| +--> YES --> Continue
|
+--> Check coordinate ranges (layer.extent())
| |
| +--> Coordinates match expected CRS range?
| |
| +--> NO --> ERR-001 (wrong EPSG assigned)
| |
| +--> YES --> Continue
|
+--> Are X/Y values swapped? (lat in X, lon in Y)
| |
| +--> YES --> ERR-002 (axis order confusion)
| |
| +--> NO --> Continue
|
+--> Is the offset small (50-200m)?
| |
| +--> YES --> ERR-003 (datum transformation issue)
| |
| +--> NO --> Continue
|
+--> Do layers look aligned but operations fail?
| |
| +--> YES --> ERR-004 (OTF reprojection masking mismatch)
| |
| +--> NO --> Continue
|
+--> Was setCrs() used to "fix" placement?
|
+--> YES --> ERR-005 (silent corruption from wrong assignment)
|
+--> NO --> Check for ERR-006 (transform without context)---
Debugging Commands
Quick diagnostic commands to run in the QGIS Python console:
from qgis.core import QgsProject
# Print CRS info for all layers
for name, layer in QgsProject.instance().mapLayers().items():
crs = layer.crs()
ext = layer.extent()
print(f"Layer: {layer.name()}")
print(f" CRS: {crs.authid()} ({crs.description()})")
print(f" Valid: {crs.isValid()}")
print(f" Geographic: {crs.isGeographic()}")
print(f" Units: {crs.mapUnits()}")
print(f" Extent: X({ext.xMinimum():.2f} to {ext.xMaximum():.2f}), Y({ext.yMinimum():.2f} to {ext.yMaximum():.2f})")
print()
# Print project CRS
proj_crs = QgsProject.instance().crs()
print(f"Project CRS: {proj_crs.authid()} ({proj_crs.description()})")---
Reference Links
- references/methods.md -- CRS and transform API signatures
- references/examples.md -- Working fix pattern examples
- references/anti-patterns.md -- What NOT to do with CRS operations
Official Sources
- https://qgis.org/pyqgis/master/core/QgsCoordinateReferenceSystem.html
- https://qgis.org/pyqgis/master/core/QgsCoordinateTransform.html
- https://qgis.org/pyqgis/master/core/QgsCoordinateTransformContext.html
- https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/crs.html
qgis-errors-projections — Anti-patterns
AP-001: Using setCrs() to Reproject Data
WRONG:
# Trying to "convert" a UTM layer to WGS 84 by changing its CRS label
layer = QgsVectorLayer("buildings_utm32.gpkg", "buildings", "ogr")
layer.setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))
# Coordinates are still UTM meter values but now interpreted as degrees
# A coordinate like (500000, 5500000) is now treated as lon=500000, lat=5500000WHY: setCrs() changes the CRS metadata only. It does NOT modify any coordinate values. The data is now silently corrupted -- every spatial operation will produce wrong results.
CORRECT:
import processing
reprojected = processing.run("native:reprojectlayer", {
'INPUT': layer,
'TARGET_CRS': QgsCoordinateReferenceSystem("EPSG:4326"),
'OUTPUT': 'memory:'
})['OUTPUT']---
AP-002: Creating QgsCoordinateTransform Without Context
WRONG:
xform = QgsCoordinateTransform(crs_source, crs_dest)
# Missing QgsCoordinateTransformContext -- ignores datum transformation preferencesWHY: Without a transform context, QGIS uses a default transformation that may lack the correct datum shift parameters. This silently degrades accuracy by 50-200+ meters for cross-datum transforms.
CORRECT:
xform = QgsCoordinateTransform(
crs_source, crs_dest,
QgsProject.instance().transformContext()
)---
AP-003: Putting Latitude in the X Parameter
WRONG:
# Amsterdam: lat=52.37, lon=4.90
point = QgsPointXY(52.37, 4.90) # X=52.37, Y=4.90 -- WRONG
# This places the point near Somalia, not AmsterdamWHY: QgsPointXY(x, y) ALWAYS uses mathematical convention where X=easting/longitude and Y=northing/latitude. The EPSG:4326 standard axis order (lat, lon) does NOT apply to QgsPointXY construction.
CORRECT:
point = QgsPointXY(4.90, 52.37) # X=lon, Y=lat---
AP-004: Ignoring CRS Validity
WRONG:
crs = QgsCoordinateReferenceSystem("EPSG:99999")
# No validity check -- crs.isValid() returns False
xform = QgsCoordinateTransform(crs, other_crs, context)
result = xform.transform(point)
# result contains garbage coordinates -- no error raisedWHY: An invalid CRS object does not raise exceptions when used. It silently produces wrong or undefined results in all downstream operations.
CORRECT:
crs = QgsCoordinateReferenceSystem("EPSG:99999")
if not crs.isValid():
raise ValueError("Invalid CRS: EPSG:99999")---
AP-005: Assuming OTF Reprojection Applies to Geoprocessing
WRONG:
# Layer A is EPSG:4326, Layer B is EPSG:28992
# They look aligned on the canvas due to OTF reprojection
result = processing.run("native:intersection", {
'INPUT': layer_a, # EPSG:4326 (degrees)
'OVERLAY': layer_b, # EPSG:28992 (meters)
'OUTPUT': 'memory:'
})
# Result is empty or wrong -- raw coordinates do not overlapWHY: On-the-fly reprojection is a DISPLAY feature only. It reprojects for rendering on the map canvas. Geoprocessing algorithms operate on the actual stored coordinates. Mixing CRS produces meaningless results.
CORRECT:
# Reproject to common CRS first
reprojected_a = processing.run("native:reprojectlayer", {
'INPUT': layer_a,
'TARGET_CRS': QgsCoordinateReferenceSystem("EPSG:28992"),
'OUTPUT': 'memory:'
})['OUTPUT']
result = processing.run("native:intersection", {
'INPUT': reprojected_a,
'OVERLAY': layer_b,
'OUTPUT': 'memory:'
})---
AP-006: Using Geographic CRS for Distance/Area Calculations
WRONG:
from qgis.core import QgsDistanceArea, QgsCoordinateReferenceSystem, QgsPointXY
d = QgsDistanceArea()
# No CRS or ellipsoid set -- uses planar calculation on degree values
distance = d.measureLine(QgsPointXY(4.9, 52.3), QgsPointXY(5.1, 52.5))
# Returns a value in degrees, not meters -- meaningless for real-world distancesWHY: Without setting the source CRS and ellipsoid, QgsDistanceArea performs a simple Euclidean calculation on raw coordinate values. For geographic CRS, these values are in degrees, producing meaningless distance/area values.
CORRECT:
from qgis.core import QgsDistanceArea, QgsCoordinateReferenceSystem, QgsPointXY, QgsProject
d = QgsDistanceArea()
d.setSourceCrs(
QgsCoordinateReferenceSystem("EPSG:4326"),
QgsProject.instance().transformContext()
)
d.setEllipsoid('WGS84')
distance = d.measureLine(QgsPointXY(4.9, 52.3), QgsPointXY(5.1, 52.5))
# Returns distance in meters using geodesic calculation---
AP-007: Hardcoding CRS Without Checking the Source Data
WRONG:
# Assuming all shapefiles are WGS 84
layer = QgsVectorLayer("unknown_data.shp", "data", "ogr")
layer.setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))WHY: Shapefiles may use any CRS. The .prj file (if present) defines the CRS. Blindly assigning EPSG:4326 corrupts data that is in a different CRS. If the .prj is missing, examine the coordinate ranges to determine the correct CRS.
CORRECT:
layer = QgsVectorLayer("unknown_data.shp", "data", "ogr")
crs = layer.crs()
if not crs.isValid():
# CRS unknown -- examine coordinates to determine CRS
ext = layer.extent()
print(f"X: {ext.xMinimum()} to {ext.xMaximum()}")
print(f"Y: {ext.yMinimum()} to {ext.yMaximum()}")
# Use the diagnostic table from SKILL.md ERR-001 to identify the CRS---
AP-008: Not Initializing QgsApplication in Standalone Scripts
WRONG:
from qgis.core import QgsCoordinateReferenceSystem
# No QgsApplication initialization
crs = QgsCoordinateReferenceSystem("EPSG:4326")
print(crs.isValid()) # False -- srs.db not foundWHY: Without QgsApplication.initQgis(), the CRS database (srs.db) is not loaded. ALL CRS objects will be invalid, and all coordinate operations will fail silently.
CORRECT:
from qgis.core import QgsApplication, QgsCoordinateReferenceSystem
qgs = QgsApplication([], False)
qgs.setPrefixPath("/path/to/qgis", True)
qgs.initQgis()
crs = QgsCoordinateReferenceSystem("EPSG:4326")
print(crs.isValid()) # True
qgs.exitQgis()---
AP-009: Using Deprecated CRS Creation Methods
WRONG:
crs = QgsCoordinateReferenceSystem()
crs.createFromSrid(4326) # Deprecated
crs.createFromEpsg(4326) # DeprecatedWHY: These methods are deprecated since QGIS 3.x and may be removed in QGIS 4.x. They also require two steps (construct + initialize) instead of one.
CORRECT:
crs = QgsCoordinateReferenceSystem("EPSG:4326")
# Or use the static factory:
crs = QgsCoordinateReferenceSystem.fromEpsgId(4326)---
AP-010: Mixing CRS in Geometry Construction
WRONG:
# Building a polygon with vertices from different CRS
from qgis.core import QgsGeometry, QgsPointXY
# Point A from WGS 84 (degrees)
p1 = QgsPointXY(4.9, 52.3)
# Point B from UTM (meters) -- different CRS!
p2 = QgsPointXY(500000, 5800000)
polygon = QgsGeometry.fromPolygonXY([[p1, p2, QgsPointXY(4.95, 52.35), p1]])
# Geometry is nonsensical -- mixing degrees and metersWHY: QgsPointXY has no CRS awareness. Combining points from different coordinate reference systems produces geometrically meaningless results. ALWAYS transform all points to the same CRS before constructing geometries.
CORRECT:
from qgis.core import (
QgsCoordinateReferenceSystem, QgsCoordinateTransform,
QgsProject, QgsPointXY, QgsGeometry
)
# Transform p2 from UTM to WGS 84 first
xform = QgsCoordinateTransform(
QgsCoordinateReferenceSystem("EPSG:32632"),
QgsCoordinateReferenceSystem("EPSG:4326"),
QgsProject.instance().transformContext()
)
p2_wgs84 = xform.transform(QgsPointXY(500000, 5800000))
# Now all points are in the same CRS
p1 = QgsPointXY(4.9, 52.3)
polygon = QgsGeometry.fromPolygonXY([[p1, p2_wgs84, QgsPointXY(4.95, 52.35), p1]])qgis-errors-projections — Examples
Example 1: Diagnose CRS Issues for All Project Layers
from qgis.core import QgsProject
def diagnose_crs_issues():
"""Print CRS diagnostic info for every layer in the project."""
project = QgsProject.instance()
project_crs = project.crs()
print(f"Project CRS: {project_crs.authid()} ({project_crs.description()})")
print(f"Project CRS valid: {project_crs.isValid()}")
print("-" * 60)
for layer_id, layer in project.mapLayers().items():
crs = layer.crs()
ext = layer.extent()
print(f"Layer: {layer.name()}")
print(f" CRS: {crs.authid()} ({crs.description()})")
print(f" CRS valid: {crs.isValid()}")
print(f" Geographic: {crs.isGeographic()}")
print(f" Map units: {crs.mapUnits()}")
print(f" Extent X: {ext.xMinimum():.4f} to {ext.xMaximum():.4f}")
print(f" Extent Y: {ext.yMinimum():.4f} to {ext.yMaximum():.4f}")
# Flag potential issues
if not crs.isValid():
print(" ** WARNING: Invalid CRS **")
if crs.authid() != project_crs.authid():
print(f" ** NOTE: Differs from project CRS ({project_crs.authid()}) **")
print()
diagnose_crs_issues()---
Example 2: Safe Coordinate Transformation with Validation
from qgis.core import (
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
QgsProject,
QgsPointXY
)
def safe_transform(point, source_epsg, dest_epsg):
"""Transform a point between CRS with full error checking."""
source_crs = QgsCoordinateReferenceSystem(f"EPSG:{source_epsg}")
dest_crs = QgsCoordinateReferenceSystem(f"EPSG:{dest_epsg}")
# Validate both CRS
if not source_crs.isValid():
raise ValueError(f"Invalid source CRS: EPSG:{source_epsg}")
if not dest_crs.isValid():
raise ValueError(f"Invalid destination CRS: EPSG:{dest_epsg}")
# Create transform with project context
xform = QgsCoordinateTransform(
source_crs, dest_crs,
QgsProject.instance().transformContext()
)
if not xform.isValid():
raise RuntimeError(f"Cannot create transform from EPSG:{source_epsg} to EPSG:{dest_epsg}")
# Perform transformation
result = xform.transform(point)
return result
# Usage: Transform Amsterdam from WGS 84 to Dutch RD New
amsterdam_wgs84 = QgsPointXY(4.8952, 52.3702) # lon, lat
amsterdam_rd = safe_transform(amsterdam_wgs84, 4326, 28992)
print(f"Amsterdam in RD New: X={amsterdam_rd.x():.2f}, Y={amsterdam_rd.y():.2f}")
# Expected: approximately X=121000, Y=487000---
Example 3: Reproject Layer Before Geoprocessing
from qgis.core import QgsCoordinateReferenceSystem, QgsProject
import processing
def reproject_and_intersect(layer_a, layer_b, target_epsg):
"""Reproject both layers to a common CRS, then intersect."""
target_crs = QgsCoordinateReferenceSystem(f"EPSG:{target_epsg}")
if not target_crs.isValid():
raise ValueError(f"Invalid target CRS: EPSG:{target_epsg}")
# Reproject layer A if needed
if layer_a.crs().authid() != target_crs.authid():
result_a = processing.run("native:reprojectlayer", {
'INPUT': layer_a,
'TARGET_CRS': target_crs,
'OUTPUT': 'memory:'
})
layer_a_proj = result_a['OUTPUT']
else:
layer_a_proj = layer_a
# Reproject layer B if needed
if layer_b.crs().authid() != target_crs.authid():
result_b = processing.run("native:reprojectlayer", {
'INPUT': layer_b,
'TARGET_CRS': target_crs,
'OUTPUT': 'memory:'
})
layer_b_proj = result_b['OUTPUT']
else:
layer_b_proj = layer_b
# Now intersect with matching CRS
result = processing.run("native:intersection", {
'INPUT': layer_a_proj,
'OVERLAY': layer_b_proj,
'OUTPUT': 'memory:'
})
return result['OUTPUT']---
Example 4: Detect and Fix Swapped Lat/Lon
from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY
def detect_swapped_coordinates(layer):
"""Detect if a WGS 84 layer has lat/lon swapped (lat in X, lon in Y)."""
if layer.crs().authid() != "EPSG:4326":
print("This check is only relevant for EPSG:4326 layers")
return False
ext = layer.extent()
x_range = (ext.xMinimum(), ext.xMaximum())
y_range = (ext.yMinimum(), ext.yMaximum())
# For correct lon/lat: X (lon) in -180..180, Y (lat) in -90..90
# For swapped lat/lon: X would be in -90..90, Y could exceed 90
x_looks_like_lat = -90 <= x_range[0] and x_range[1] <= 90
y_looks_like_lon = abs(y_range[0]) > 90 or abs(y_range[1]) > 90
if x_looks_like_lat and y_looks_like_lon:
print(f"LIKELY SWAPPED: X range {x_range} looks like latitude, "
f"Y range {y_range} looks like longitude")
return True
print(f"Coordinates appear correct: X (lon) {x_range}, Y (lat) {y_range}")
return False
def fix_swapped_coordinates(layer):
"""Create a new memory layer with X/Y coordinates swapped."""
fields = layer.fields()
mem_layer = QgsVectorLayer(
f"Point?crs=EPSG:4326",
f"{layer.name()}_fixed",
"memory"
)
provider = mem_layer.dataProvider()
provider.addAttributes(fields.toList())
mem_layer.updateFields()
features = []
for feat in layer.getFeatures():
new_feat = QgsFeature(fields)
new_feat.setAttributes(feat.attributes())
geom = feat.geometry()
if not geom.isNull():
point = geom.asPoint()
# Swap X and Y
new_geom = QgsGeometry.fromPointXY(QgsPointXY(point.y(), point.x()))
new_feat.setGeometry(new_geom)
features.append(new_feat)
provider.addFeatures(features)
mem_layer.updateExtents()
return mem_layer---
Example 5: Verify Datum Transformation Accuracy
from qgis.core import (
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
QgsProject,
QgsPointXY
)
def verify_datum_transform(source_epsg, dest_epsg, test_point, expected_point, tolerance_m=1.0):
"""Verify a datum transformation against a known reference point."""
source_crs = QgsCoordinateReferenceSystem(f"EPSG:{source_epsg}")
dest_crs = QgsCoordinateReferenceSystem(f"EPSG:{dest_epsg}")
xform = QgsCoordinateTransform(
source_crs, dest_crs,
QgsProject.instance().transformContext()
)
result = xform.transform(test_point)
dx = result.x() - expected_point.x()
dy = result.y() - expected_point.y()
# For geographic CRS, rough conversion: 1 degree ~ 111320m at equator
if dest_crs.isGeographic():
error_m = ((dx * 111320) ** 2 + (dy * 111320) ** 2) ** 0.5
else:
error_m = (dx ** 2 + dy ** 2) ** 0.5
print(f"Transform result: ({result.x():.8f}, {result.y():.8f})")
print(f"Expected: ({expected_point.x():.8f}, {expected_point.y():.8f})")
print(f"Estimated error: {error_m:.3f} meters")
if error_m > tolerance_m:
print(f"WARNING: Error exceeds tolerance of {tolerance_m}m")
print("Possible cause: missing datum transformation grid")
return False
print("PASS: Within tolerance")
return True
# Example: Verify ED50 to WGS 84 transformation
verify_datum_transform(
source_epsg=4230, # ED50
dest_epsg=4326, # WGS 84
test_point=QgsPointXY(5.0, 52.0),
expected_point=QgsPointXY(4.99867, 51.99895), # Approximate known value
tolerance_m=5.0
)---
Example 6: Standalone Script with Correct CRS Initialization
import sys
from qgis.core import (
QgsApplication,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
QgsPointXY
)
# Initialize QGIS application (REQUIRED for standalone scripts)
qgs = QgsApplication([], False)
# Platform-specific prefix paths:
# Windows: "C:/Program Files/QGIS 3.34/apps/qgis"
# Linux: "/usr"
# macOS: "/Applications/QGIS.app/Contents/MacOS"
qgs.setPrefixPath("C:/Program Files/QGIS 3.34/apps/qgis", True)
qgs.initQgis()
# Verify CRS system is working
test_crs = QgsCoordinateReferenceSystem("EPSG:4326")
if not test_crs.isValid():
print("ERROR: CRS system not initialized. Check prefix path.")
qgs.exitQgis()
sys.exit(1)
# Perform CRS operations
source = QgsCoordinateReferenceSystem("EPSG:4326")
dest = QgsCoordinateReferenceSystem("EPSG:32632")
from qgis.core import QgsCoordinateTransformContext
context = QgsCoordinateTransformContext()
xform = QgsCoordinateTransform(source, dest, context)
point = QgsPointXY(9.0, 48.0)
result = xform.transform(point)
print(f"UTM 32N: {result.x():.2f}, {result.y():.2f}")
# Clean up
qgs.exitQgis()---
Example 7: Batch Validate CRS for All Layers
from qgis.core import QgsProject
def validate_project_crs():
"""Validate CRS consistency across all project layers. Returns list of issues."""
project = QgsProject.instance()
project_crs = project.crs()
issues = []
for layer_id, layer in project.mapLayers().items():
crs = layer.crs()
name = layer.name()
# Check 1: CRS validity
if not crs.isValid():
issues.append(f"CRITICAL: '{name}' has invalid CRS")
continue
# Check 2: CRS matches project
if crs.authid() != project_crs.authid():
issues.append(
f"MISMATCH: '{name}' uses {crs.authid()}, "
f"project uses {project_crs.authid()}"
)
# Check 3: Extent sanity for geographic CRS
if crs.isGeographic():
ext = layer.extent()
if abs(ext.xMinimum()) > 180 or abs(ext.xMaximum()) > 180:
issues.append(
f"SUSPECT: '{name}' has geographic CRS but "
f"X values exceed 180 degrees (possible wrong CRS)"
)
if abs(ext.yMinimum()) > 90 or abs(ext.yMaximum()) > 90:
issues.append(
f"SUSPECT: '{name}' has geographic CRS but "
f"Y values exceed 90 degrees (possible wrong CRS)"
)
if not issues:
print("All layers pass CRS validation")
else:
for issue in issues:
print(issue)
return issues
validate_project_crs()qgis-errors-projections — Methods Reference
QgsCoordinateReferenceSystem
Constructors
QgsCoordinateReferenceSystem()
# Creates an invalid CRS. ALWAYS check isValid() after creation.
QgsCoordinateReferenceSystem(definition: str)
# Creates CRS from definition string with prefix.
# Supported prefixes: "EPSG:", "POSTGIS:", "INTERNAL:", "PROJ:", "WKT:"
# Example: QgsCoordinateReferenceSystem("EPSG:4326")Validation Methods
| Method | Return Type | Description |
|---|---|---|
isValid() | bool | Returns True if CRS was successfully created. ALWAYS call after construction. |
isGeographic() | bool | Returns True if CRS uses angular units (degrees). False for projected CRS (meters, feet). |
Identity Methods
| Method | Return Type | Description |
|---|---|---|
authid() | str | Authority identifier (e.g., "EPSG:4326"). Returns empty string if no authority. |
description() | str | Human-readable name (e.g., "WGS 84"). |
postgisSrid() | int | PostGIS SRID value (e.g., 4326). |
srsid() | int | QGIS internal SRS ID. NOT the same as EPSG code. |
Representation Methods
| Method | Return Type | Description |
|---|---|---|
toProj() | str | Full Proj pipeline string. |
toWkt(variant) | str | WKT representation. Use Qgis.CrsWktVariant.Wkt2_2019 for modern WKT. |
projectionAcronym() | str | Projection type (e.g., "longlat", "utm", "tmerc"). |
ellipsoidAcronym() | str | Ellipsoid identifier (e.g., "EPSG:7030" for WGS 84). |
Unit Methods
| Method | Return Type | Description |
|---|---|---|
mapUnits() | Qgis.DistanceUnit | Distance unit for this CRS. |
Static Factory Methods
| Method | Return Type | Description |
|---|---|---|
fromEpsgId(id) | QgsCoordinateReferenceSystem | Create from EPSG integer. |
fromProj(proj) | QgsCoordinateReferenceSystem | Create from Proj string. |
fromWkt(wkt) | QgsCoordinateReferenceSystem | Create from WKT string. |
Deprecated Methods (NEVER Use)
| Deprecated Method | Replacement |
|---|---|
createFromSrid(srid) | QgsCoordinateReferenceSystem("EPSG:{srid}") |
createFromEpsg(epsg) | QgsCoordinateReferenceSystem("EPSG:{epsg}") |
createFromId(id) | Use constructor with prefix string |
---
QgsCoordinateTransform
Constructors
QgsCoordinateTransform()
# Creates an invalid/empty transform.
QgsCoordinateTransform(source: QgsCoordinateReferenceSystem,
destination: QgsCoordinateReferenceSystem,
context: QgsCoordinateTransformContext)
# CORRECT — with transform context for datum handling.
QgsCoordinateTransform(source: QgsCoordinateReferenceSystem,
destination: QgsCoordinateReferenceSystem,
project: QgsProject)
# CORRECT — extracts context from project automatically.
QgsCoordinateTransform(source: QgsCoordinateReferenceSystem,
destination: QgsCoordinateReferenceSystem)
# DEPRECATED — NEVER use. Missing context degrades datum transformation accuracy.Transform Methods
| Method | Parameters | Return Type | Description |
|---|---|---|---|
transform(point) | QgsPointXY | QgsPointXY | Forward transform (source to dest). |
transform(point, direction) | QgsPointXY, TransformDirection | QgsPointXY | Transform with explicit direction. |
transform(x, y) | float, float | QgsPointXY | Transform from raw coordinates. |
transformBoundingBox(rect) | QgsRectangle | QgsRectangle | Transform a bounding box. |
Direction Enum
| Value | Description |
|---|---|
QgsCoordinateTransform.ForwardTransform | Source CRS to destination CRS. |
QgsCoordinateTransform.ReverseTransform | Destination CRS to source CRS. |
Validation Methods
| Method | Return Type | Description |
|---|---|---|
isValid() | bool | Returns True if both source and dest CRS are valid. |
isShortCircuited() | bool | Returns True if source == dest (no actual transform needed). |
sourceCrs() | QgsCoordinateReferenceSystem | The source CRS. |
destinationCrs() | QgsCoordinateReferenceSystem | The destination CRS. |
---
QgsCoordinateTransformContext
Constructor
QgsCoordinateTransformContext()
# Creates an empty context. Prefer QgsProject.instance().transformContext() instead.Methods
| Method | Parameters | Description |
|---|---|---|
addCoordinateOperation(src, dest, operation) | CRS, CRS, str | Add a specific Proj operation for a CRS pair. |
removeCoordinateOperation(src, dest) | CRS, CRS | Remove a specific operation. |
hasTransform(src, dest) | CRS, CRS | Check if a specific transform exists. |
calculateCoordinateOperation(src, dest) | CRS, CRS | Get the Proj operation string for a CRS pair. |
---
QgsGeometry Transform
In-Place Geometry Transform
geometry.transform(transform: QgsCoordinateTransform) -> Qgis.GeometryOperationResult
# Transforms geometry coordinates in-place.
# Returns Qgis.GeometryOperationResult.Success on success.---
QgsDistanceArea (CRS-Aware Measurements)
Setup Methods
| Method | Parameters | Description |
|---|---|---|
setSourceCrs(crs, context) | CRS, TransformContext | Set source CRS for calculations. |
setEllipsoid(ellipsoid) | str | Set ellipsoid (e.g., "WGS84", "GRS80"). |
Measurement Methods
| Method | Parameters | Return Type | Description |
|---|---|---|---|
measureLine(p1, p2) | QgsPointXY, QgsPointXY | float | Distance in meters. |
measureLine(points) | list[QgsPointXY] | float | Polyline length in meters. |
measureArea(geometry) | QgsGeometry | float | Area in square meters. |
convertLengthMeasurement(length, unit) | float, DistanceUnit | float | Convert length units. |
convertAreaMeasurement(area, unit) | float, AreaUnit | float | Convert area units. |
---
QgsProject CRS Methods
| Method | Description |
|---|---|
QgsProject.instance().crs() | Get the project CRS. |
QgsProject.instance().setCrs(crs) | Set the project CRS (controls OTF reprojection target). |
QgsProject.instance().transformContext() | Get the project's datum transform context. ALWAYS use this for transforms. |
QgsProject.instance().ellipsoid() | Get the project ellipsoid string. |
---
Layer CRS Methods
| Method | Description |
|---|---|
layer.crs() | Get the layer's CRS. |
layer.setCrs(crs) | Set the layer's CRS metadata. NEVER use for reprojection — this only changes the label. |
layer.extent() | Get the layer extent in native CRS coordinates. Use to diagnose wrong CRS assignment. |