[{"data":1,"prerenderedAt":1642},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Ffix-invalid-geometries-pyqgis":3},{"id":4,"title":5,"body":6,"description":1631,"extension":1632,"meta":1633,"navigation":236,"path":1638,"seo":1639,"stem":1640,"__hash__":1641},"docs\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Ffix-invalid-geometries-pyqgis\u002Findex.md","Find and Fix Invalid Geometries in PyQGIS",{"type":7,"value":8,"toc":1618},"minimark",[9,13,17,31,166,171,188,192,203,540,572,576,582,726,743,747,754,838,854,984,988,991,1123,1134,1142,1146,1149,1244,1247,1383,1388,1392,1398,1464,1474,1478,1530,1534,1549,1553,1564,1570,1576,1587,1591,1614],[10,11,5],"h1",{"id":12},"find-and-fix-invalid-geometries-in-pyqgis",[14,15,16],"p",{},"An invalid geometry is one that breaks the rules the simple-features model assumes: a polygon whose boundary crosses itself, a ring that touches itself at a point, a hole that falls outside its shell, or a shape with duplicate consecutive vertices. QGIS will happily draw all of them. GEOS will not process them, so the failure surfaces later — as an empty buffer, a clip that returns nothing, or a terse exception naming a coordinate pair with no indication of which feature it came from.",[14,18,19,20,25,26,30],{},"This page is a focused recipe within ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002F","Geometry Operations and Spatial Predicates in PyQGIS",". It covers detecting invalidity and reporting ",[27,28,29],"em",{},"where"," it is, repairing individual geometries, repairing a whole layer, and dealing with the fact that a repair can change a geometry's type.",[14,32,33],{},[34,35,40,44,48,55,64,100,118,141],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 264","img","Four common invalidity types illustrated: a self-intersecting bowtie polygon, a ring touching itself, a hole outside its shell, and duplicate consecutive vertices","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Four ways a polygon can be invalid",[45,46,47],"desc",{},"Four panels each show one invalidity. A bowtie polygon whose boundary crosses itself, with the crossing point marked. A ring that pinches to touch itself at a single point. A polygon with a hole drawn entirely outside its outer shell. A boundary with two identical consecutive vertices marked at one corner.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","264","#f6f3ea",[56,57,63],"text",{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","QGIS draws all four of these; GEOS refuses to process them",[65,66,67,76,82,88,94],"g",{},[49,68],{"x":69,"y":70,"width":71,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},"12","44","176","10","#fffdf7","#b91c1c","stroke-width:2.5",[56,77,81],{"x":78,"y":79,"style":80,"fill":74,"textAnchor":62},"100","68","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","self-intersection",[83,84],"path",{"d":85,"fill":74,"fillOpacity":86,"stroke":74,"style":87},"M40 190 L160 100 L160 190 L40 100 Z",0.14,"stroke-width:2.5;stroke-linejoin:round",[89,90],"circle",{"cx":78,"cy":91,"r":92,"fill":93},"145","6","#b45309",[56,95,99],{"x":78,"y":96,"style":97,"fill":98,"textAnchor":62},"212","text-anchor:middle;font-size:11px;font-family:sans-serif","#2f3b35","the classic bowtie",[65,101,102,105,109,112,115],{},[49,103],{"x":104,"y":70,"width":71,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},"200",[56,106,108],{"x":107,"y":79,"style":80,"fill":74,"textAnchor":62},"288","ring self-touch",[83,110],{"d":111,"fill":74,"fillOpacity":86,"stroke":74,"style":87},"M230 190 L230 104 L288 148 L346 104 L346 190 Z",[89,113],{"cx":107,"cy":114,"r":92,"fill":93},"148",[56,116,117],{"x":107,"y":96,"style":97,"fill":98,"textAnchor":62},"pinches to a point",[65,119,120,123,127,132,138],{},[49,121],{"x":122,"y":70,"width":71,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},"388",[56,124,126],{"x":125,"y":79,"style":80,"fill":74,"textAnchor":62},"476","hole outside shell",[49,128],{"x":129,"y":130,"width":131,"height":131,"fill":74,"fillOpacity":86,"stroke":74,"style":75},"410","102","80",[49,133],{"x":134,"y":135,"width":136,"height":136,"fill":54,"stroke":93,"style":137},"508","122","40","stroke-width:2.5;stroke-dasharray:5 4",[56,139,140],{"x":125,"y":96,"style":97,"fill":98,"textAnchor":62},"interior ring is not inside",[65,142,143,147,151,154,160,163],{},[49,144],{"x":145,"y":70,"width":146,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},"576","172",[56,148,150],{"x":149,"y":79,"style":80,"fill":74,"textAnchor":62},"662","duplicate vertices",[83,152],{"d":153,"fill":74,"fillOpacity":86,"stroke":74,"style":87},"M604 188 L604 106 L718 106 L718 188 Z",[89,155],{"cx":156,"cy":157,"r":158,"fill":159,"stroke":93,"style":75},"604","106","8","none",[89,161],{"cx":156,"cy":157,"r":162,"fill":93},"4",[56,164,165],{"x":149,"y":96,"style":97,"fill":98,"textAnchor":62},"two nodes at one location",[167,168,170],"h2",{"id":169},"prerequisites","Prerequisites",[172,173,174,182,185],"ul",{},[175,176,177,181],"li",{},[178,179,180],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[175,183,184],{},"A vector layer you suspect. CAD exports, hand-digitised data and anything that has round-tripped through a shapefile are the usual candidates.",[175,186,187],{},"Write access to an output location, since the repair produces a new layer rather than editing in place.",[167,189,191],{"id":190},"detect-and-report-where","Detect and report where",[14,193,194,198,199,202],{},[195,196,197],"code",{},"isGeosValid()"," gives a boolean. ",[195,200,201],{},"validateGeometry()"," gives a list of errors, each with a message and usually a location — which is what you actually need to fix data rather than just discard it.",[204,205,210],"pre",{"className":206,"code":207,"language":208,"meta":209,"style":209},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nproblems = []\nfor feature in layer.getFeatures():\n    geometry = feature.geometry()\n    if geometry.isNull() or geometry.isEmpty():\n        problems.append((feature.id(), \"no geometry\", None))\n        continue\n    for error in geometry.validateGeometry():\n        location = error.where() if error.hasWhere() else None\n        problems.append((feature.id(), error.what(), location))\n\nfor fid, message, where in problems[:20]:\n    coords = f\" at {where.x():.2f}, {where.y():.2f}\" if where else \"\"\n    print(f\"feature {fid}: {message}{coords}\")\nprint(f\"{len(problems)} problem(s) across {layer.featureCount()} feature(s)\")\n","python","",[195,211,212,231,238,263,268,279,294,305,320,338,344,358,381,387,392,411,462,505],{"__ignoreMap":209},[213,214,217,221,225,228],"span",{"class":215,"line":216},"line",1,[213,218,220],{"class":219},"snl16","from",[213,222,224],{"class":223},"s95oV"," qgis.core ",[213,226,227],{"class":219},"import",[213,229,230],{"class":223}," QgsProject\n",[213,232,234],{"class":215,"line":233},2,[213,235,237],{"emptyLinePlaceholder":236},true,"\n",[213,239,241,244,247,250,254,257,260],{"class":215,"line":240},3,[213,242,243],{"class":223},"layer ",[213,245,246],{"class":219},"=",[213,248,249],{"class":223}," QgsProject.instance().mapLayersByName(",[213,251,253],{"class":252},"sU2Wk","\"parcels\"",[213,255,256],{"class":223},")[",[213,258,51],{"class":259},"sDLfK",[213,261,262],{"class":223},"]\n",[213,264,266],{"class":215,"line":265},4,[213,267,237],{"emptyLinePlaceholder":236},[213,269,271,274,276],{"class":215,"line":270},5,[213,272,273],{"class":223},"problems ",[213,275,246],{"class":219},[213,277,278],{"class":223}," []\n",[213,280,282,285,288,291],{"class":215,"line":281},6,[213,283,284],{"class":219},"for",[213,286,287],{"class":223}," feature ",[213,289,290],{"class":219},"in",[213,292,293],{"class":223}," layer.getFeatures():\n",[213,295,297,300,302],{"class":215,"line":296},7,[213,298,299],{"class":223},"    geometry ",[213,301,246],{"class":219},[213,303,304],{"class":223}," feature.geometry()\n",[213,306,308,311,314,317],{"class":215,"line":307},8,[213,309,310],{"class":219},"    if",[213,312,313],{"class":223}," geometry.isNull() ",[213,315,316],{"class":219},"or",[213,318,319],{"class":223}," geometry.isEmpty():\n",[213,321,323,326,329,332,335],{"class":215,"line":322},9,[213,324,325],{"class":223},"        problems.append((feature.id(), ",[213,327,328],{"class":252},"\"no geometry\"",[213,330,331],{"class":223},", ",[213,333,334],{"class":259},"None",[213,336,337],{"class":223},"))\n",[213,339,341],{"class":215,"line":340},10,[213,342,343],{"class":219},"        continue\n",[213,345,347,350,353,355],{"class":215,"line":346},11,[213,348,349],{"class":219},"    for",[213,351,352],{"class":223}," error ",[213,354,290],{"class":219},[213,356,357],{"class":223}," geometry.validateGeometry():\n",[213,359,361,364,366,369,372,375,378],{"class":215,"line":360},12,[213,362,363],{"class":223},"        location ",[213,365,246],{"class":219},[213,367,368],{"class":223}," error.where() ",[213,370,371],{"class":219},"if",[213,373,374],{"class":223}," error.hasWhere() ",[213,376,377],{"class":219},"else",[213,379,380],{"class":259}," None\n",[213,382,384],{"class":215,"line":383},13,[213,385,386],{"class":223},"        problems.append((feature.id(), error.what(), location))\n",[213,388,390],{"class":215,"line":389},14,[213,391,237],{"emptyLinePlaceholder":236},[213,393,395,397,400,402,405,408],{"class":215,"line":394},15,[213,396,284],{"class":219},[213,398,399],{"class":223}," fid, message, where ",[213,401,290],{"class":219},[213,403,404],{"class":223}," problems[:",[213,406,407],{"class":259},"20",[213,409,410],{"class":223},"]:\n",[213,412,414,417,419,422,425,428,431,434,437,439,441,444,446,448,451,454,457,459],{"class":215,"line":413},16,[213,415,416],{"class":223},"    coords ",[213,418,246],{"class":219},[213,420,421],{"class":219}," f",[213,423,424],{"class":252},"\" at ",[213,426,427],{"class":259},"{",[213,429,430],{"class":223},"where.x()",[213,432,433],{"class":219},":.2f",[213,435,436],{"class":259},"}",[213,438,331],{"class":252},[213,440,427],{"class":259},[213,442,443],{"class":223},"where.y()",[213,445,433],{"class":219},[213,447,436],{"class":259},[213,449,450],{"class":252},"\"",[213,452,453],{"class":219}," if",[213,455,456],{"class":223}," where ",[213,458,377],{"class":219},[213,460,461],{"class":252}," \"\"\n",[213,463,465,468,471,474,477,479,482,484,487,489,492,495,498,500,502],{"class":215,"line":464},17,[213,466,467],{"class":259},"    print",[213,469,470],{"class":223},"(",[213,472,473],{"class":219},"f",[213,475,476],{"class":252},"\"feature ",[213,478,427],{"class":259},[213,480,481],{"class":223},"fid",[213,483,436],{"class":259},[213,485,486],{"class":252},": ",[213,488,427],{"class":259},[213,490,491],{"class":223},"message",[213,493,494],{"class":259},"}{",[213,496,497],{"class":223},"coords",[213,499,436],{"class":259},[213,501,450],{"class":252},[213,503,504],{"class":223},")\n",[213,506,508,511,513,515,517,520,523,525,528,530,533,535,538],{"class":215,"line":507},18,[213,509,510],{"class":259},"print",[213,512,470],{"class":223},[213,514,473],{"class":219},[213,516,450],{"class":252},[213,518,519],{"class":259},"{len",[213,521,522],{"class":223},"(problems)",[213,524,436],{"class":259},[213,526,527],{"class":252}," problem(s) across ",[213,529,427],{"class":259},[213,531,532],{"class":223},"layer.featureCount()",[213,534,436],{"class":259},[213,536,537],{"class":252}," feature(s)\"",[213,539,504],{"class":223},[14,541,542,545,546,548,549,552,553,556,557,560,561,564,565,568,569,571],{},[178,543,544],{},"Breakdown:"," ",[195,547,201],{}," returns a list of ",[195,550,551],{},"QgsGeometry.Error"," objects and an empty list means valid — one geometry can have several distinct errors. ",[195,554,555],{},"hasWhere()"," guards the ",[195,558,559],{},"where()"," call, because some errors are structural and have no single coordinate. Checking ",[195,562,563],{},"isNull()","\u002F",[195,566,567],{},"isEmpty()"," separately matters: a feature with no geometry is a different problem from a malformed one, and ",[195,570,201],{}," reports nothing for it. Capping the printed output at twenty keeps a console session usable on a badly broken dataset while the full count still tells you the scale.",[167,573,575],{"id":574},"repair-one-geometry","Repair one geometry",[14,577,578,581],{},[195,579,580],{},"makeValid()"," returns a repaired copy using the GEOS validity routines. It does not modify the original.",[204,583,585],{"className":206,"code":584,"language":208,"meta":209,"style":209},"from qgis.core import QgsProject, QgsWkbTypes\n\nfeature = next(layer.getFeatures())\ngeometry = feature.geometry()\n\nif not geometry.isGeosValid():\n    repaired = geometry.makeValid()\n    print(\"valid now:\", repaired.isGeosValid())\n    print(\"type before:\", QgsWkbTypes.displayString(geometry.wkbType()))\n    print(\"type after: \", QgsWkbTypes.displayString(repaired.wkbType()))\n    print(\"area before:\", round(geometry.area(), 2))\n    print(\"area after: \", round(repaired.area(), 2))\n",[195,586,587,598,602,615,624,628,638,648,660,672,684,706],{"__ignoreMap":209},[213,588,589,591,593,595],{"class":215,"line":216},[213,590,220],{"class":219},[213,592,224],{"class":223},[213,594,227],{"class":219},[213,596,597],{"class":223}," QgsProject, QgsWkbTypes\n",[213,599,600],{"class":215,"line":233},[213,601,237],{"emptyLinePlaceholder":236},[213,603,604,607,609,612],{"class":215,"line":240},[213,605,606],{"class":223},"feature ",[213,608,246],{"class":219},[213,610,611],{"class":259}," next",[213,613,614],{"class":223},"(layer.getFeatures())\n",[213,616,617,620,622],{"class":215,"line":265},[213,618,619],{"class":223},"geometry ",[213,621,246],{"class":219},[213,623,304],{"class":223},[213,625,626],{"class":215,"line":270},[213,627,237],{"emptyLinePlaceholder":236},[213,629,630,632,635],{"class":215,"line":281},[213,631,371],{"class":219},[213,633,634],{"class":219}," not",[213,636,637],{"class":223}," geometry.isGeosValid():\n",[213,639,640,643,645],{"class":215,"line":296},[213,641,642],{"class":223},"    repaired ",[213,644,246],{"class":219},[213,646,647],{"class":223}," geometry.makeValid()\n",[213,649,650,652,654,657],{"class":215,"line":307},[213,651,467],{"class":259},[213,653,470],{"class":223},[213,655,656],{"class":252},"\"valid now:\"",[213,658,659],{"class":223},", repaired.isGeosValid())\n",[213,661,662,664,666,669],{"class":215,"line":322},[213,663,467],{"class":259},[213,665,470],{"class":223},[213,667,668],{"class":252},"\"type before:\"",[213,670,671],{"class":223},", QgsWkbTypes.displayString(geometry.wkbType()))\n",[213,673,674,676,678,681],{"class":215,"line":340},[213,675,467],{"class":259},[213,677,470],{"class":223},[213,679,680],{"class":252},"\"type after: \"",[213,682,683],{"class":223},", QgsWkbTypes.displayString(repaired.wkbType()))\n",[213,685,686,688,690,693,695,698,701,704],{"class":215,"line":346},[213,687,467],{"class":259},[213,689,470],{"class":223},[213,691,692],{"class":252},"\"area before:\"",[213,694,331],{"class":223},[213,696,697],{"class":259},"round",[213,699,700],{"class":223},"(geometry.area(), ",[213,702,703],{"class":259},"2",[213,705,337],{"class":223},[213,707,708,710,712,715,717,719,722,724],{"class":215,"line":360},[213,709,467],{"class":259},[213,711,470],{"class":223},[213,713,714],{"class":252},"\"area after: \"",[213,716,331],{"class":223},[213,718,697],{"class":259},[213,720,721],{"class":223},"(repaired.area(), ",[213,723,703],{"class":259},[213,725,337],{"class":223},[14,727,728,730,731,734,735,738,739,742],{},[178,729,544],{}," Comparing the WKB type before and after is the important part. Repairing a bowtie splits it into two triangles, so a ",[195,732,733],{},"Polygon"," becomes a ",[195,736,737],{},"MultiPolygon"," — and writing that back into a layer declared as single-part silently drops all but one part. Comparing the areas catches the other surprise: a self-intersecting polygon has no well-defined area, and the repaired version's may differ substantially from what ",[195,740,741],{},"area()"," reported before.",[167,744,746],{"id":745},"repair-a-whole-layer","Repair a whole layer",[14,748,749,750,753],{},"For a layer, ",[195,751,752],{},"native:fixgeometries"," does the iteration and writes a clean output in one call.",[204,755,757],{"className":206,"code":756,"language":208,"meta":209,"style":209},"import processing\n\nfixed = processing.run(\"native:fixgeometries\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"METHOD\": 1,          # 0 = linework, 1 = structure\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fparcels_fixed.gpkg\",\n})[\"OUTPUT\"]\n",[195,758,759,766,770,786,799,816,828],{"__ignoreMap":209},[213,760,761,763],{"class":215,"line":216},[213,762,227],{"class":219},[213,764,765],{"class":223}," processing\n",[213,767,768],{"class":215,"line":233},[213,769,237],{"emptyLinePlaceholder":236},[213,771,772,775,777,780,783],{"class":215,"line":240},[213,773,774],{"class":223},"fixed ",[213,776,246],{"class":219},[213,778,779],{"class":223}," processing.run(",[213,781,782],{"class":252},"\"native:fixgeometries\"",[213,784,785],{"class":223},", {\n",[213,787,788,791,793,796],{"class":215,"line":265},[213,789,790],{"class":252},"    \"INPUT\"",[213,792,486],{"class":223},[213,794,795],{"class":252},"\"\u002Fdata\u002Fparcels.gpkg|layername=parcels\"",[213,797,798],{"class":223},",\n",[213,800,801,804,806,809,812],{"class":215,"line":270},[213,802,803],{"class":252},"    \"METHOD\"",[213,805,486],{"class":223},[213,807,808],{"class":259},"1",[213,810,811],{"class":223},",          ",[213,813,815],{"class":814},"sjoCn","# 0 = linework, 1 = structure\n",[213,817,818,821,823,826],{"class":215,"line":281},[213,819,820],{"class":252},"    \"OUTPUT\"",[213,822,486],{"class":223},[213,824,825],{"class":252},"\"\u002Fdata\u002Foutput\u002Fparcels_fixed.gpkg\"",[213,827,798],{"class":223},[213,829,830,833,836],{"class":215,"line":296},[213,831,832],{"class":223},"})[",[213,834,835],{"class":252},"\"OUTPUT\"",[213,837,262],{"class":223},[14,839,840,545,842,845,846,849,850,853],{},[178,841,544],{},[195,843,844],{},"METHOD"," chooses the repair strategy. ",[178,847,848],{},"Linework"," (0) preserves every input vertex and rebuilds the topology around them, which keeps boundaries exactly where the surveyor put them. ",[178,851,852],{},"Structure"," (1) is the newer GEOS approach; it can move or drop vertices but produces a cleaner result on badly tangled input. Linework is the safer default for cadastral data where vertex positions are authoritative; structure is better for messy imports. The algorithm silently drops features whose geometry cannot be repaired at all, so compare feature counts afterwards.",[14,855,856],{},[34,857,860,863,866,869,883,886,892,897,900,903,907,912,916,921,924,928,933,936,939,943,946,950,954,957,960,963,967,974,977,981],{"viewBox":858,"role":37,"ariaLabel":859,"xmlns":39},"0 0 760 250","A bowtie polygon repaired two ways: linework keeps every original vertex and splits into two triangles, while structure produces a single simplified polygon",[41,861,862],{},"Linework repair versus structure repair",[45,864,865],{},"A self-intersecting bowtie polygon is shown on the left. The linework method produces two separate triangles sharing the original crossing point, keeping every input vertex, and the result is a multi-polygon. The structure method produces a single simplified polygon, which may have moved vertices but remains single-part.",[49,867],{"x":51,"y":51,"width":52,"height":868,"fill":54},"250",[870,871,872],"defs",{},[873,874,880],"marker",{"id":875,"viewBox":876,"refX":158,"refY":877,"markerWidth":878,"markerHeight":878,"orient":879},"fixMethodArrow","0 0 10 10","5","7","auto-start-reverse",[83,881],{"d":882,"fill":98},"M0 0 L10 5 L0 10 z",[56,884,885],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"The same broken input, two repair strategies",[49,887],{"x":888,"y":889,"width":890,"height":891,"rx":72,"fill":73,"stroke":74,"style":75},"16","60","192","164",[56,893,896],{"x":894,"y":895,"style":80,"fill":74,"textAnchor":62},"112","84","input",[83,898],{"d":899,"fill":74,"fillOpacity":86,"stroke":74,"style":87},"M48 194 L176 112 L176 194 L48 112 Z",[89,901],{"cx":894,"cy":902,"r":877,"fill":93},"153",[56,904,906],{"x":894,"y":905,"style":97,"fill":98,"textAnchor":62},"216","Polygon, invalid",[49,908],{"x":107,"y":909,"width":104,"height":910,"rx":72,"fill":73,"stroke":911,"style":75},"42","92","#0f766e",[56,913,915],{"x":122,"y":914,"style":80,"fill":911,"textAnchor":62},"64","METHOD 0 · linework",[83,917],{"d":918,"fill":911,"fillOpacity":919,"stroke":911,"style":920},"M312 116 L388 86 L312 86 Z",0.25,"stroke-width:2",[83,922],{"d":923,"fill":911,"fillOpacity":919,"stroke":911,"style":920},"M464 116 L388 86 L464 86 Z",[56,925,927],{"x":122,"y":926,"style":97,"fill":98,"textAnchor":62},"128","every input vertex kept",[49,929],{"x":107,"y":930,"width":104,"height":931,"rx":72,"fill":73,"stroke":932,"style":75},"150","88","#2563eb",[56,934,935],{"x":122,"y":146,"style":80,"fill":932,"textAnchor":62},"METHOD 1 · structure",[83,937],{"d":938,"fill":932,"fillOpacity":919,"stroke":932,"style":920},"M320 222 L388 186 L456 222 Z",[56,940,942],{"x":122,"y":941,"style":97,"fill":98,"textAnchor":62},"236","may move or drop vertices",[49,944],{"x":945,"y":909,"width":71,"height":910,"rx":72,"fill":73,"stroke":93,"style":75},"568",[56,947,737],{"x":948,"y":949,"style":80,"fill":93,"textAnchor":62},"656","70",[56,951,953],{"x":948,"y":952,"style":97,"fill":98,"textAnchor":62},"94","2 parts — check your",[56,955,956],{"x":948,"y":894,"style":97,"fill":98,"textAnchor":62},"output schema allows it",[49,958],{"x":945,"y":930,"width":71,"height":931,"rx":72,"fill":73,"stroke":959,"style":75},"#15803d",[56,961,733],{"x":948,"y":962,"style":80,"fill":959,"textAnchor":62},"178",[56,964,966],{"x":948,"y":965,"style":97,"fill":98,"textAnchor":62},"202","still single-part",[215,968],{"x1":969,"y1":970,"x2":971,"y2":972,"stroke":98,"style":973},"208","130","284","90","stroke-width:2;marker-end:url(#fixMethodArrow)",[215,975],{"x1":969,"y1":976,"x2":971,"y2":890,"stroke":98,"style":973},"155",[215,978],{"x1":979,"y1":931,"x2":980,"y2":931,"stroke":93,"style":973},"488","564",[215,982],{"x1":979,"y1":983,"x2":980,"y2":983,"stroke":959,"style":973},"194",[167,985,987],{"id":986},"handle-the-type-change","Handle the type change",[14,989,990],{},"Because a repair can turn a single-part geometry into a multi-part one, a pipeline that writes into a fixed schema needs an explicit decision: promote the output schema to multi-part, or split the parts into separate features.",[204,992,994],{"className":206,"code":993,"language":208,"meta":209,"style":209},"import processing\n\nfixed = processing.run(\"native:fixgeometries\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"METHOD\": 0,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nsingle = processing.run(\"native:multiparttosingleparts\", {\n    \"INPUT\": fixed,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fparcels_clean.gpkg\",\n})[\"OUTPUT\"]\n\nprint(fixed.featureCount(), \"->\", single.featureCount(), \"feature(s)\")\n",[195,995,996,1002,1006,1018,1028,1038,1049,1057,1061,1075,1082,1093,1101,1105],{"__ignoreMap":209},[213,997,998,1000],{"class":215,"line":216},[213,999,227],{"class":219},[213,1001,765],{"class":223},[213,1003,1004],{"class":215,"line":233},[213,1005,237],{"emptyLinePlaceholder":236},[213,1007,1008,1010,1012,1014,1016],{"class":215,"line":240},[213,1009,774],{"class":223},[213,1011,246],{"class":219},[213,1013,779],{"class":223},[213,1015,782],{"class":252},[213,1017,785],{"class":223},[213,1019,1020,1022,1024,1026],{"class":215,"line":265},[213,1021,790],{"class":252},[213,1023,486],{"class":223},[213,1025,795],{"class":252},[213,1027,798],{"class":223},[213,1029,1030,1032,1034,1036],{"class":215,"line":270},[213,1031,803],{"class":252},[213,1033,486],{"class":223},[213,1035,51],{"class":259},[213,1037,798],{"class":223},[213,1039,1040,1042,1044,1047],{"class":215,"line":281},[213,1041,820],{"class":252},[213,1043,486],{"class":223},[213,1045,1046],{"class":252},"\"TEMPORARY_OUTPUT\"",[213,1048,798],{"class":223},[213,1050,1051,1053,1055],{"class":215,"line":296},[213,1052,832],{"class":223},[213,1054,835],{"class":252},[213,1056,262],{"class":223},[213,1058,1059],{"class":215,"line":307},[213,1060,237],{"emptyLinePlaceholder":236},[213,1062,1063,1066,1068,1070,1073],{"class":215,"line":322},[213,1064,1065],{"class":223},"single ",[213,1067,246],{"class":219},[213,1069,779],{"class":223},[213,1071,1072],{"class":252},"\"native:multiparttosingleparts\"",[213,1074,785],{"class":223},[213,1076,1077,1079],{"class":215,"line":340},[213,1078,790],{"class":252},[213,1080,1081],{"class":223},": fixed,\n",[213,1083,1084,1086,1088,1091],{"class":215,"line":346},[213,1085,820],{"class":252},[213,1087,486],{"class":223},[213,1089,1090],{"class":252},"\"\u002Fdata\u002Foutput\u002Fparcels_clean.gpkg\"",[213,1092,798],{"class":223},[213,1094,1095,1097,1099],{"class":215,"line":360},[213,1096,832],{"class":223},[213,1098,835],{"class":252},[213,1100,262],{"class":223},[213,1102,1103],{"class":215,"line":383},[213,1104,237],{"emptyLinePlaceholder":236},[213,1106,1107,1109,1112,1115,1118,1121],{"class":215,"line":389},[213,1108,510],{"class":259},[213,1110,1111],{"class":223},"(fixed.featureCount(), ",[213,1113,1114],{"class":252},"\"->\"",[213,1116,1117],{"class":223},", single.featureCount(), ",[213,1119,1120],{"class":252},"\"feature(s)\"",[213,1122,504],{"class":223},[14,1124,1125,545,1127,1130,1131,1133],{},[178,1126,544],{},[195,1128,1129],{},"native:multiparttosingleparts"," explodes every multi-part geometry into one feature per part, copying the attributes onto each. The feature-count comparison tells you how many were split — and that number is worth logging, because two parcels now share one parcel identifier, which downstream joins will not expect. The alternative is to keep them multi-part and make sure the output format allows it; GeoPackage does, and a shapefile declared as ",[195,1132,733],{}," does not.",[14,1135,1136,1137,1141],{},"Validity repair belongs at the start of a chain, not the middle. Running it once on ingest and working from the clean copy avoids re-diagnosing the same failure in every algorithm downstream — the pattern used in ",[21,1138,1140],{"href":1139},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002F","Chaining Processing Algorithms in PyQGIS",".",[167,1143,1145],{"id":1144},"prevent-invalidity-rather-than-repair-it","Prevent invalidity rather than repair it",[14,1147,1148],{},"Most invalidity is created, not inherited. Three habits stop it appearing in the first place, which is worth far more than any repair routine.",[14,1150,1151],{},[34,1152,1155,1158,1161,1163,1170,1173,1177,1183,1188,1191,1195,1198,1200,1203,1207,1209,1212,1215,1218,1222,1226,1228,1231,1234,1239,1242],{"viewBox":1153,"role":37,"ariaLabel":1154,"xmlns":39},"0 0 760 236","Three sources of invalid geometry and the practice that prevents each: snapping while digitising, a precision model on coordinates, and validating before writing",[41,1156,1157],{},"Where invalidity comes from, and what stops it",[45,1159,1160],{},"Three paired rows. Digitising without snapping produces near-miss vertices and is prevented by enabling snapping. Excessive coordinate precision produces near-duplicate vertices and is prevented by applying a precision model. Writing unvalidated output propagates errors downstream and is prevented by validating before the write step.",[49,1162],{"x":51,"y":51,"width":52,"height":941,"fill":54},[870,1164,1165],{},[873,1166,1168],{"id":1167,"viewBox":876,"refX":158,"refY":877,"markerWidth":878,"markerHeight":878,"orient":879},"fixPrevArrow",[83,1169],{"d":882,"fill":959},[56,1171,1172],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Each source has a cheap preventative",[49,1174],{"x":888,"y":70,"width":1175,"height":1176,"rx":158,"fill":73,"stroke":74,"style":920},"316","52",[56,1178,1182],{"x":1179,"y":1180,"style":1181,"fill":74},"36","66","font-size:11.5px;font-weight:bold;font-family:sans-serif","digitising without snapping",[56,1184,1187],{"x":1179,"y":1185,"style":1186,"fill":98},"85","font-size:11px;font-family:sans-serif","near-miss vertices, slivers between neighbours",[49,1189],{"x":1190,"y":70,"width":1175,"height":1176,"rx":158,"fill":73,"stroke":959,"style":920},"428",[56,1192,1194],{"x":1193,"y":1180,"style":1181,"fill":959},"448","enable project snapping",[56,1196,1197],{"x":1193,"y":1185,"style":1186,"fill":98},"vertices land exactly on their neighbours",[49,1199],{"x":888,"y":157,"width":1175,"height":1176,"rx":158,"fill":73,"stroke":74,"style":920},[56,1201,1202],{"x":1179,"y":926,"style":1181,"fill":74},"unbounded coordinate precision",[56,1204,1206],{"x":1179,"y":1205,"style":1186,"fill":98},"147","vertices a nanometre apart, ring self-touches",[49,1208],{"x":1190,"y":157,"width":1175,"height":1176,"rx":158,"fill":73,"stroke":959,"style":920},[56,1210,1211],{"x":1193,"y":926,"style":1181,"fill":959},"snap to a precision grid",[56,1213,1214],{"x":1193,"y":1205,"style":1186,"fill":98},"native:snappointstogrid before writing",[49,1216],{"x":888,"y":1217,"width":1175,"height":1176,"rx":158,"fill":73,"stroke":74,"style":920},"168",[56,1219,1221],{"x":1179,"y":1220,"style":1181,"fill":74},"190","writing unvalidated output",[56,1223,1225],{"x":1179,"y":1224,"style":1186,"fill":98},"209","the failure surfaces three algorithms later",[49,1227],{"x":1190,"y":1217,"width":1175,"height":1176,"rx":158,"fill":73,"stroke":959,"style":920},[56,1229,1230],{"x":1193,"y":1220,"style":1181,"fill":959},"validate at the write step",[56,1232,1233],{"x":1193,"y":1224,"style":1186,"fill":98},"reject or repair before it propagates",[215,1235],{"x1":1236,"y1":949,"x2":1237,"y2":949,"stroke":959,"style":1238},"336","424","stroke-width:2;marker-end:url(#fixPrevArrow)",[215,1240],{"x1":1236,"y1":1241,"x2":1237,"y2":1241,"stroke":959,"style":1238},"132",[215,1243],{"x1":1236,"y1":983,"x2":1237,"y2":983,"stroke":959,"style":1238},[14,1245,1246],{},"The precision one is the least known and the most effective on computed geometry. Overlay operations produce coordinates at full double precision, and two vertices a fraction of a nanometre apart are enough to make a ring self-touch:",[204,1248,1250],{"className":206,"code":1249,"language":208,"meta":209,"style":209},"import processing\n\nsnapped = processing.run(\"native:snappointstogrid\", {\n    \"INPUT\": \"\u002Fdata\u002Foutput\u002Foverlay_result.gpkg\",\n    \"HSPACING\": 0.001,          # 1 mm in a metric CRS\n    \"VSPACING\": 0.001,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nclean = processing.run(\"native:fixgeometries\", {\n    \"INPUT\": snapped, \"METHOD\": 0,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Foverlay_clean.gpkg\",\n})[\"OUTPUT\"]\n",[195,1251,1252,1258,1262,1276,1287,1302,1313,1323,1331,1335,1348,1364,1375],{"__ignoreMap":209},[213,1253,1254,1256],{"class":215,"line":216},[213,1255,227],{"class":219},[213,1257,765],{"class":223},[213,1259,1260],{"class":215,"line":233},[213,1261,237],{"emptyLinePlaceholder":236},[213,1263,1264,1267,1269,1271,1274],{"class":215,"line":240},[213,1265,1266],{"class":223},"snapped ",[213,1268,246],{"class":219},[213,1270,779],{"class":223},[213,1272,1273],{"class":252},"\"native:snappointstogrid\"",[213,1275,785],{"class":223},[213,1277,1278,1280,1282,1285],{"class":215,"line":265},[213,1279,790],{"class":252},[213,1281,486],{"class":223},[213,1283,1284],{"class":252},"\"\u002Fdata\u002Foutput\u002Foverlay_result.gpkg\"",[213,1286,798],{"class":223},[213,1288,1289,1292,1294,1297,1299],{"class":215,"line":270},[213,1290,1291],{"class":252},"    \"HSPACING\"",[213,1293,486],{"class":223},[213,1295,1296],{"class":259},"0.001",[213,1298,811],{"class":223},[213,1300,1301],{"class":814},"# 1 mm in a metric CRS\n",[213,1303,1304,1307,1309,1311],{"class":215,"line":281},[213,1305,1306],{"class":252},"    \"VSPACING\"",[213,1308,486],{"class":223},[213,1310,1296],{"class":259},[213,1312,798],{"class":223},[213,1314,1315,1317,1319,1321],{"class":215,"line":296},[213,1316,820],{"class":252},[213,1318,486],{"class":223},[213,1320,1046],{"class":252},[213,1322,798],{"class":223},[213,1324,1325,1327,1329],{"class":215,"line":307},[213,1326,832],{"class":223},[213,1328,835],{"class":252},[213,1330,262],{"class":223},[213,1332,1333],{"class":215,"line":322},[213,1334,237],{"emptyLinePlaceholder":236},[213,1336,1337,1340,1342,1344,1346],{"class":215,"line":340},[213,1338,1339],{"class":223},"clean ",[213,1341,246],{"class":219},[213,1343,779],{"class":223},[213,1345,782],{"class":252},[213,1347,785],{"class":223},[213,1349,1350,1352,1355,1358,1360,1362],{"class":215,"line":346},[213,1351,790],{"class":252},[213,1353,1354],{"class":223},": snapped, ",[213,1356,1357],{"class":252},"\"METHOD\"",[213,1359,486],{"class":223},[213,1361,51],{"class":259},[213,1363,798],{"class":223},[213,1365,1366,1368,1370,1373],{"class":215,"line":360},[213,1367,820],{"class":252},[213,1369,486],{"class":223},[213,1371,1372],{"class":252},"\"\u002Fdata\u002Foutput\u002Foverlay_clean.gpkg\"",[213,1374,798],{"class":223},[213,1376,1377,1379,1381],{"class":215,"line":383},[213,1378,832],{"class":223},[213,1380,835],{"class":252},[213,1382,262],{"class":223},[14,1384,1385,1387],{},[178,1386,544],{}," A 1 mm grid is far finer than any real-world survey tolerance yet coarse enough to collapse the sub-micron differences that overlay arithmetic introduces. Snapping first and repairing second is the right order — snapping removes the near-duplicates that would otherwise make the repair harder, and often leaves nothing for the repair to do. Adding this pair to the end of an overlay chain is the single most effective way to stop invalidity accumulating through a pipeline.",[167,1389,1391],{"id":1390},"qgis-version-compatibility","QGIS version compatibility",[14,1393,1394,1395,1397],{},"The examples target ",[178,1396,180],{}," (Python 3.12).",[1399,1400,1401,1417],"table",{},[1402,1403,1404],"thead",{},[1405,1406,1407,1411,1414],"tr",{},[1408,1409,1410],"th",{},"QGIS version",[1408,1412,1413],{},"Python",[1408,1415,1416],{},"Notes",[1418,1419,1420,1437,1451],"tbody",{},[1405,1421,1422,1426,1429],{},[1423,1424,1425],"td",{},"3.28 LTR",[1423,1427,1428],{},"3.9",[1423,1430,1431,1433,1434,1436],{},[195,1432,752],{}," has no ",[195,1435,844],{}," parameter — it always uses linework.",[1405,1438,1439,1442,1445],{},[1423,1440,1441],{},"3.34 LTR",[1423,1443,1444],{},"3.12",[1423,1446,1447,1448,1450],{},"Baseline. ",[195,1449,844],{}," added, defaulting to linework.",[1405,1452,1453,1456,1458],{},[1423,1454,1455],{},"3.40 \u002F 3.44",[1423,1457,1444],{},[1423,1459,1460,1461,1463],{},"Structure method improved; ",[195,1462,580],{}," gained an explicit method argument.",[14,1465,1466,331,1468,1470,1471,1473],{},[195,1467,201],{},[195,1469,197],{}," and ",[195,1472,580],{}," are available throughout 3.x with unchanged signatures.",[167,1475,1477],{"id":1476},"troubleshooting","Troubleshooting",[172,1479,1480,1488,1494,1503,1509,1519],{},[175,1481,1482,1487],{},[178,1483,1484,1486],{},[195,1485,580],{}," returns an empty geometry."," The input has no recoverable area — a degenerate polygon of zero width, for example. Drop the feature and log its ID.",[175,1489,1490,1493],{},[178,1491,1492],{},"Feature count drops after fixing."," Some geometries could not be repaired at all and were discarded. Compare counts explicitly and re-run the detection pass on the input to identify them.",[175,1495,1496,1499,1500,1502],{},[178,1497,1498],{},"A shapefile output loses parts."," The format's declared geometry type is single-part. Write to GeoPackage, or explode with ",[195,1501,1129],{}," first.",[175,1504,1505,1508],{},[178,1506,1507],{},"Areas change after repair."," Expected for self-intersecting input, whose area was never well defined. Compare before and after and decide whether the difference is acceptable for your use.",[175,1510,1511,1514,1515,1518],{},[178,1512,1513],{},"Validation reports nothing but GEOS still fails."," The failure is in the ",[27,1516,1517],{},"other"," operand of the operation, not this one. Validate both inputs.",[175,1520,1521,545,1524,1526,1527,1529],{},[178,1522,1523],{},"Repair is slow on a large layer.",[195,1525,201],{}," is much more expensive than ",[195,1528,197],{},". Filter with the cheap boolean first and only collect detailed errors for the failures.",[167,1531,1533],{"id":1532},"conclusion","Conclusion",[14,1535,1536,1537,1539,1540,1542,1543,1545,1546,1548],{},"Detect with ",[195,1538,197],{}," for speed and ",[195,1541,201],{}," for detail, repair with ",[195,1544,580],{}," on a single geometry or ",[195,1547,752],{}," on a layer, and always check what the repair did to the geometry type and area. Doing this once on ingest is far cheaper than diagnosing the same corrupted feature from three different algorithm failures later.",[167,1550,1552],{"id":1551},"frequently-asked-questions","Frequently Asked Questions",[14,1554,1555,1558,1560,1561,1563],{},[178,1556,1557],{},"What is the difference between isGeosValid and validateGeometry?",[195,1559,197],{}," returns a boolean and is fast. ",[195,1562,201],{}," returns a list of specific errors, most of which carry a coordinate, and is considerably slower. Use the boolean to find the bad features and the detailed call only on those.",[14,1565,1566,1569],{},[178,1567,1568],{},"Which fix method should I use, linework or structure?","\nLinework preserves every input vertex and rebuilds topology around them, which suits cadastral or surveyed data where vertex positions are authoritative. Structure produces cleaner results on badly tangled input but may move or drop vertices.",[14,1571,1572,1575],{},[178,1573,1574],{},"Why did my polygon become a multi-polygon?","\nRepairing a self-intersecting shape splits it into the separate valid pieces it implied. That is the correct result, but it changes the geometry type — make sure the output format and any downstream schema accept multi-part geometry.",[14,1577,1578,1581,1583,1584,1586],{},[178,1579,1580],{},"Can I fix geometries in place instead of writing a new layer?",[195,1582,752],{}," always writes an output. To edit in place, loop the features yourself, call ",[195,1585,580],{}," on each, and write the results back through the edit buffer — checking the type change on every one.",[167,1588,1590],{"id":1589},"related","Related",[172,1592,1593,1598,1604,1610],{},[175,1594,1595,1597],{},[21,1596,24],{"href":23}," — the guide this recipe belongs to",[175,1599,1600],{},[21,1601,1603],{"href":1602},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fbuffer-geometry-pyqgis\u002F","Buffer a Geometry in PyQGIS",[175,1605,1606],{},[21,1607,1609],{"href":1608},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fsimplify-geometry-pyqgis\u002F","Simplify Geometry in PyQGIS",[175,1611,1612],{},[21,1613,1140],{"href":1139},[1615,1616,1617],"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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":209,"searchDepth":233,"depth":233,"links":1619},[1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630],{"id":169,"depth":233,"text":170},{"id":190,"depth":233,"text":191},{"id":574,"depth":233,"text":575},{"id":745,"depth":233,"text":746},{"id":986,"depth":233,"text":987},{"id":1144,"depth":233,"text":1145},{"id":1390,"depth":233,"text":1391},{"id":1476,"depth":233,"text":1477},{"id":1532,"depth":233,"text":1533},{"id":1551,"depth":233,"text":1552},{"id":1589,"depth":233,"text":1590},"Detect invalid geometries in PyQGIS with validateGeometry, repair them with makeValid or native:fixgeometries, and handle the type changes a repair can introduce.","md",{"slug":1634,"type":1635,"breadcrumb":1636,"datePublished":1637,"dateModified":1637},"fix-invalid-geometries-pyqgis","article","Fix Invalid Geometries","2026-08-01","\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Ffix-invalid-geometries-pyqgis",{"title":5,"description":1631},"spatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Ffix-invalid-geometries-pyqgis\u002Findex","0htdlp9000qQbHbgexFAab-xXcdqGHdlpcN7YXNVLjw",1785585990477]