[{"data":1,"prerenderedAt":1994},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Ferase-features-with-difference-pyqgis":3},{"id":4,"title":5,"body":6,"description":1983,"extension":1984,"meta":1985,"navigation":176,"path":1990,"seo":1991,"stem":1992,"__hash__":1993},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Ferase-features-with-difference-pyqgis\u002Findex.md","Erase Features with Difference in PyQGIS",{"type":7,"value":8,"toc":1968},"minimark",[9,13,17,31,110,115,128,132,135,320,327,331,334,403,636,641,645,652,829,842,846,849,1016,1029,1033,1036,1326,1335,1339,1342,1397,1659,1664,1668,1671,1821,1826,1830,1853,1857,1883,1887,1893,1897,1903,1909,1915,1930,1934,1964],[10,11,5],"h1",{"id":12},"erase-features-with-difference-in-pyqgis",[14,15,16],"p",{},"Many analyses start by taking things away. Developable land is parcels minus protected areas, minus flood zones, minus existing buildings. Usable habitat is forest minus a road buffer. A service area is the 10-minute catchment minus the lake in the middle. The difference operation — \"erase\" in some other software — cuts the parts of one layer that are covered by another and keeps the rest, with the input's attributes intact.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","Vector Data Manipulation",". It runs ",[27,28,29],"code",{},"native:difference",", chains several exclusions, keeps measurements honest, handles multipart results and slivers, uses symmetric difference to find change between two versions of a layer, and keeps large overlays fast.",[14,32,33],{},[34,35,40,44,48,55,64,74,83,88,93,98,102,105],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 280","img","A parcel with a protected area removed, leaving the remainder with the parcel's attributes, possibly split into several parts","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Difference keeps what is not covered",[45,46,47],"desc",{},"A parcel polygon overlapped by a protected area. Difference removes the overlapping part and returns the remainder of the parcel with the parcel's attributes; the protected area's attributes are not added. If the protected area splits the parcel, the remainder becomes a multipart polygon. Features entirely covered disappear from the output.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","280","#f6f3ea",[56,57,63],"text",{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Input minus overlay, input's attributes only",[49,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"60","70","240","150","4","#eef7f4","#0f766e","stroke-width:2",[49,75],{"x":76,"y":77,"width":78,"height":79,"rx":70,"fill":80,"fillOpacity":81,"stroke":82,"style":73},"160","50","80","190","#fdf2e2",0.6,"#b45309",[56,84,87],{"x":85,"y":69,"style":86,"fill":72,"textAnchor":62},"110","text-anchor:middle;font-size:10.5px;font-family:sans-serif","parcel",[56,89,92],{"x":90,"y":91,"style":86,"fill":82,"textAnchor":62},"200","262","protected",[56,94,97],{"x":58,"y":69,"style":95,"fill":96,"textAnchor":62},"text-anchor:middle;font-size:22.0px;font-family:sans-serif","#59645f","→",[49,99],{"x":100,"y":67,"width":101,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"460","100",[49,103],{"x":104,"y":67,"width":66,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"640",[56,106,109],{"x":107,"y":108,"style":86,"fill":72,"textAnchor":62},"580","248","remainder: multipart, parcel attributes",[111,112,114],"h2",{"id":113},"prerequisites","Prerequisites",[116,117,118,122,125],"ul",{},[119,120,121],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[119,123,124],{},"An input layer and one or more overlay layers in the same projected CRS.",[119,126,127],{},"Valid geometries; difference is an overlay and fails or misbehaves on invalid input.",[111,129,131],{"id":130},"erase-one-layer-with-another","Erase one layer with another",[14,133,134],{},"The algorithm takes the layer to cut and the layer to cut away.",[136,137,142],"pre",{"className":138,"code":139,"language":140,"meta":141,"style":141},"language-python shiki shiki-themes github-dark","import processing\nfrom qgis.core import QgsProject\n\nparcels = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nprotected = QgsProject.instance().mapLayersByName(\"protected_areas\")[0]\n\nremaining = processing.run(\"native:difference\", {\n    \"INPUT\": parcels,\n    \"OVERLAY\": protected,\n    \"OUTPUT\": \"memory:parcels_unprotected\",\n})[\"OUTPUT\"]\n\nprint(parcels.featureCount(), \"parcels →\", remaining.featureCount(), \"with unprotected land\")\nQgsProject.instance().addMapLayer(remaining)\n","python","",[27,143,144,157,171,178,203,222,227,244,253,262,277,288,293,314],{"__ignoreMap":141},[145,146,149,153],"span",{"class":147,"line":148},"line",1,[145,150,152],{"class":151},"snl16","import",[145,154,156],{"class":155},"s95oV"," processing\n",[145,158,160,163,166,168],{"class":147,"line":159},2,[145,161,162],{"class":151},"from",[145,164,165],{"class":155}," qgis.core ",[145,167,152],{"class":151},[145,169,170],{"class":155}," QgsProject\n",[145,172,174],{"class":147,"line":173},3,[145,175,177],{"emptyLinePlaceholder":176},true,"\n",[145,179,181,184,187,190,194,197,200],{"class":147,"line":180},4,[145,182,183],{"class":155},"parcels ",[145,185,186],{"class":151},"=",[145,188,189],{"class":155}," QgsProject.instance().mapLayersByName(",[145,191,193],{"class":192},"sU2Wk","\"parcels\"",[145,195,196],{"class":155},")[",[145,198,51],{"class":199},"sDLfK",[145,201,202],{"class":155},"]\n",[145,204,206,209,211,213,216,218,220],{"class":147,"line":205},5,[145,207,208],{"class":155},"protected ",[145,210,186],{"class":151},[145,212,189],{"class":155},[145,214,215],{"class":192},"\"protected_areas\"",[145,217,196],{"class":155},[145,219,51],{"class":199},[145,221,202],{"class":155},[145,223,225],{"class":147,"line":224},6,[145,226,177],{"emptyLinePlaceholder":176},[145,228,230,233,235,238,241],{"class":147,"line":229},7,[145,231,232],{"class":155},"remaining ",[145,234,186],{"class":151},[145,236,237],{"class":155}," processing.run(",[145,239,240],{"class":192},"\"native:difference\"",[145,242,243],{"class":155},", {\n",[145,245,247,250],{"class":147,"line":246},8,[145,248,249],{"class":192},"    \"INPUT\"",[145,251,252],{"class":155},": parcels,\n",[145,254,256,259],{"class":147,"line":255},9,[145,257,258],{"class":192},"    \"OVERLAY\"",[145,260,261],{"class":155},": protected,\n",[145,263,265,268,271,274],{"class":147,"line":264},10,[145,266,267],{"class":192},"    \"OUTPUT\"",[145,269,270],{"class":155},": ",[145,272,273],{"class":192},"\"memory:parcels_unprotected\"",[145,275,276],{"class":155},",\n",[145,278,280,283,286],{"class":147,"line":279},11,[145,281,282],{"class":155},"})[",[145,284,285],{"class":192},"\"OUTPUT\"",[145,287,202],{"class":155},[145,289,291],{"class":147,"line":290},12,[145,292,177],{"emptyLinePlaceholder":176},[145,294,296,299,302,305,308,311],{"class":147,"line":295},13,[145,297,298],{"class":199},"print",[145,300,301],{"class":155},"(parcels.featureCount(), ",[145,303,304],{"class":192},"\"parcels →\"",[145,306,307],{"class":155},", remaining.featureCount(), ",[145,309,310],{"class":192},"\"with unprotected land\"",[145,312,313],{"class":155},")\n",[145,315,317],{"class":147,"line":316},14,[145,318,319],{"class":155},"QgsProject.instance().addMapLayer(remaining)\n",[14,321,322,326],{},[323,324,325],"strong",{},"Breakdown:"," Each input feature has every overlapping overlay feature subtracted from it. The output keeps the input's attributes only — nothing from the overlay is added, which is the main practical difference from intersection. Parcels wholly inside a protected area vanish from the output, so the feature count can drop; parcels split by a protected corridor become multipart features. All overlay features are subtracted regardless of their attributes; filter the overlay first if only some designations should count.",[111,328,330],{"id":329},"chain-several-exclusions","Chain several exclusions",[14,332,333],{},"Typical suitability analyses subtract several constraint layers in turn. Running difference repeatedly works, but dissolving the constraints into one overlay first is simpler and often much faster.",[14,335,336],{},[34,337,340,343,346,348,351,358,364,369,373,377,381,386,391,394,397,400],{"viewBox":338,"role":37,"ariaLabel":339,"xmlns":39},"0 0 760 240","Subtracting three constraint layers one after another compared with merging them into one constraint layer and subtracting once",[41,341,342],{},"Chained or merged constraints",[45,344,345],{},"Chained: parcels minus protected areas, then minus flood zones, then minus buildings, three overlay passes with intermediate layers. Merged: protected areas, flood zones and buffered buildings merged and dissolved into one constraint layer, then a single difference. The result is the same; the merged route is easier to inspect and usually faster.",[49,347],{"x":51,"y":51,"width":52,"height":68,"fill":54},[56,349,350],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Three passes, or one",[49,352],{"x":353,"y":354,"width":355,"height":76,"rx":356,"fill":357,"stroke":96,"style":73},"24","56","340","8","#fffdf7",[56,359,363],{"x":360,"y":361,"style":362,"fill":61,"textAnchor":62},"194","87.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","chained",[56,365,368],{"x":360,"y":366,"style":86,"fill":367,"textAnchor":62},"113.78","#2f3b35","− protected",[56,370,372],{"x":360,"y":371,"style":86,"fill":367,"textAnchor":62},"139.78","− flood zones",[56,374,376],{"x":360,"y":375,"style":86,"fill":367,"textAnchor":62},"165.78","− buildings",[56,378,380],{"x":360,"y":379,"style":86,"fill":96,"textAnchor":62},"191.78","three overlays",[49,382],{"x":383,"y":354,"width":355,"height":76,"rx":356,"fill":384,"stroke":385,"style":73},"396","#e8efe6","#15803d",[56,387,390],{"x":388,"y":361,"style":362,"fill":389,"textAnchor":62},"566","#166534","merged",[56,392,393],{"x":388,"y":366,"style":86,"fill":367,"textAnchor":62},"merge + dissolve",[56,395,396],{"x":388,"y":371,"style":86,"fill":367,"textAnchor":62},"constraints → one layer",[56,398,399],{"x":388,"y":375,"style":86,"fill":367,"textAnchor":62},"− once",[56,401,402],{"x":388,"y":379,"style":86,"fill":96,"textAnchor":62},"easier to inspect",[136,404,406],{"className":138,"code":405,"language":140,"meta":141,"style":141},"constraints = [protected,\n               QgsProject.instance().mapLayersByName(\"flood_zones\")[0],\n               processing.run(\"native:buffer\", {\n                   \"INPUT\": QgsProject.instance().mapLayersByName(\"buildings\")[0],\n                   \"DISTANCE\": 10, \"SEGMENTS\": 8, \"DISSOLVE\": False,\n                   \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]]\n\nmerged = processing.run(\"native:mergevectorlayers\", {\n    \"LAYERS\": constraints, \"CRS\": parcels.crs(), \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\nmask = processing.run(\"native:dissolve\", {\n    \"INPUT\": merged, \"FIELD\": [], \"OUTPUT\": \"memory:constraints\"})[\"OUTPUT\"]\n\ndevelopable = processing.run(\"native:difference\", {\n    \"INPUT\": parcels, \"OVERLAY\": mask, \"OUTPUT\": \"memory:developable\"})[\"OUTPUT\"]\n",[27,407,408,418,433,443,460,492,509,513,527,553,567,593,597,610],{"__ignoreMap":141},[145,409,410,413,415],{"class":147,"line":148},[145,411,412],{"class":155},"constraints ",[145,414,186],{"class":151},[145,416,417],{"class":155}," [protected,\n",[145,419,420,423,426,428,430],{"class":147,"line":159},[145,421,422],{"class":155},"               QgsProject.instance().mapLayersByName(",[145,424,425],{"class":192},"\"flood_zones\"",[145,427,196],{"class":155},[145,429,51],{"class":199},[145,431,432],{"class":155},"],\n",[145,434,435,438,441],{"class":147,"line":173},[145,436,437],{"class":155},"               processing.run(",[145,439,440],{"class":192},"\"native:buffer\"",[145,442,243],{"class":155},[145,444,445,448,451,454,456,458],{"class":147,"line":180},[145,446,447],{"class":192},"                   \"INPUT\"",[145,449,450],{"class":155},": QgsProject.instance().mapLayersByName(",[145,452,453],{"class":192},"\"buildings\"",[145,455,196],{"class":155},[145,457,51],{"class":199},[145,459,432],{"class":155},[145,461,462,465,467,470,473,476,478,480,482,485,487,490],{"class":147,"line":205},[145,463,464],{"class":192},"                   \"DISTANCE\"",[145,466,270],{"class":155},[145,468,469],{"class":199},"10",[145,471,472],{"class":155},", ",[145,474,475],{"class":192},"\"SEGMENTS\"",[145,477,270],{"class":155},[145,479,356],{"class":199},[145,481,472],{"class":155},[145,483,484],{"class":192},"\"DISSOLVE\"",[145,486,270],{"class":155},[145,488,489],{"class":199},"False",[145,491,276],{"class":155},[145,493,494,497,499,502,504,506],{"class":147,"line":224},[145,495,496],{"class":192},"                   \"OUTPUT\"",[145,498,270],{"class":155},[145,500,501],{"class":192},"\"memory:\"",[145,503,282],{"class":155},[145,505,285],{"class":192},[145,507,508],{"class":155},"]]\n",[145,510,511],{"class":147,"line":229},[145,512,177],{"emptyLinePlaceholder":176},[145,514,515,518,520,522,525],{"class":147,"line":246},[145,516,517],{"class":155},"merged ",[145,519,186],{"class":151},[145,521,237],{"class":155},[145,523,524],{"class":192},"\"native:mergevectorlayers\"",[145,526,243],{"class":155},[145,528,529,532,535,538,541,543,545,547,549,551],{"class":147,"line":255},[145,530,531],{"class":192},"    \"LAYERS\"",[145,533,534],{"class":155},": constraints, ",[145,536,537],{"class":192},"\"CRS\"",[145,539,540],{"class":155},": parcels.crs(), ",[145,542,285],{"class":192},[145,544,270],{"class":155},[145,546,501],{"class":192},[145,548,282],{"class":155},[145,550,285],{"class":192},[145,552,202],{"class":155},[145,554,555,558,560,562,565],{"class":147,"line":264},[145,556,557],{"class":155},"mask ",[145,559,186],{"class":151},[145,561,237],{"class":155},[145,563,564],{"class":192},"\"native:dissolve\"",[145,566,243],{"class":155},[145,568,569,571,574,577,580,582,584,587,589,591],{"class":147,"line":279},[145,570,249],{"class":192},[145,572,573],{"class":155},": merged, ",[145,575,576],{"class":192},"\"FIELD\"",[145,578,579],{"class":155},": [], ",[145,581,285],{"class":192},[145,583,270],{"class":155},[145,585,586],{"class":192},"\"memory:constraints\"",[145,588,282],{"class":155},[145,590,285],{"class":192},[145,592,202],{"class":155},[145,594,595],{"class":147,"line":290},[145,596,177],{"emptyLinePlaceholder":176},[145,598,599,602,604,606,608],{"class":147,"line":295},[145,600,601],{"class":155},"developable ",[145,603,186],{"class":151},[145,605,237],{"class":155},[145,607,240],{"class":192},[145,609,243],{"class":155},[145,611,612,614,617,620,623,625,627,630,632,634],{"class":147,"line":316},[145,613,249],{"class":192},[145,615,616],{"class":155},": parcels, ",[145,618,619],{"class":192},"\"OVERLAY\"",[145,621,622],{"class":155},": mask, ",[145,624,285],{"class":192},[145,626,270],{"class":155},[145,628,629],{"class":192},"\"memory:developable\"",[145,631,282],{"class":155},[145,633,285],{"class":192},[145,635,202],{"class":155},[14,637,638,640],{},[323,639,325],{}," Merging brings the constraint layers into one, reprojecting to the parcels' CRS; dissolving fuses overlapping constraints so the difference subtracts each place once. The buildings are buffered first to exclude a margin around them. A single constraint layer is also a useful product in itself: styled on the map it shows exactly what was excluded and why, which is the first thing anyone reviewing a suitability result asks. Dissolving a very large constraint set can be slow; for national data, dissolve per region.",[111,642,644],{"id":643},"keep-measurements-honest","Keep measurements honest",[14,646,647,648,651],{},"Like every overlay, difference leaves stale measurement fields behind. A parcel's ",[27,649,650],{},"area_m2"," still holds the full parcel area on the remainder.",[136,653,655],{"className":138,"code":654,"language":140,"meta":141,"style":141},"measured = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": developable, \"FIELD_NAME\": \"free_m2\", \"FIELD_TYPE\": 0,\n    \"FIELD_LENGTH\": 14, \"FIELD_PRECISION\": 1,\n    \"FORMULA\": \"area($geometry)\", \"OUTPUT\": \"memory:developable_measured\"})[\"OUTPUT\"]\n\nshare = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": measured, \"FIELD_NAME\": \"free_share\", \"FIELD_TYPE\": 0,\n    \"FIELD_LENGTH\": 6, \"FIELD_PRECISION\": 3,\n    \"FORMULA\": '\"free_m2\" \u002F \"area_m2\"', \"OUTPUT\": \"memory:developable_final\"})[\"OUTPUT\"]\n",[27,656,657,671,697,719,744,748,761,785,805],{"__ignoreMap":141},[145,658,659,662,664,666,669],{"class":147,"line":148},[145,660,661],{"class":155},"measured ",[145,663,186],{"class":151},[145,665,237],{"class":155},[145,667,668],{"class":192},"\"native:fieldcalculator\"",[145,670,243],{"class":155},[145,672,673,675,678,681,683,686,688,691,693,695],{"class":147,"line":159},[145,674,249],{"class":192},[145,676,677],{"class":155},": developable, ",[145,679,680],{"class":192},"\"FIELD_NAME\"",[145,682,270],{"class":155},[145,684,685],{"class":192},"\"free_m2\"",[145,687,472],{"class":155},[145,689,690],{"class":192},"\"FIELD_TYPE\"",[145,692,270],{"class":155},[145,694,51],{"class":199},[145,696,276],{"class":155},[145,698,699,702,704,707,709,712,714,717],{"class":147,"line":173},[145,700,701],{"class":192},"    \"FIELD_LENGTH\"",[145,703,270],{"class":155},[145,705,706],{"class":199},"14",[145,708,472],{"class":155},[145,710,711],{"class":192},"\"FIELD_PRECISION\"",[145,713,270],{"class":155},[145,715,716],{"class":199},"1",[145,718,276],{"class":155},[145,720,721,724,726,729,731,733,735,738,740,742],{"class":147,"line":180},[145,722,723],{"class":192},"    \"FORMULA\"",[145,725,270],{"class":155},[145,727,728],{"class":192},"\"area($geometry)\"",[145,730,472],{"class":155},[145,732,285],{"class":192},[145,734,270],{"class":155},[145,736,737],{"class":192},"\"memory:developable_measured\"",[145,739,282],{"class":155},[145,741,285],{"class":192},[145,743,202],{"class":155},[145,745,746],{"class":147,"line":205},[145,747,177],{"emptyLinePlaceholder":176},[145,749,750,753,755,757,759],{"class":147,"line":224},[145,751,752],{"class":155},"share ",[145,754,186],{"class":151},[145,756,237],{"class":155},[145,758,668],{"class":192},[145,760,243],{"class":155},[145,762,763,765,768,770,772,775,777,779,781,783],{"class":147,"line":229},[145,764,249],{"class":192},[145,766,767],{"class":155},": measured, ",[145,769,680],{"class":192},[145,771,270],{"class":155},[145,773,774],{"class":192},"\"free_share\"",[145,776,472],{"class":155},[145,778,690],{"class":192},[145,780,270],{"class":155},[145,782,51],{"class":199},[145,784,276],{"class":155},[145,786,787,789,791,794,796,798,800,803],{"class":147,"line":246},[145,788,701],{"class":192},[145,790,270],{"class":155},[145,792,793],{"class":199},"6",[145,795,472],{"class":155},[145,797,711],{"class":192},[145,799,270],{"class":155},[145,801,802],{"class":199},"3",[145,804,276],{"class":155},[145,806,807,809,811,814,816,818,820,823,825,827],{"class":147,"line":255},[145,808,723],{"class":192},[145,810,270],{"class":155},[145,812,813],{"class":192},"'\"free_m2\" \u002F \"area_m2\"'",[145,815,472],{"class":155},[145,817,285],{"class":192},[145,819,270],{"class":155},[145,821,822],{"class":192},"\"memory:developable_final\"",[145,824,282],{"class":155},[145,826,285],{"class":192},[145,828,202],{"class":155},[14,830,831,833,834,837,838,841],{},[323,832,325],{}," Recomputing area on the remainder gives the free area per parcel; dividing by the original area — which the stale field still conveniently holds — gives the share of each parcel left after constraints. That share is often the most useful single number in the result, because it ranks parcels by how constrained they are. ",[27,835,836],{},"area($geometry)"," is planar in layer units; use ",[27,839,840],{},"$area"," for ellipsoidal measurement.",[111,843,845],{"id":844},"clean-up-slivers-and-fragments","Clean up slivers and fragments",[14,847,848],{},"Where constraint boundaries almost coincide with parcel boundaries, difference leaves thin slivers and tiny fragments that are not usable land. Removing them makes counts and maps meaningful.",[136,850,852],{"className":138,"code":851,"language":140,"meta":141,"style":141},"from qgis.core import edit, QgsGeometry\n\nMIN_PART_M2 = 200\nwith edit(share):\n    for f in share.getFeatures():\n        g = f.geometry()\n        parts = [QgsGeometry(p.clone()) for p in g.constParts()]\n        keep = [p for p in parts if p.area() >= MIN_PART_M2]\n        if not keep:\n            share.deleteFeature(f.id())\n        elif len(keep) \u003C len(parts):\n            new = QgsGeometry.collectGeometry(keep)\n            share.changeGeometry(f.id(), new)\n",[27,853,854,865,869,880,888,902,912,933,966,977,982,1001,1011],{"__ignoreMap":141},[145,855,856,858,860,862],{"class":147,"line":148},[145,857,162],{"class":151},[145,859,165],{"class":155},[145,861,152],{"class":151},[145,863,864],{"class":155}," edit, QgsGeometry\n",[145,866,867],{"class":147,"line":159},[145,868,177],{"emptyLinePlaceholder":176},[145,870,871,874,877],{"class":147,"line":173},[145,872,873],{"class":199},"MIN_PART_M2",[145,875,876],{"class":151}," =",[145,878,879],{"class":199}," 200\n",[145,881,882,885],{"class":147,"line":180},[145,883,884],{"class":151},"with",[145,886,887],{"class":155}," edit(share):\n",[145,889,890,893,896,899],{"class":147,"line":205},[145,891,892],{"class":151},"    for",[145,894,895],{"class":155}," f ",[145,897,898],{"class":151},"in",[145,900,901],{"class":155}," share.getFeatures():\n",[145,903,904,907,909],{"class":147,"line":224},[145,905,906],{"class":155},"        g ",[145,908,186],{"class":151},[145,910,911],{"class":155}," f.geometry()\n",[145,913,914,917,919,922,925,928,930],{"class":147,"line":229},[145,915,916],{"class":155},"        parts ",[145,918,186],{"class":151},[145,920,921],{"class":155}," [QgsGeometry(p.clone()) ",[145,923,924],{"class":151},"for",[145,926,927],{"class":155}," p ",[145,929,898],{"class":151},[145,931,932],{"class":155}," g.constParts()]\n",[145,934,935,938,940,943,945,947,949,952,955,958,961,964],{"class":147,"line":246},[145,936,937],{"class":155},"        keep ",[145,939,186],{"class":151},[145,941,942],{"class":155}," [p ",[145,944,924],{"class":151},[145,946,927],{"class":155},[145,948,898],{"class":151},[145,950,951],{"class":155}," parts ",[145,953,954],{"class":151},"if",[145,956,957],{"class":155}," p.area() ",[145,959,960],{"class":151},">=",[145,962,963],{"class":199}," MIN_PART_M2",[145,965,202],{"class":155},[145,967,968,971,974],{"class":147,"line":255},[145,969,970],{"class":151},"        if",[145,972,973],{"class":151}," not",[145,975,976],{"class":155}," keep:\n",[145,978,979],{"class":147,"line":264},[145,980,981],{"class":155},"            share.deleteFeature(f.id())\n",[145,983,984,987,990,993,996,998],{"class":147,"line":279},[145,985,986],{"class":151},"        elif",[145,988,989],{"class":199}," len",[145,991,992],{"class":155},"(keep) ",[145,994,995],{"class":151},"\u003C",[145,997,989],{"class":199},[145,999,1000],{"class":155},"(parts):\n",[145,1002,1003,1006,1008],{"class":147,"line":290},[145,1004,1005],{"class":155},"            new ",[145,1007,186],{"class":151},[145,1009,1010],{"class":155}," QgsGeometry.collectGeometry(keep)\n",[145,1012,1013],{"class":147,"line":295},[145,1014,1015],{"class":155},"            share.changeGeometry(f.id(), new)\n",[14,1017,1018,1020,1021,1024,1025,1028],{},[323,1019,325],{}," Working part by part removes small fragments from multipart remainders while keeping the substantial parts. Features with nothing above the threshold are deleted outright. The threshold should reflect the purpose — the minimum plot size for development, the minimum patch size for a habitat. Recompute areas afterwards. A negative-then-positive buffer (",[27,1022,1023],{},"buffer(-1)"," then ",[27,1026,1027],{},"buffer(1)",") is an alternative that removes slivers narrower than two metres regardless of area.",[111,1030,1032],{"id":1031},"report-what-was-excluded-and-why","Report what was excluded, and why",[14,1034,1035],{},"A suitability result invites the question \"why is this parcel not on the list?\" Answering it needs the exclusion broken down by constraint. Intersecting the parcels with each constraint separately, before the merged difference, gives that breakdown.",[136,1037,1039],{"className":138,"code":1038,"language":140,"meta":141,"style":141},"from collections import defaultdict\n\nnames = [\"protected areas\", \"flood zones\", \"building margin\"]\nexcluded = defaultdict(dict)\nfor name, layer in zip(names, constraints):\n    hit = processing.run(\"native:intersection\", {\n        \"INPUT\": parcels, \"OVERLAY\": layer, \"INPUT_FIELDS\": [\"parcel_id\"],\n        \"OVERLAY_FIELDS\": [], \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n    for f in hit.getFeatures():\n        excluded[f[\"parcel_id\"]][name] = excluded[f[\"parcel_id\"]].get(name, 0) + f.geometry().area()\n\nfor pid, parts in list(excluded.items())[:5]:\n    reasons = \", \".join(f\"{k} {v:,.0f} m²\" for k, v in sorted(parts.items(), key=lambda kv: -kv[1]))\n    print(pid, \"→\", reasons)\n",[27,1040,1041,1053,1057,1082,1097,1112,1126,1149,1168,1179,1210,1214,1235,1312],{"__ignoreMap":141},[145,1042,1043,1045,1048,1050],{"class":147,"line":148},[145,1044,162],{"class":151},[145,1046,1047],{"class":155}," collections ",[145,1049,152],{"class":151},[145,1051,1052],{"class":155}," defaultdict\n",[145,1054,1055],{"class":147,"line":159},[145,1056,177],{"emptyLinePlaceholder":176},[145,1058,1059,1062,1064,1067,1070,1072,1075,1077,1080],{"class":147,"line":173},[145,1060,1061],{"class":155},"names ",[145,1063,186],{"class":151},[145,1065,1066],{"class":155}," [",[145,1068,1069],{"class":192},"\"protected areas\"",[145,1071,472],{"class":155},[145,1073,1074],{"class":192},"\"flood zones\"",[145,1076,472],{"class":155},[145,1078,1079],{"class":192},"\"building margin\"",[145,1081,202],{"class":155},[145,1083,1084,1087,1089,1092,1095],{"class":147,"line":180},[145,1085,1086],{"class":155},"excluded ",[145,1088,186],{"class":151},[145,1090,1091],{"class":155}," defaultdict(",[145,1093,1094],{"class":199},"dict",[145,1096,313],{"class":155},[145,1098,1099,1101,1104,1106,1109],{"class":147,"line":205},[145,1100,924],{"class":151},[145,1102,1103],{"class":155}," name, layer ",[145,1105,898],{"class":151},[145,1107,1108],{"class":199}," zip",[145,1110,1111],{"class":155},"(names, constraints):\n",[145,1113,1114,1117,1119,1121,1124],{"class":147,"line":224},[145,1115,1116],{"class":155},"    hit ",[145,1118,186],{"class":151},[145,1120,237],{"class":155},[145,1122,1123],{"class":192},"\"native:intersection\"",[145,1125,243],{"class":155},[145,1127,1128,1131,1133,1135,1138,1141,1144,1147],{"class":147,"line":229},[145,1129,1130],{"class":192},"        \"INPUT\"",[145,1132,616],{"class":155},[145,1134,619],{"class":192},[145,1136,1137],{"class":155},": layer, ",[145,1139,1140],{"class":192},"\"INPUT_FIELDS\"",[145,1142,1143],{"class":155},": [",[145,1145,1146],{"class":192},"\"parcel_id\"",[145,1148,432],{"class":155},[145,1150,1151,1154,1156,1158,1160,1162,1164,1166],{"class":147,"line":246},[145,1152,1153],{"class":192},"        \"OVERLAY_FIELDS\"",[145,1155,579],{"class":155},[145,1157,285],{"class":192},[145,1159,270],{"class":155},[145,1161,501],{"class":192},[145,1163,282],{"class":155},[145,1165,285],{"class":192},[145,1167,202],{"class":155},[145,1169,1170,1172,1174,1176],{"class":147,"line":255},[145,1171,892],{"class":151},[145,1173,895],{"class":155},[145,1175,898],{"class":151},[145,1177,1178],{"class":155}," hit.getFeatures():\n",[145,1180,1181,1184,1186,1189,1191,1194,1196,1199,1201,1204,1207],{"class":147,"line":264},[145,1182,1183],{"class":155},"        excluded[f[",[145,1185,1146],{"class":192},[145,1187,1188],{"class":155},"]][name] ",[145,1190,186],{"class":151},[145,1192,1193],{"class":155}," excluded[f[",[145,1195,1146],{"class":192},[145,1197,1198],{"class":155},"]].get(name, ",[145,1200,51],{"class":199},[145,1202,1203],{"class":155},") ",[145,1205,1206],{"class":151},"+",[145,1208,1209],{"class":155}," f.geometry().area()\n",[145,1211,1212],{"class":147,"line":279},[145,1213,177],{"emptyLinePlaceholder":176},[145,1215,1216,1218,1221,1223,1226,1229,1232],{"class":147,"line":290},[145,1217,924],{"class":151},[145,1219,1220],{"class":155}," pid, parts ",[145,1222,898],{"class":151},[145,1224,1225],{"class":199}," list",[145,1227,1228],{"class":155},"(excluded.items())[:",[145,1230,1231],{"class":199},"5",[145,1233,1234],{"class":155},"]:\n",[145,1236,1237,1240,1242,1245,1248,1251,1254,1257,1260,1263,1266,1269,1272,1274,1277,1280,1283,1285,1288,1291,1295,1298,1301,1304,1307,1309],{"class":147,"line":295},[145,1238,1239],{"class":155},"    reasons ",[145,1241,186],{"class":151},[145,1243,1244],{"class":192}," \", \"",[145,1246,1247],{"class":155},".join(",[145,1249,1250],{"class":151},"f",[145,1252,1253],{"class":192},"\"",[145,1255,1256],{"class":199},"{",[145,1258,1259],{"class":155},"k",[145,1261,1262],{"class":199},"}",[145,1264,1265],{"class":199}," {",[145,1267,1268],{"class":155},"v",[145,1270,1271],{"class":151},":,.0f",[145,1273,1262],{"class":199},[145,1275,1276],{"class":192}," m²\"",[145,1278,1279],{"class":151}," for",[145,1281,1282],{"class":155}," k, v ",[145,1284,898],{"class":151},[145,1286,1287],{"class":199}," sorted",[145,1289,1290],{"class":155},"(parts.items(), ",[145,1292,1294],{"class":1293},"s9osk","key",[145,1296,1297],{"class":151},"=lambda",[145,1299,1300],{"class":155}," kv: ",[145,1302,1303],{"class":151},"-",[145,1305,1306],{"class":155},"kv[",[145,1308,716],{"class":199},[145,1310,1311],{"class":155},"]))\n",[145,1313,1314,1317,1320,1323],{"class":147,"line":316},[145,1315,1316],{"class":199},"    print",[145,1318,1319],{"class":155},"(pid, ",[145,1321,1322],{"class":192},"\"→\"",[145,1324,1325],{"class":155},", reasons)\n",[14,1327,1328,1330,1331,1334],{},[323,1329,325],{}," One intersection per constraint records how much of each parcel each constraint covers. Because constraints overlap — a flood zone inside a protected area — the per-constraint areas can sum to more than the excluded total; they explain the exclusion rather than partition it. Writing the reasons into a text field on the parcels, or into a separate table joined by ",[27,1332,1333],{},"parcel_id",", lets a reviewer click a parcel and see why it was ruled out. That transparency is what turns a suitability map from a black box into something people trust.",[111,1336,1338],{"id":1337},"find-change-with-symmetric-difference","Find change with symmetric difference",[14,1340,1341],{},"Symmetric difference returns the parts of either layer not covered by the other — exactly what changed between two versions of a coverage, such as last year's and this year's forest.",[14,1343,1344],{},[34,1345,1348,1351,1354,1357,1360,1363,1368,1372,1375,1379,1383,1387,1390,1394],{"viewBox":1346,"role":37,"ariaLabel":1347,"xmlns":39},"0 0 760 222","Symmetric difference of old and new forest layers returning lost areas from the old layer and gained areas from the new layer",[41,1349,1350],{},"Symmetric difference as change",[45,1352,1353],{},"Two versions of a forest layer overlap mostly. Symmetric difference returns the areas only in the old version, which were lost, and the areas only in the new version, which were gained, each with attributes from the layer it came from. The unchanged core is dropped.",[49,1355],{"x":51,"y":51,"width":52,"height":1356,"fill":54},"222",[56,1358,1359],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Only in old, or only in new",[49,1361],{"x":353,"y":354,"width":1356,"height":69,"rx":356,"fill":80,"stroke":1362,"style":73},"#b91c1c",[56,1364,1367],{"x":1365,"y":1366,"style":362,"fill":1362,"textAnchor":62},"135","121.78","only in 2025",[56,1369,1371],{"x":1365,"y":1370,"style":86,"fill":96,"textAnchor":62},"147.78","forest lost",[49,1373],{"x":1374,"y":354,"width":1356,"height":69,"rx":356,"fill":357,"stroke":96,"style":73},"269",[56,1376,1378],{"x":58,"y":1377,"style":362,"fill":61,"textAnchor":62},"108.78","in both",[56,1380,1382],{"x":58,"y":1381,"style":86,"fill":96,"textAnchor":62},"134.78","unchanged",[56,1384,1386],{"x":58,"y":1385,"style":86,"fill":96,"textAnchor":62},"160.78","dropped",[49,1388],{"x":1389,"y":354,"width":1356,"height":69,"rx":356,"fill":384,"stroke":385,"style":73},"514",[56,1391,1393],{"x":1392,"y":1366,"style":362,"fill":389,"textAnchor":62},"625","only in 2026",[56,1395,1396],{"x":1392,"y":1370,"style":86,"fill":96,"textAnchor":62},"forest gained",[136,1398,1400],{"className":138,"code":1399,"language":140,"meta":141,"style":141},"old = QgsProject.instance().mapLayersByName(\"forest_2025\")[0]\nnew = QgsProject.instance().mapLayersByName(\"forest_2026\")[0]\n\nchange = processing.run(\"native:symmetricaldifference\", {\n    \"INPUT\": old, \"OVERLAY\": new, \"OVERLAY_FIELDS_PREFIX\": \"new_\",\n    \"OUTPUT\": \"memory:forest_change\"})[\"OUTPUT\"]\n\nlost = gained = 0.0\nfor f in change.getFeatures():\n    a = f.geometry().area() \u002F 1e4\n    if f[\"new_forest_id\"] is None or (hasattr(f[\"new_forest_id\"], \"isNull\") and f[\"new_forest_id\"].isNull()):\n        lost += a\n    else:\n        gained += a\nprint(f\"lost {lost:.1f} ha, gained {gained:.1f} ha\")\n",[27,1401,1402,1420,1438,1442,1456,1478,1493,1497,1512,1523,1539,1591,1602,1610,1619],{"__ignoreMap":141},[145,1403,1404,1407,1409,1411,1414,1416,1418],{"class":147,"line":148},[145,1405,1406],{"class":155},"old ",[145,1408,186],{"class":151},[145,1410,189],{"class":155},[145,1412,1413],{"class":192},"\"forest_2025\"",[145,1415,196],{"class":155},[145,1417,51],{"class":199},[145,1419,202],{"class":155},[145,1421,1422,1425,1427,1429,1432,1434,1436],{"class":147,"line":159},[145,1423,1424],{"class":155},"new ",[145,1426,186],{"class":151},[145,1428,189],{"class":155},[145,1430,1431],{"class":192},"\"forest_2026\"",[145,1433,196],{"class":155},[145,1435,51],{"class":199},[145,1437,202],{"class":155},[145,1439,1440],{"class":147,"line":173},[145,1441,177],{"emptyLinePlaceholder":176},[145,1443,1444,1447,1449,1451,1454],{"class":147,"line":180},[145,1445,1446],{"class":155},"change ",[145,1448,186],{"class":151},[145,1450,237],{"class":155},[145,1452,1453],{"class":192},"\"native:symmetricaldifference\"",[145,1455,243],{"class":155},[145,1457,1458,1460,1463,1465,1468,1471,1473,1476],{"class":147,"line":205},[145,1459,249],{"class":192},[145,1461,1462],{"class":155},": old, ",[145,1464,619],{"class":192},[145,1466,1467],{"class":155},": new, ",[145,1469,1470],{"class":192},"\"OVERLAY_FIELDS_PREFIX\"",[145,1472,270],{"class":155},[145,1474,1475],{"class":192},"\"new_\"",[145,1477,276],{"class":155},[145,1479,1480,1482,1484,1487,1489,1491],{"class":147,"line":224},[145,1481,267],{"class":192},[145,1483,270],{"class":155},[145,1485,1486],{"class":192},"\"memory:forest_change\"",[145,1488,282],{"class":155},[145,1490,285],{"class":192},[145,1492,202],{"class":155},[145,1494,1495],{"class":147,"line":229},[145,1496,177],{"emptyLinePlaceholder":176},[145,1498,1499,1502,1504,1507,1509],{"class":147,"line":246},[145,1500,1501],{"class":155},"lost ",[145,1503,186],{"class":151},[145,1505,1506],{"class":155}," gained ",[145,1508,186],{"class":151},[145,1510,1511],{"class":199}," 0.0\n",[145,1513,1514,1516,1518,1520],{"class":147,"line":255},[145,1515,924],{"class":151},[145,1517,895],{"class":155},[145,1519,898],{"class":151},[145,1521,1522],{"class":155}," change.getFeatures():\n",[145,1524,1525,1528,1530,1533,1536],{"class":147,"line":264},[145,1526,1527],{"class":155},"    a ",[145,1529,186],{"class":151},[145,1531,1532],{"class":155}," f.geometry().area() ",[145,1534,1535],{"class":151},"\u002F",[145,1537,1538],{"class":199}," 1e4\n",[145,1540,1541,1544,1547,1550,1553,1556,1559,1562,1565,1568,1571,1573,1576,1579,1581,1584,1586,1588],{"class":147,"line":279},[145,1542,1543],{"class":151},"    if",[145,1545,1546],{"class":155}," f[",[145,1548,1549],{"class":192},"\"new_forest_id\"",[145,1551,1552],{"class":155},"] ",[145,1554,1555],{"class":151},"is",[145,1557,1558],{"class":199}," None",[145,1560,1561],{"class":151}," or",[145,1563,1564],{"class":155}," (",[145,1566,1567],{"class":199},"hasattr",[145,1569,1570],{"class":155},"(f[",[145,1572,1549],{"class":192},[145,1574,1575],{"class":155},"], ",[145,1577,1578],{"class":192},"\"isNull\"",[145,1580,1203],{"class":155},[145,1582,1583],{"class":151},"and",[145,1585,1546],{"class":155},[145,1587,1549],{"class":192},[145,1589,1590],{"class":155},"].isNull()):\n",[145,1592,1593,1596,1599],{"class":147,"line":290},[145,1594,1595],{"class":155},"        lost ",[145,1597,1598],{"class":151},"+=",[145,1600,1601],{"class":155}," a\n",[145,1603,1604,1607],{"class":147,"line":295},[145,1605,1606],{"class":151},"    else",[145,1608,1609],{"class":155},":\n",[145,1611,1612,1615,1617],{"class":147,"line":316},[145,1613,1614],{"class":155},"        gained ",[145,1616,1598],{"class":151},[145,1618,1601],{"class":155},[145,1620,1622,1624,1627,1629,1632,1634,1637,1640,1642,1645,1647,1650,1652,1654,1657],{"class":147,"line":1621},15,[145,1623,298],{"class":199},[145,1625,1626],{"class":155},"(",[145,1628,1250],{"class":151},[145,1630,1631],{"class":192},"\"lost ",[145,1633,1256],{"class":199},[145,1635,1636],{"class":155},"lost",[145,1638,1639],{"class":151},":.1f",[145,1641,1262],{"class":199},[145,1643,1644],{"class":192}," ha, gained ",[145,1646,1256],{"class":199},[145,1648,1649],{"class":155},"gained",[145,1651,1639],{"class":151},[145,1653,1262],{"class":199},[145,1655,1656],{"class":192}," ha\"",[145,1658,313],{"class":155},[14,1660,1661,1663],{},[323,1662,325],{}," Pieces from the old layer carry the old attributes with NULL in the prefixed new fields, and vice versa, which is how the code tells losses from gains. Simple differences in each direction — old minus new, new minus old — give the same result as two layers if that is easier to use. Boundary noise between versions shows up as slivers of fake change, so apply the same sliver filter before reporting change areas.",[111,1665,1667],{"id":1666},"performance-on-large-overlays","Performance on large overlays",[14,1669,1670],{},"Difference against a large, detailed overlay can be slow because each input feature is tested against many complex overlay polygons.",[136,1672,1674],{"className":138,"code":1673,"language":140,"meta":141,"style":141},"from qgis.core import QgsFeatureRequest\n\nsimple_mask = processing.run(\"native:simplifygeometries\", {\n    \"INPUT\": mask, \"METHOD\": 0, \"TOLERANCE\": 1.0, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\nsubdivided = processing.run(\"native:subdivide\", {\n    \"INPUT\": simple_mask, \"MAX_NODES\": 256, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\nfast = processing.run(\"native:difference\", {\n    \"INPUT\": parcels, \"OVERLAY\": subdivided, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n",[27,1675,1676,1687,1691,1705,1742,1756,1785,1798],{"__ignoreMap":141},[145,1677,1678,1680,1682,1684],{"class":147,"line":148},[145,1679,162],{"class":151},[145,1681,165],{"class":155},[145,1683,152],{"class":151},[145,1685,1686],{"class":155}," QgsFeatureRequest\n",[145,1688,1689],{"class":147,"line":159},[145,1690,177],{"emptyLinePlaceholder":176},[145,1692,1693,1696,1698,1700,1703],{"class":147,"line":173},[145,1694,1695],{"class":155},"simple_mask ",[145,1697,186],{"class":151},[145,1699,237],{"class":155},[145,1701,1702],{"class":192},"\"native:simplifygeometries\"",[145,1704,243],{"class":155},[145,1706,1707,1709,1711,1714,1716,1718,1720,1723,1725,1728,1730,1732,1734,1736,1738,1740],{"class":147,"line":180},[145,1708,249],{"class":192},[145,1710,622],{"class":155},[145,1712,1713],{"class":192},"\"METHOD\"",[145,1715,270],{"class":155},[145,1717,51],{"class":199},[145,1719,472],{"class":155},[145,1721,1722],{"class":192},"\"TOLERANCE\"",[145,1724,270],{"class":155},[145,1726,1727],{"class":199},"1.0",[145,1729,472],{"class":155},[145,1731,285],{"class":192},[145,1733,270],{"class":155},[145,1735,501],{"class":192},[145,1737,282],{"class":155},[145,1739,285],{"class":192},[145,1741,202],{"class":155},[145,1743,1744,1747,1749,1751,1754],{"class":147,"line":205},[145,1745,1746],{"class":155},"subdivided ",[145,1748,186],{"class":151},[145,1750,237],{"class":155},[145,1752,1753],{"class":192},"\"native:subdivide\"",[145,1755,243],{"class":155},[145,1757,1758,1760,1763,1766,1768,1771,1773,1775,1777,1779,1781,1783],{"class":147,"line":224},[145,1759,249],{"class":192},[145,1761,1762],{"class":155},": simple_mask, ",[145,1764,1765],{"class":192},"\"MAX_NODES\"",[145,1767,270],{"class":155},[145,1769,1770],{"class":199},"256",[145,1772,472],{"class":155},[145,1774,285],{"class":192},[145,1776,270],{"class":155},[145,1778,501],{"class":192},[145,1780,282],{"class":155},[145,1782,285],{"class":192},[145,1784,202],{"class":155},[145,1786,1787,1790,1792,1794,1796],{"class":147,"line":229},[145,1788,1789],{"class":155},"fast ",[145,1791,186],{"class":151},[145,1793,237],{"class":155},[145,1795,240],{"class":192},[145,1797,243],{"class":155},[145,1799,1800,1802,1804,1806,1809,1811,1813,1815,1817,1819],{"class":147,"line":246},[145,1801,249],{"class":192},[145,1803,616],{"class":155},[145,1805,619],{"class":192},[145,1807,1808],{"class":155},": subdivided, ",[145,1810,285],{"class":192},[145,1812,270],{"class":155},[145,1814,501],{"class":192},[145,1816,282],{"class":155},[145,1818,285],{"class":192},[145,1820,202],{"class":155},[14,1822,1823,1825],{},[323,1824,325],{}," Subdividing a huge dissolved polygon into pieces of at most 256 vertices lets the spatial index find only the nearby pieces for each parcel, and each geometry operation works on small shapes — often an order of magnitude faster on national datasets. Light simplification at a tolerance below the data's accuracy reduces vertex counts further without changing results meaningfully. Both steps are worth trying whenever an overlay takes minutes.",[111,1827,1829],{"id":1828},"qgis-version-compatibility","QGIS version compatibility",[14,1831,1832,472,1834,472,1837,1840,1841,1844,1845,1848,1849,1852],{},[27,1833,29],{},[27,1835,1836],{},"native:symmetricaldifference",[27,1838,1839],{},"native:mergevectorlayers"," and ",[27,1842,1843],{},"native:subdivide"," work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. Multiple overlay layers in one difference call are supported through ",[27,1846,1847],{},"native:multidifference"," from QGIS 3.20. On QGIS 4, NULL attributes arrive as ",[27,1850,1851],{},"None",".",[111,1854,1856],{"id":1855},"troubleshooting","Troubleshooting",[116,1858,1859,1865,1871,1877],{},[119,1860,1861,1864],{},[323,1862,1863],{},"Features vanished."," They were entirely covered by the overlay — expected; count them if it matters.",[119,1866,1867,1870],{},[323,1868,1869],{},"The overlay's attributes are missing."," Difference keeps only input attributes; use intersection or union to combine them.",[119,1872,1873,1876],{},[323,1874,1875],{},"Thin slivers everywhere."," Boundaries nearly coincide; filter by part area or snap the layers first.",[119,1878,1879,1882],{},[323,1880,1881],{},"The run takes very long."," Dissolve, simplify slightly and subdivide the overlay.",[111,1884,1886],{"id":1885},"conclusion","Conclusion",[14,1888,1889,1890,1892],{},"Use ",[27,1891,29],{}," to remove covered areas, merge and dissolve several constraints into one overlay before subtracting, recompute area and the remaining share, drop slivers and tiny fragments by part, use symmetric difference to measure change between versions, and subdivide large overlays for speed.",[111,1894,1896],{"id":1895},"frequently-asked-questions","Frequently Asked Questions",[14,1898,1899,1902],{},[323,1900,1901],{},"Is \"erase\" the same as difference?","\nYes. ArcGIS calls it Erase; QGIS calls it Difference.",[14,1904,1905,1908],{},[323,1906,1907],{},"Can difference be applied to lines?","\nYes — lines minus polygons gives the line parts outside the polygons, useful for road length outside protected areas.",[14,1910,1911,1914],{},[323,1912,1913],{},"Does the order of input and overlay matter?","\nVery much: A minus B is not B minus A. Symmetric difference is the order-independent version.",[14,1916,1917,1920,1921,1925,1926,1852],{},[323,1918,1919],{},"How do I keep only the parts inside instead?","\nUse ",[21,1922,1924],{"href":1923},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fintersect-two-vector-layers-pyqgis\u002F","intersection"," or ",[21,1927,1929],{"href":1928},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fclip-vector-layer-pyqgis\u002F","clip",[111,1931,1933],{"id":1932},"related","Related",[116,1935,1936,1941,1946,1952,1958],{},[119,1937,1938,1940],{},[21,1939,24],{"href":23}," — the guide this recipe belongs to",[119,1942,1943],{},[21,1944,1945],{"href":1923},"Intersect Two Vector Layers in PyQGIS",[119,1947,1948],{},[21,1949,1951],{"href":1950},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Funion-overlay-layers-pyqgis\u002F","Union Overlay of Layers in PyQGIS",[119,1953,1954],{},[21,1955,1957],{"href":1956},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fdissolve-features-by-attribute-pyqgis\u002F","Dissolve Features by Attribute in PyQGIS",[119,1959,1960],{},[21,1961,1963],{"href":1962},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fbuffer-geometry-pyqgis\u002F","Buffer Geometry in PyQGIS",[1965,1966,1967],"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":141,"searchDepth":159,"depth":159,"links":1969},[1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982],{"id":113,"depth":159,"text":114},{"id":130,"depth":159,"text":131},{"id":329,"depth":159,"text":330},{"id":643,"depth":159,"text":644},{"id":844,"depth":159,"text":845},{"id":1031,"depth":159,"text":1032},{"id":1337,"depth":159,"text":1338},{"id":1666,"depth":159,"text":1667},{"id":1828,"depth":159,"text":1829},{"id":1855,"depth":159,"text":1856},{"id":1885,"depth":159,"text":1886},{"id":1895,"depth":159,"text":1896},{"id":1932,"depth":159,"text":1933},"Remove the parts of one layer covered by another with native:difference — excluding water, protected land or built-up areas from an analysis, keeping attributes and measurements correct, handling multipart results, symmetric difference for change detection, and performance on large overlays.","md",{"slug":1986,"type":1987,"breadcrumb":1988,"datePublished":1989,"dateModified":1989},"erase-features-with-difference-pyqgis","article","Erase Features with Difference","2026-10-02","\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Ferase-features-with-difference-pyqgis",{"title":5,"description":1983},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Ferase-features-with-difference-pyqgis\u002Findex","7N_7Lfn_BSNYV4dB78sVBlxWceFf4fX92n00v9vkCpU",1790966265845]