[{"data":1,"prerenderedAt":1461},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fhandling-missing-crs-in-pyqgis":3},{"id":4,"title":5,"body":6,"description":1450,"extension":1451,"meta":1452,"navigation":391,"path":1457,"seo":1458,"stem":1459,"__hash__":1460},"docs\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fhandling-missing-crs-in-pyqgis\u002Findex.md","Handling Missing CRS in PyQGIS",{"type":7,"value":8,"toc":1438},"minimark",[9,13,41,279,284,320,340,344,347,705,734,745,780,784,787,867,905,909,912,991,994,998,1001,1082,1089,1093,1105,1192,1199,1253,1257,1324,1328,1344,1348,1363,1380,1389,1400,1409,1413,1434],[10,11,5],"h1",{"id":12},"handling-missing-crs-in-pyqgis",[14,15,16,17,22,23,27,28,31,32,35,36,40],"p",{},"When a layer loads without a defined ",[18,19,21],"a",{"href":20},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","Coordinate Reference System",", PyQGIS hands you an invalid ",[24,25,26],"code",{},"QgsCoordinateReferenceSystem"," object rather than an error. Nothing crashes — but the layer has no idea where on Earth its coordinates sit, so the first spatial join, buffer, or distance calculation you run against it silently produces garbage. The fix is a small, deliberate sequence: detect the undefined state with ",[24,29,30],{},"isValid()",", validate the projection you intend to apply, and attach it with ",[24,33,34],{},"layer.setCrs()"," ",[37,38,39],"strong",{},"before"," any geometry processing runs. This page shows that recipe end to end, plus the fallback strategies for files that arrive with no usable projection metadata at all.",[42,43,48,52,56,63,93,103,112,117,121,126,130,136,142,146,153,157,164,169,174,177,181,185,188,193,199,206,210,214,217,221,224,227,233,237,243,247,252,256,260,266,271,275],"svg",{"viewBox":44,"role":45,"ariaLabel":46,"xmlns":47},"0 0 780 650","img","Decision flowchart for handling a layer with a missing CRS in PyQGIS. A loaded layer is tested with layer.crs().isValid(). If it is valid, geometry operations run safely. If it is invalid, work through the projection in order without guessing: first check for .prj or .xml sidecar files, then inspect the raw coordinate bounds using the WGS84-versus-projected heuristic. Then decide whether the CRS is now identified. If it is known, call layer.setCrs(target) after a target.isValid() check, leaving geometry unchanged. If it is still unknown, log and skip the layer and wait for review rather than guessing.","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[49,50,51],"title",{},"Handling a missing CRS in PyQGIS: detect, identify, then assign or skip",[53,54,55],"desc",{},"Start with a loaded layer and test layer.crs().isValid(). A valid result means the CRS is defined and geometry operations run safely. An invalid result routes into an ordered projection-identification block that never guesses: step one checks for .prj or .xml sidecar files (createFromWkt for a corrupted one), step two inspects raw coordinate bounds, where roughly minus-180 to 180 indicates WGS84 and large values around 400k to 900k indicate a projected system. A second decision asks whether the CRS is now identified. If known, call layer.setCrs(target) only after a target.isValid() check, which relabels the layer and leaves stored geometry unchanged. If still unknown, log the layer, skip it, keep an audit trail, and wait for human review.",[57,58],"rect",{"x":59,"y":59,"width":60,"height":61,"fill":62},"0","780","650","#f6f3ea",[64,65,66,81,87],"defs",{},[67,68,76],"marker",{"id":69,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74,"markerUnits":75},"mc-arrow","0 0 10 10","9","5","7","auto","strokeWidth",[77,78],"path",{"d":79,"fill":80},"M0,0 L10,5 L0,10 z","#2f3b35",[67,82,84],{"id":83,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74,"markerUnits":75},"mc-green",[77,85],{"d":79,"fill":86},"#15803d",[67,88,90],{"id":89,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74,"markerUnits":75},"mc-amber",[77,91],{"d":79,"fill":92},"#b45309",[57,94],{"x":95,"y":96,"width":97,"height":98,"rx":99,"fill":100,"stroke":101,"style":102},"305","24","170","52","8","#fffdf7","#2563eb","stroke-width:2.5",[104,105,111],"text",{"x":106,"y":107,"style":108,"fill":109,"textAnchor":110},"390","48","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Layer loaded",[104,113,116],{"x":106,"y":114,"style":115,"fill":80,"textAnchor":110},"66","text-anchor:middle;font-size:11px;font-family:sans-serif","added to the project",[118,119],"polygon",{"points":120,"fill":100,"stroke":101,"style":102},"390,100 486,150 390,200 294,150",[104,122,125],{"x":106,"y":123,"style":124,"fill":109,"textAnchor":110},"145","text-anchor:middle;font-size:12px;font-family:monospace","layer.crs()",[104,127,129],{"x":106,"y":128,"style":124,"fill":109,"textAnchor":110},"163",".isValid() ?",[57,131],{"x":132,"y":133,"width":134,"height":135,"rx":99,"fill":100,"stroke":86,"style":102},"558","122","200","56",[104,137,141],{"x":138,"y":139,"style":140,"fill":109,"textAnchor":110},"658","146","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif","CRS is defined",[104,143,145],{"x":138,"y":144,"style":115,"fill":80,"textAnchor":110},"165","geometry ops run safely",[57,147],{"x":148,"y":149,"width":150,"height":97,"rx":151,"fill":100,"stroke":92,"style":152},"110","232","560","10","stroke-width:2;stroke-dasharray:7 5",[104,154,156],{"x":106,"y":155,"style":140,"fill":92,"textAnchor":110},"254","Work out the projection — never guess",[57,158],{"x":159,"y":160,"width":161,"height":98,"rx":99,"fill":100,"stroke":162,"style":163},"150","266","480","#0f766e","stroke-width:2",[104,165,168],{"x":106,"y":166,"style":167,"fill":109,"textAnchor":110},"288","text-anchor:middle;font-size:12.5px;font-weight:bold;font-family:sans-serif","1 · Check sidecar files (.prj \u002F .xml)",[104,170,173],{"x":106,"y":171,"style":172,"fill":80,"textAnchor":110},"306","text-anchor:middle;font-size:11px;font-family:monospace","createFromWkt(wkt) for a corrupted one",[57,175],{"x":159,"y":176,"width":161,"height":98,"rx":99,"fill":100,"stroke":162,"style":163},"330",[104,178,180],{"x":106,"y":179,"style":167,"fill":109,"textAnchor":110},"352","2 · Inspect raw coordinate bounds",[104,182,184],{"x":106,"y":183,"style":172,"fill":80,"textAnchor":110},"370","–180..180 → WGS84 · 400k..900k → projected",[118,186],{"points":187,"fill":100,"stroke":101,"style":102},"390,410 484,452 390,494 296,452",[104,189,192],{"x":106,"y":190,"style":191,"fill":109,"textAnchor":110},"456","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","CRS identified ?",[57,194],{"x":195,"y":132,"width":196,"height":197,"rx":99,"fill":198,"stroke":198,"style":163},"88","272","74","#26322d",[104,200,205],{"x":201,"y":202,"style":203,"fill":204,"textAnchor":110},"224","586","text-anchor:middle;font-size:12px;font-weight:bold;font-family:monospace","#d9f99d","layer.setCrs(target)",[104,207,209],{"x":201,"y":208,"style":115,"fill":204,"textAnchor":110},"606","after target.isValid() check",[104,211,213],{"x":201,"y":212,"style":115,"fill":204,"textAnchor":110},"623","relabels — geometry unchanged",[57,215],{"x":216,"y":132,"width":196,"height":197,"rx":99,"fill":100,"stroke":92,"style":102},"420",[104,218,220],{"x":219,"y":202,"style":167,"fill":109,"textAnchor":110},"556","Log · skip · never guess",[104,222,223],{"x":219,"y":208,"style":115,"fill":80,"textAnchor":110},"keep an audit trail",[104,225,226],{"x":219,"y":212,"style":115,"fill":80,"textAnchor":110},"wait for human review",[228,229],"line",{"x1":106,"y1":230,"x2":106,"y2":231,"stroke":80,"style":232},"76","98","stroke-width:2.5;marker-end:url(#mc-arrow)",[228,234],{"x1":235,"y1":159,"x2":219,"y2":159,"stroke":86,"style":236},"486","stroke-width:2.5;marker-end:url(#mc-green)",[104,238,242],{"x":239,"y":240,"style":241,"fill":86,"textAnchor":110},"522","140","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","valid",[228,244],{"x1":106,"y1":134,"x2":106,"y2":245,"stroke":92,"style":246},"230","stroke-width:2.5;marker-end:url(#mc-amber)",[104,248,251],{"x":249,"y":250,"style":241,"fill":92,"textAnchor":110},"430","218","invalid",[228,253],{"x1":106,"y1":254,"x2":106,"y2":255,"stroke":80,"style":232},"318","328",[228,257],{"x1":106,"y1":258,"x2":106,"y2":259,"stroke":80,"style":232},"382","408",[228,261],{"x1":262,"y1":263,"x2":264,"y2":265,"stroke":86,"style":236},"343","473","226","555",[104,267,270],{"x":268,"y":269,"style":241,"fill":86,"textAnchor":110},"256","512","known",[228,272],{"x1":273,"y1":263,"x2":274,"y2":265,"stroke":92,"style":246},"437","554",[104,276,278],{"x":277,"y":269,"style":241,"fill":92,"textAnchor":110},"520","unknown",[280,281,283],"h2",{"id":282},"prerequisites","Prerequisites",[285,286,287,302,305,312],"ul",{},[288,289,290,293,294,297,298,301],"li",{},[37,291,292],{},"QGIS 3.34 LTR"," (bundled Python 3.12) with the Python Console open (",[24,295,296],{},"Plugins > Python Console",", or ",[24,299,300],{},"Ctrl+Alt+P",").",[288,303,304],{},"One or more layers already loaded into the project — ideally at least one with a broken or missing projection to test against.",[288,306,307,308,311],{},"GDAL ≥ 3.4 and PROJ ≥ 8.2 installed, so ",[24,309,310],{},".prj"," parsing and datum lookups behave predictably.",[288,313,314,315,319],{},"A clear idea of what the ",[316,317,318],"em",{},"correct"," projection for your data should be. This recipe attaches a known CRS; it cannot invent the right one for you.",[14,321,322,323,327,328,331,332,335,336,339],{},"If you are new to the console and the PyQGIS layer model, start with ",[18,324,326],{"href":325},"\u002Fpyqgis-fundamentals-environment-setup\u002F","PyQGIS Fundamentals & Environment Setup"," and the parent ",[18,329,330],{"href":20},"Coordinate Reference Systems in PyQGIS"," guide, which explains the difference between ",[316,333,334],{},"assigning"," a CRS and ",[316,337,338],{},"transforming"," coordinates — the single most important distinction on this page.",[280,341,343],{"id":342},"detect-and-assign-a-missing-crs","Detect and Assign a Missing CRS",[14,345,346],{},"The script below iterates every loaded vector layer, flags the ones whose projection is undefined, validates a known target CRS, and assigns it without touching the stored coordinates.",[348,349,354],"pre",{"className":350,"code":351,"language":352,"meta":353,"style":353},"language-python shiki shiki-themes github-dark","from qgis.core import (\n    QgsProject, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsMessageLog,\n)\n\nTARGET_CRS = \"EPSG:4326\"   # Replace with the projection your data is actually in\nLOG_CATEGORY = \"CRS_Fixer\"\n\n\ndef fix_missing_crs():\n    layers = QgsProject.instance().mapLayers().values()\n    fixed_count = 0\n\n    for layer in layers:\n        if not isinstance(layer, QgsVectorLayer):\n            continue\n\n        if not layer.crs().isValid():\n            QgsMessageLog.logMessage(f\"Missing CRS on: {layer.name()}\", LOG_CATEGORY)\n\n            target = QgsCoordinateReferenceSystem(TARGET_CRS)\n            if target.isValid():\n                # Updates metadata only; does NOT transform coordinates\n                layer.setCrs(target)\n                fixed_count += 1\n            else:\n                QgsMessageLog.logMessage(\n                    f\"Invalid target CRS: {TARGET_CRS}\", LOG_CATEGORY\n                )\n\n    QgsMessageLog.logMessage(f\"Fixed {fixed_count} layers.\", LOG_CATEGORY)\n    return fixed_count\n\n\nfix_missing_crs()\n","python","",[24,355,356,374,380,386,393,411,422,427,432,445,457,468,473,488,503,509,514,524,555,560,575,584,590,596,608,617,623,642,648,653,680,689,694,699],{"__ignoreMap":353},[357,358,360,364,368,371],"span",{"class":228,"line":359},1,[357,361,363],{"class":362},"snl16","from",[357,365,367],{"class":366},"s95oV"," qgis.core ",[357,369,370],{"class":362},"import",[357,372,373],{"class":366}," (\n",[357,375,377],{"class":228,"line":376},2,[357,378,379],{"class":366},"    QgsProject, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsMessageLog,\n",[357,381,383],{"class":228,"line":382},3,[357,384,385],{"class":366},")\n",[357,387,389],{"class":228,"line":388},4,[357,390,392],{"emptyLinePlaceholder":391},true,"\n",[357,394,396,400,403,407],{"class":228,"line":395},5,[357,397,399],{"class":398},"sDLfK","TARGET_CRS",[357,401,402],{"class":362}," =",[357,404,406],{"class":405},"sU2Wk"," \"EPSG:4326\"",[357,408,410],{"class":409},"sjoCn","   # Replace with the projection your data is actually in\n",[357,412,414,417,419],{"class":228,"line":413},6,[357,415,416],{"class":398},"LOG_CATEGORY",[357,418,402],{"class":362},[357,420,421],{"class":405}," \"CRS_Fixer\"\n",[357,423,425],{"class":228,"line":424},7,[357,426,392],{"emptyLinePlaceholder":391},[357,428,430],{"class":228,"line":429},8,[357,431,392],{"emptyLinePlaceholder":391},[357,433,435,438,442],{"class":228,"line":434},9,[357,436,437],{"class":362},"def",[357,439,441],{"class":440},"svObZ"," fix_missing_crs",[357,443,444],{"class":366},"():\n",[357,446,448,451,454],{"class":228,"line":447},10,[357,449,450],{"class":366},"    layers ",[357,452,453],{"class":362},"=",[357,455,456],{"class":366}," QgsProject.instance().mapLayers().values()\n",[357,458,460,463,465],{"class":228,"line":459},11,[357,461,462],{"class":366},"    fixed_count ",[357,464,453],{"class":362},[357,466,467],{"class":398}," 0\n",[357,469,471],{"class":228,"line":470},12,[357,472,392],{"emptyLinePlaceholder":391},[357,474,476,479,482,485],{"class":228,"line":475},13,[357,477,478],{"class":362},"    for",[357,480,481],{"class":366}," layer ",[357,483,484],{"class":362},"in",[357,486,487],{"class":366}," layers:\n",[357,489,491,494,497,500],{"class":228,"line":490},14,[357,492,493],{"class":362},"        if",[357,495,496],{"class":362}," not",[357,498,499],{"class":398}," isinstance",[357,501,502],{"class":366},"(layer, QgsVectorLayer):\n",[357,504,506],{"class":228,"line":505},15,[357,507,508],{"class":362},"            continue\n",[357,510,512],{"class":228,"line":511},16,[357,513,392],{"emptyLinePlaceholder":391},[357,515,517,519,521],{"class":228,"line":516},17,[357,518,493],{"class":362},[357,520,496],{"class":362},[357,522,523],{"class":366}," layer.crs().isValid():\n",[357,525,527,530,533,536,539,542,545,548,551,553],{"class":228,"line":526},18,[357,528,529],{"class":366},"            QgsMessageLog.logMessage(",[357,531,532],{"class":362},"f",[357,534,535],{"class":405},"\"Missing CRS on: ",[357,537,538],{"class":398},"{",[357,540,541],{"class":366},"layer.name()",[357,543,544],{"class":398},"}",[357,546,547],{"class":405},"\"",[357,549,550],{"class":366},", ",[357,552,416],{"class":398},[357,554,385],{"class":366},[357,556,558],{"class":228,"line":557},19,[357,559,392],{"emptyLinePlaceholder":391},[357,561,563,566,568,571,573],{"class":228,"line":562},20,[357,564,565],{"class":366},"            target ",[357,567,453],{"class":362},[357,569,570],{"class":366}," QgsCoordinateReferenceSystem(",[357,572,399],{"class":398},[357,574,385],{"class":366},[357,576,578,581],{"class":228,"line":577},21,[357,579,580],{"class":362},"            if",[357,582,583],{"class":366}," target.isValid():\n",[357,585,587],{"class":228,"line":586},22,[357,588,589],{"class":409},"                # Updates metadata only; does NOT transform coordinates\n",[357,591,593],{"class":228,"line":592},23,[357,594,595],{"class":366},"                layer.setCrs(target)\n",[357,597,599,602,605],{"class":228,"line":598},24,[357,600,601],{"class":366},"                fixed_count ",[357,603,604],{"class":362},"+=",[357,606,607],{"class":398}," 1\n",[357,609,611,614],{"class":228,"line":610},25,[357,612,613],{"class":362},"            else",[357,615,616],{"class":366},":\n",[357,618,620],{"class":228,"line":619},26,[357,621,622],{"class":366},"                QgsMessageLog.logMessage(\n",[357,624,626,629,632,635,637,639],{"class":228,"line":625},27,[357,627,628],{"class":362},"                    f",[357,630,631],{"class":405},"\"Invalid target CRS: ",[357,633,634],{"class":398},"{TARGET_CRS}",[357,636,547],{"class":405},[357,638,550],{"class":366},[357,640,641],{"class":398},"LOG_CATEGORY\n",[357,643,645],{"class":228,"line":644},28,[357,646,647],{"class":366},"                )\n",[357,649,651],{"class":228,"line":650},29,[357,652,392],{"emptyLinePlaceholder":391},[357,654,656,659,661,664,666,669,671,674,676,678],{"class":228,"line":655},30,[357,657,658],{"class":366},"    QgsMessageLog.logMessage(",[357,660,532],{"class":362},[357,662,663],{"class":405},"\"Fixed ",[357,665,538],{"class":398},[357,667,668],{"class":366},"fixed_count",[357,670,544],{"class":398},[357,672,673],{"class":405}," layers.\"",[357,675,550],{"class":366},[357,677,416],{"class":398},[357,679,385],{"class":366},[357,681,683,686],{"class":228,"line":682},31,[357,684,685],{"class":362},"    return",[357,687,688],{"class":366}," fixed_count\n",[357,690,692],{"class":228,"line":691},32,[357,693,392],{"emptyLinePlaceholder":391},[357,695,697],{"class":228,"line":696},33,[357,698,392],{"emptyLinePlaceholder":391},[357,700,702],{"class":228,"line":701},34,[357,703,704],{"class":366},"fix_missing_crs()\n",[14,706,707,35,710,713,714,717,718,721,722,725,726,729,730,733],{},[37,708,709],{},"Breakdown:",[24,711,712],{},"layer.crs().isValid()"," is the reliable test for an undefined projection — it returns ",[24,715,716],{},"False"," when QGIS could not read a CRS from the file or its sidecar. Before assigning, the script builds ",[24,719,720],{},"QgsCoordinateReferenceSystem(TARGET_CRS)"," and checks ",[24,723,724],{},"target.isValid()",", so a typo like ",[24,727,728],{},"\"EPSG:43266\""," is caught instead of silently attaching a broken CRS. ",[24,731,732],{},"QgsMessageLog"," writes to the QGIS Log Messages panel, giving you an audit trail of exactly which layers were changed.",[14,735,736,737,740,741,744],{},"The critical point is what ",[24,738,739],{},"setCrs()"," does ",[316,742,743],{},"not"," do:",[285,746,747,765,771],{},[288,748,749,751,752,755,756,759,760,764],{},[24,750,34],{}," only rewrites the layer's metadata label. To physically move geometries into a new projection, use ",[24,753,754],{},"QgsCoordinateTransform"," or ",[24,757,758],{},"processing.run(\"native:reprojectlayer\", ...)"," instead — see ",[18,761,763],{"href":762},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reprojecting-raster-datasets\u002F","Batch Reprojecting Raster Datasets in PyQGIS"," for the reprojection pattern applied at scale.",[288,766,767,768,770],{},"Always validate ",[24,769,724],{}," before assignment to prevent silent failures.",[288,772,773,775,776,779],{},[24,774,739],{}," does not refresh the canvas — call ",[24,777,778],{},"layer.triggerRepaint()"," if you need the map to update.",[280,781,783],{"id":782},"fallback-strategies-for-unidentified-projections","Fallback Strategies for Unidentified Projections",[14,785,786],{},"Detection only helps once you know which CRS to assign. When source files arrive with no embedded metadata — common with legacy Shapefiles, CSV exports, or CAD conversions — you have to work out the projection before you can attach it. Apply this in order, and never skip to a guess:",[788,789,790,810,820,852],"ol",{},[288,791,792,795,796,798,799,802,803,805,806,809],{},[37,793,794],{},"Check for sidecar files."," Look for a ",[24,797,310],{}," (WKT) or ",[24,800,801],{},".xml"," file alongside the data. PyQGIS reads ",[24,804,310],{}," automatically on load; a corrupted one may need the WKT pasted in manually via ",[24,807,808],{},"QgsCoordinateReferenceSystem.createFromWkt(...)",".",[288,811,812,815,816,819],{},[37,813,814],{},"Inspect the raw bounds with GDAL."," Run ",[24,817,818],{},"ogrinfo -al -so \u003Cfile>"," to read the coordinate extents, then match those ranges to a known regional datum or grid.",[288,821,822,825,826],{},[37,823,824],{},"Force a project-level CRS with on-the-fly transformation."," When per-layer assignment is impractical, set the project CRS and let QGIS reproject for display:",[348,827,829],{"className":350,"code":828,"language":352,"meta":353,"style":353},"project = QgsProject.instance()\nproject.setCrs(QgsCoordinateReferenceSystem(\"EPSG:3857\"))\n",[24,830,831,841],{"__ignoreMap":353},[357,832,833,836,838],{"class":228,"line":359},[357,834,835],{"class":366},"project ",[357,837,453],{"class":362},[357,839,840],{"class":366}," QgsProject.instance()\n",[357,842,843,846,849],{"class":228,"line":376},[357,844,845],{"class":366},"project.setCrs(QgsCoordinateReferenceSystem(",[357,847,848],{"class":405},"\"EPSG:3857\"",[357,850,851],{"class":366},"))\n",[288,853,854,857,858,862,863,866],{},[37,855,856],{},"Skip and log anything you cannot verify."," In automated ",[18,859,861],{"href":860},"\u002Fspatial-data-processing-automation\u002F","Spatial Data Processing & Automation"," pipelines, wrap each assignment in ",[24,864,865],{},"try\u002Fexcept",", log the layer path, and skip processing until a human confirms the projection. Guessing corrupts topology and invalidates every downstream result.",[14,868,869,872,873,876,877,880,881,884,885,888,889,892,893,896,897,900,901,904],{},[37,870,871],{},"Quick heuristic:"," coordinates between ",[24,874,875],{},"-180"," and ",[24,878,879],{},"180"," paired with ",[24,882,883],{},"-90"," to ",[24,886,887],{},"90"," almost always indicate WGS84 (",[24,890,891],{},"EPSG:4326","). Large positive numbers such as ",[24,894,895],{},"400000","–",[24,898,899],{},"900000"," signal a projected system — typically a UTM zone or a national grid. Treat this only as a hint that tells you ",[316,902,903],{},"where to verify",", never as a licence to assign a projection blind.",[280,906,908],{"id":907},"read-the-coordinates-to-narrow-the-candidates","Read the coordinates to narrow the candidates",[14,910,911],{},"When no projection file exists, the coordinate values themselves are the best evidence. Their magnitude and sign rule out whole families of systems in seconds.",[14,913,914],{},[42,915,918,921,924,928,933,938,943,949,953,956,960,964,967,970,974,978,981,987],{"viewBox":916,"role":45,"ariaLabel":917,"xmlns":47},"0 0 760 250","Coordinate magnitude ranges mapped to likely CRS families: small signed values suggest degrees, six and seven digit values suggest UTM, and very large values suggest web mercator or a national grid",[49,919,920],{},"What the coordinate magnitudes tell you",[53,922,923],{},"Four magnitude bands are shown against the CRS families they suggest. Values under one hundred eighty with decimals indicate degrees and therefore a geographic system. Six-digit eastings paired with seven-digit northings indicate a UTM zone. Values in the millions with both axes large indicate web mercator. Values with no obvious pattern but a consistent offset indicate a national grid with a false origin.",[57,925],{"x":59,"y":59,"width":926,"height":927,"fill":62},"760","250",[104,929,932],{"x":930,"y":931,"style":108,"fill":109,"textAnchor":110},"380","26","The numbers narrow it down before you guess",[57,934],{"x":935,"y":936,"width":937,"height":936,"rx":99,"fill":100,"stroke":101,"style":163},"16","46","728",[104,939,942],{"x":940,"y":230,"style":941,"fill":80},"36","font-size:12px;font-family:monospace","12.4924, 41.8902",[104,944,948],{"x":945,"y":946,"style":947,"fill":101},"300","70","font-size:11.5px;font-weight:bold;font-family:sans-serif","within ±180 \u002F ±90, decimals",[104,950,952],{"x":150,"y":946,"style":951,"fill":80},"font-size:11.5px;font-family:sans-serif","→ geographic · EPSG:4326",[57,954],{"x":935,"y":955,"width":937,"height":936,"rx":99,"fill":100,"stroke":86,"style":163},"102",[104,957,959],{"x":940,"y":958,"style":941,"fill":80},"132","789038, 4641418",[104,961,963],{"x":945,"y":962,"style":947,"fill":86},"126","6 digits E, 7 digits N",[104,965,966],{"x":150,"y":962,"style":951,"fill":80},"→ a UTM zone · EPSG:326xx",[57,968],{"x":935,"y":969,"width":937,"height":936,"rx":99,"fill":100,"stroke":92,"style":163},"158",[104,971,973],{"x":940,"y":972,"style":941,"fill":80},"188","1390223, 5145112",[104,975,977],{"x":945,"y":976,"style":947,"fill":92},"182","both axes in the millions",[104,979,980],{"x":150,"y":976,"style":951,"fill":80},"→ web mercator · EPSG:3857",[57,982],{"x":935,"y":983,"width":937,"height":984,"rx":99,"fill":100,"stroke":985,"style":986},"214","30","#59645f","stroke-width:1.5",[104,988,990],{"x":940,"y":989,"style":951,"fill":80},"234","anything else with a consistent offset → a national grid with a false origin",[14,992,993],{},"Confirm a guess rather than trusting it: assign the candidate CRS, reproject a copy to EPSG:4326, and check the resulting latitude and longitude fall where the data is supposed to be. A guess that puts a Norwegian dataset in the Gulf of Guinea is wrong in a way that is immediately obvious, and that check takes ten seconds.",[280,995,997],{"id":996},"assigning-is-not-converting","Assigning is not converting",[14,999,1000],{},"The distinction is worth restating in the context of repair, because the two operations look identical in a script and one of them destroys data.",[14,1002,1003],{},[42,1004,1007,1010,1013,1016,1025,1028,1032,1035,1056,1059,1062,1066,1079],{"viewBox":1005,"role":45,"ariaLabel":1006,"xmlns":47},"0 0 760 236","Assigning a CRS relabels the metadata leaving coordinates untouched, while reprojecting recomputes every coordinate, shown side by side with the same input",[49,1008,1009],{},"setCrs relabels; reprojection recomputes",[53,1011,1012],{},"The same layer with coordinates in metres is shown twice. Assigning a CRS with setCrs changes only the label attached to the layer, and the coordinate values are identical afterwards. Reprojecting recomputes every coordinate into the new system, so the numbers change while the real-world position stays the same.",[57,1014],{"x":59,"y":59,"width":926,"height":1015,"fill":62},"236",[64,1017,1018],{},[67,1019,1022],{"id":1020,"viewBox":70,"refX":99,"refY":72,"markerWidth":73,"markerHeight":73,"orient":1021},"crsFixArrow","auto-start-reverse",[77,1023],{"d":1024,"fill":80},"M0 0 L10 5 L0 10 z",[104,1026,1027],{"x":930,"y":931,"style":108,"fill":109,"textAnchor":110},"Use assignment to fix a wrong label, never to move data",[57,1029],{"x":935,"y":98,"width":1030,"height":1031,"rx":151,"fill":100,"stroke":92,"style":102},"356","164",[104,1033,34],{"x":1034,"y":230,"style":191,"fill":92,"textAnchor":110},"194",[1036,1037,1039,1044,1048,1052],"g",{"style":1038},"font-size:11px;font-family:monospace",[104,1040,1043],{"x":1041,"y":1042,"fill":80},"44","106","before  789038, 4641418",[104,1045,1047],{"x":1041,"y":1046,"fill":985},"130","label   (none)",[104,1049,1051],{"x":1041,"y":1050,"fill":80},"168","after   789038, 4641418",[104,1053,1055],{"x":1041,"y":1054,"fill":86},"192","label   EPSG:32633",[228,1057],{"x1":134,"y1":240,"x2":134,"y2":969,"stroke":80,"style":1058},"stroke-width:2;marker-end:url(#crsFixArrow)",[57,1060],{"x":1061,"y":98,"width":1030,"height":1031,"rx":151,"fill":100,"stroke":101,"style":102},"388",[104,1063,1065],{"x":1064,"y":230,"style":191,"fill":101,"textAnchor":110},"566","native:reprojectlayer",[1036,1067,1068,1071,1073,1076],{"style":1038},[104,1069,1043],{"x":1070,"y":1042,"fill":80},"416",[104,1072,1055],{"x":1070,"y":1046,"fill":985},[104,1074,1075],{"x":1070,"y":1050,"fill":92},"after   12.4924, 41.8902",[104,1077,1078],{"x":1070,"y":1054,"fill":86},"label   EPSG:4326",[228,1080],{"x1":1081,"y1":240,"x2":1081,"y2":969,"stroke":80,"style":1058},"572",[14,1083,1084,1085,1088],{},"Assigning the ",[316,1086,1087],{},"wrong"," CRS is the worst outcome of all: the layer now claims to be something it is not, every later transform compounds the error, and nothing anywhere reports a problem. That is why the confirmation step above is not optional.",[280,1090,1092],{"id":1091},"qgis-version-compatibility","QGIS Version Compatibility",[14,1094,1095,1096,1098,1099,1101,1102,1104],{},"The code targets ",[37,1097,292],{}," (Python 3.12). The ",[24,1100,26],{}," \u002F ",[24,1103,739],{}," API shown here is stable across the current LTR line.",[1106,1107,1108,1124],"table",{},[1109,1110,1111],"thead",{},[1112,1113,1114,1118,1121],"tr",{},[1115,1116,1117],"th",{},"Component",[1115,1119,1120],{},"Minimum version",[1115,1122,1123],{},"Notes",[1125,1126,1127,1146,1156,1167,1181],"tbody",{},[1112,1128,1129,1133,1136],{},[1130,1131,1132],"td",{},"QGIS",[1130,1134,1135],{},"3.28 LTR",[1130,1137,1138,1139,1142,1143,1145],{},"Avoid the deprecated ",[24,1140,1141],{},"QgsCRSCache","; construct ",[24,1144,26],{}," directly.",[1112,1147,1148,1150,1153],{},[1130,1149,1132],{},[1130,1151,1152],{},"3.34 LTR",[1130,1154,1155],{},"Baseline for this page; API identical to 3.40 \u002F 3.44.",[1112,1157,1158,1161,1164],{},[1130,1159,1160],{},"Python",[1130,1162,1163],{},"3.9+",[1130,1165,1166],{},"Legacy Python 2 bindings are removed.",[1112,1168,1169,1172,1175],{},[1130,1170,1171],{},"GDAL",[1130,1173,1174],{},"3.4+",[1130,1176,1177,1178,1180],{},"Required for ",[24,1179,310],{}," parsing and WKT2 conversion.",[1112,1182,1183,1186,1189],{},[1130,1184,1185],{},"PROJ",[1130,1187,1188],{},"8.2+",[1130,1190,1191],{},"Handles modern datum transformations and grid shifts.",[14,1193,1194,1195,1198],{},"Standalone scripts run outside the console must initialise the application context before any layer loads, or ",[24,1196,1197],{},"crs()"," calls return meaningless results:",[348,1200,1202],{"className":350,"code":1201,"language":352,"meta":353,"style":353},"from qgis.core import QgsApplication\n\nqgs = QgsApplication([], False)\nqgs.setPrefixPath(\"\u002Fpath\u002Fto\u002Fqgis\", True)\nqgs.initQgis()\n",[24,1203,1204,1215,1219,1233,1248],{"__ignoreMap":353},[357,1205,1206,1208,1210,1212],{"class":228,"line":359},[357,1207,363],{"class":362},[357,1209,367],{"class":366},[357,1211,370],{"class":362},[357,1213,1214],{"class":366}," QgsApplication\n",[357,1216,1217],{"class":228,"line":376},[357,1218,392],{"emptyLinePlaceholder":391},[357,1220,1221,1224,1226,1229,1231],{"class":228,"line":382},[357,1222,1223],{"class":366},"qgs ",[357,1225,453],{"class":362},[357,1227,1228],{"class":366}," QgsApplication([], ",[357,1230,716],{"class":398},[357,1232,385],{"class":366},[357,1234,1235,1238,1241,1243,1246],{"class":228,"line":388},[357,1236,1237],{"class":366},"qgs.setPrefixPath(",[357,1239,1240],{"class":405},"\"\u002Fpath\u002Fto\u002Fqgis\"",[357,1242,550],{"class":366},[357,1244,1245],{"class":398},"True",[357,1247,385],{"class":366},[357,1249,1250],{"class":228,"line":395},[357,1251,1252],{"class":366},"qgs.initQgis()\n",[280,1254,1256],{"id":1255},"troubleshooting","Troubleshooting",[285,1258,1259,1280,1291,1302,1312],{},[288,1260,1261,1269,1270,1272,1273,1275,1276,1279],{},[37,1262,1263,1265,1266,1268],{},[24,1264,30],{}," returns ",[24,1267,1245],{}," but coordinates are clearly wrong."," The layer has ",[316,1271,18],{}," CRS, just not the right one — a previous ",[24,1274,739],{}," attached the wrong code. Check ",[24,1277,1278],{},"layer.crs().authid()"," against the coordinate bounds, and remember that fixing this means reprojecting the data, not re-labelling it.",[288,1281,1282,35,1285,1287,1288,1290],{},[37,1283,1284],{},"Canvas does not update after assignment.",[24,1286,739],{}," never triggers a repaint. Call ",[24,1289,778],{}," and refresh the map canvas; in headless scripts this does not matter.",[288,1292,1293,1298,1299,809],{},[37,1294,1295,1297],{},[24,1296,26],{}," will not accept an EPSG code."," The code is invalid or unknown to your PROJ build. Validate the syntax against the EPSG registry or test it on the command line with ",[24,1300,1301],{},"projinfo EPSG:XXXX",[288,1303,1304,1307,1308,1311],{},[37,1305,1306],{},"A PostGIS or GeoPackage layer shows no CRS."," Database layers inherit their projection from the backend. Query ",[24,1309,1310],{},"layer.dataProvider().crs()"," before overriding, so you do not mask a definition the database already holds.",[288,1313,1314,1317,1318,1320,1321,1323],{},[37,1315,1316],{},"Assignment appears to \"move\" the data."," It did not — ",[24,1319,739],{}," only changes the label, but relabelling a layer that had the ",[316,1322,318],{}," CRS makes the coordinates land in the wrong place. Always test on a copy of the dataset; recovery otherwise means re-importing the original source.",[280,1325,1327],{"id":1326},"conclusion","Conclusion",[14,1329,1330,1331,1333,1334,1337,1338,1340,1341,1343],{},"Handling a missing CRS well is mostly about restraint. Detect the undefined state with ",[24,1332,712],{},", validate the target with ",[24,1335,1336],{},"QgsCoordinateReferenceSystem(...).isValid()"," before you attach it, and keep firmly in mind that ",[24,1339,739],{}," labels the data rather than moving it — reprojection is a separate step with ",[24,1342,754],{},". When metadata is genuinely absent, work through sidecar files, raw bounds, and project-level fallbacks in order, and log-and-skip anything you cannot verify rather than guessing. Do that on dataset copies, with a clean audit trail in the Log Messages panel, and a folder of orphaned layers becomes analysis-ready without silently corrupting a single geometry.",[280,1345,1347],{"id":1346},"frequently-asked-questions","Frequently Asked Questions",[14,1349,1350,1353,1354,1356,1357,1359,1360,1362],{},[37,1351,1352],{},"How do I detect that a layer has no CRS in PyQGIS?","\nCheck ",[24,1355,712],{},", which returns ",[24,1358,716],{}," when QGIS could not read projection metadata from the file or its sidecar. You can also inspect ",[24,1361,1278],{},", which is empty for an undefined projection. Run this check before any geometry operation so misaligned layers never reach your analysis.",[14,1364,1365,1371,1372,1374,1375,755,1377,1379],{},[37,1366,1367,1368,1370],{},"Does ",[24,1369,739],{}," move my coordinates to the new projection?","\nNo. ",[24,1373,34],{}," only rewrites the metadata label and leaves the stored coordinates exactly as they are, so it fixes a wrong or missing definition rather than converting data. To actually reproject geometries, use ",[24,1376,754],{},[24,1378,758],{},". Assigning the wrong CRS this way will silently misplace your data.",[14,1381,1382,1388],{},[37,1383,1384,1385,1387],{},"How can I guess the right CRS when there is no ",[24,1386,310],{}," file?","\nInspect the raw coordinate ranges: values between -180 and 180 paired with -90 to 90 usually indicate WGS84 (EPSG:4326), while large positive numbers such as 400000-900000 suggest a projected UTM zone or national grid. Cross-check the extent against the known geographic area of the data. Never assign a projection on a guess for production data; verify against an authoritative source first.",[14,1390,1391,1394,1396,1397,1399],{},[37,1392,1393],{},"Why does my canvas not update after I assign a CRS in a script?",[24,1395,34],{}," does not automatically trigger a repaint, so call ",[24,1398,778],{}," and refresh the map canvas when you need the UI to reflect the change. In headless scripts this does not matter, but in plugins and console sessions the stale display can be misleading.",[14,1401,1402,1405,1406,1408],{},[37,1403,1404],{},"How should I handle missing CRS in an automated batch pipeline?","\nWrap each assignment in a ",[24,1407,865],{}," block, log the layer path and the action taken, and skip any layer whose projection cannot be verified rather than guessing. This keeps a clean audit trail and prevents a single bad layer from corrupting downstream topology. Test on copies so the original sources remain recoverable.",[280,1410,1412],{"id":1411},"related","Related",[285,1414,1415,1420,1424,1430],{},[288,1416,1417,1418],{},"Up to the parent guide: ",[18,1419,330],{"href":20},[288,1421,1422],{},[18,1423,763],{"href":762},[288,1425,1426],{},[18,1427,1429],{"href":1428},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002F","Batch Processing with PyQGIS",[288,1431,1432],{},[18,1433,861],{"href":860},[1435,1436,1437],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":353,"searchDepth":376,"depth":376,"links":1439},[1440,1441,1442,1443,1444,1445,1446,1447,1448,1449],{"id":282,"depth":376,"text":283},{"id":342,"depth":376,"text":343},{"id":782,"depth":376,"text":783},{"id":907,"depth":376,"text":908},{"id":996,"depth":376,"text":997},{"id":1091,"depth":376,"text":1092},{"id":1255,"depth":376,"text":1256},{"id":1326,"depth":376,"text":1327},{"id":1346,"depth":376,"text":1347},{"id":1411,"depth":376,"text":1412},"Fix missing or undefined CRS in PyQGIS. Detect layers without a projection, assign the correct EPSG code, and stop misaligned geometry from breaking spatial analysis.","md",{"slug":12,"type":1453,"breadcrumb":1454,"datePublished":1455,"dateModified":1456},"article","Handling Missing CRS","2026-06-20","2026-07-18","\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fhandling-missing-crs-in-pyqgis",{"title":5,"description":1450},"spatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fhandling-missing-crs-in-pyqgis\u002Findex","m_NpmfJEQpUgwv3HVTQQf12TtWPDxdXBE9EsUUAsTEM",1787823363588]