[{"data":1,"prerenderedAt":2043},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Funion-overlay-layers-pyqgis":3},{"id":4,"title":5,"body":6,"description":2032,"extension":2033,"meta":2034,"navigation":207,"path":2039,"seo":2040,"stem":2041,"__hash__":2042},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Funion-overlay-layers-pyqgis\u002Findex.md","Union Overlay of Layers in PyQGIS",{"type":7,"value":8,"toc":2017},"minimark",[9,13,17,31,141,146,159,163,166,358,378,382,385,636,641,645,648,708,863,868,872,875,1155,1164,1168,1171,1234,1349,1354,1358,1361,1704,1709,1713,1716,1872,1885,1889,1907,1911,1940,1944,1950,1954,1960,1966,1972,1978,1982,2013],[10,11,5],"h1",{"id":12},"union-overlay-of-layers-in-pyqgis",[14,15,16],"p",{},"Intersection keeps only where two layers overlap; difference keeps only where they do not. Union keeps everything: it splits both layers along each other's boundaries and returns every resulting piece, each carrying the attributes of whichever input features cover it. The result is a single layer that partitions the combined area into zones with a consistent combination of attributes — the basis for cross-tabulations such as land use by zoning designation, or for comparing two classifications of the same area.",[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:union"," on two layers, classifies the pieces by origin, recomputes areas, builds a cross-tabulation, uses self-union to count overlaps within one layer, and handles the slivers and NULLs that union inevitably produces.",[14,32,33],{},[34,35,40,44,48,55,64,74,81,88,92,96,103,110,115,121,124,127,131,134,137],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 280","img","Union of two overlapping polygons producing pieces only in A, only in B, and in both, with attributes filled accordingly","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Union keeps every piece",[45,46,47],"desc",{},"Two overlapping polygons, A and B. Union returns three kinds of piece: parts only in A, with A's attributes and NULLs for B; parts only in B, with B's attributes and NULLs for A; and the overlap, with attributes from both. Together the pieces cover exactly the combined area of A and B with no overlaps.",[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","Everything, split by everything",[49,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"60","70","200","140","4","#eef7f4","#0f766e","stroke-width:2",[49,75],{"x":76,"y":77,"width":68,"height":69,"rx":70,"fill":78,"fillOpacity":79,"stroke":80,"style":73},"170","110","#eff3ff",0.6,"#2563eb",[49,82],{"x":76,"y":77,"width":83,"height":84,"rx":51,"fill":85,"stroke":86,"style":87},"90","100","#e8efe6","#15803d","stroke-width:2.5",[56,89,91],{"x":77,"y":69,"style":90,"fill":72,"textAnchor":62},"text-anchor:middle;font-size:10.5px;font-family:sans-serif","A only",[56,93,95],{"x":94,"y":68,"style":90,"fill":80,"textAnchor":62},"315","B only",[56,97,102],{"x":98,"y":99,"style":100,"fill":101,"textAnchor":62},"215","165","text-anchor:middle;font-size:10.5px;font-family:sans-serif;font-weight:bold","#166534","both",[49,104],{"x":105,"y":106,"width":107,"height":108,"rx":109,"fill":71,"stroke":72,"style":73},"420","52","316","56","8",[56,111,91],{"x":112,"y":113,"style":114,"fill":72,"textAnchor":62},"578","73.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold",[56,116,120],{"x":112,"y":117,"style":118,"fill":119,"textAnchor":62},"93.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","#59645f","A attributes, B fields NULL",[49,122],{"x":105,"y":123,"width":107,"height":108,"rx":109,"fill":85,"stroke":86,"style":73},"120",[56,125,102],{"x":112,"y":126,"style":114,"fill":101,"textAnchor":62},"141.6",[56,128,130],{"x":112,"y":129,"style":118,"fill":119,"textAnchor":62},"161.6","attributes from A and B",[49,132],{"x":105,"y":133,"width":107,"height":108,"rx":109,"fill":78,"stroke":80,"style":73},"188",[56,135,95],{"x":112,"y":136,"style":114,"fill":80,"textAnchor":62},"209.6",[56,138,140],{"x":112,"y":139,"style":118,"fill":119,"textAnchor":62},"229.6","B attributes, A fields NULL",[142,143,145],"h2",{"id":144},"prerequisites","Prerequisites",[147,148,149,153,156],"ul",{},[150,151,152],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[150,154,155],{},"Two polygon layers in the same projected CRS, with valid geometries.",[150,157,158],{},"Distinct field names, or a prefix to tell them apart — union combines both attribute tables.",[142,160,162],{"id":161},"run-the-union","Run the union",[14,164,165],{},"The algorithm takes an input and an overlay layer and returns pieces with fields from both.",[167,168,173],"pre",{"className":169,"code":170,"language":171,"meta":172,"style":172},"language-python shiki shiki-themes github-dark","import processing\nfrom qgis.core import QgsProject\n\nlanduse = QgsProject.instance().mapLayersByName(\"landuse_2026\")[0]\nzoning = QgsProject.instance().mapLayersByName(\"zoning_plan\")[0]\n\npieces = processing.run(\"native:union\", {\n    \"INPUT\": landuse,\n    \"OVERLAY\": zoning,\n    \"OVERLAY_FIELDS_PREFIX\": \"zone_\",\n    \"OUTPUT\": \"memory:landuse_u_zoning\",\n})[\"OUTPUT\"]\n\nprint(pieces.featureCount(), \"pieces;\", pieces.fields().names())\nQgsProject.instance().addMapLayer(pieces)\n","python","",[27,174,175,188,202,209,234,253,258,275,284,293,308,321,332,337,352],{"__ignoreMap":172},[176,177,180,184],"span",{"class":178,"line":179},"line",1,[176,181,183],{"class":182},"snl16","import",[176,185,187],{"class":186},"s95oV"," processing\n",[176,189,191,194,197,199],{"class":178,"line":190},2,[176,192,193],{"class":182},"from",[176,195,196],{"class":186}," qgis.core ",[176,198,183],{"class":182},[176,200,201],{"class":186}," QgsProject\n",[176,203,205],{"class":178,"line":204},3,[176,206,208],{"emptyLinePlaceholder":207},true,"\n",[176,210,212,215,218,221,225,228,231],{"class":178,"line":211},4,[176,213,214],{"class":186},"landuse ",[176,216,217],{"class":182},"=",[176,219,220],{"class":186}," QgsProject.instance().mapLayersByName(",[176,222,224],{"class":223},"sU2Wk","\"landuse_2026\"",[176,226,227],{"class":186},")[",[176,229,51],{"class":230},"sDLfK",[176,232,233],{"class":186},"]\n",[176,235,237,240,242,244,247,249,251],{"class":178,"line":236},5,[176,238,239],{"class":186},"zoning ",[176,241,217],{"class":182},[176,243,220],{"class":186},[176,245,246],{"class":223},"\"zoning_plan\"",[176,248,227],{"class":186},[176,250,51],{"class":230},[176,252,233],{"class":186},[176,254,256],{"class":178,"line":255},6,[176,257,208],{"emptyLinePlaceholder":207},[176,259,261,264,266,269,272],{"class":178,"line":260},7,[176,262,263],{"class":186},"pieces ",[176,265,217],{"class":182},[176,267,268],{"class":186}," processing.run(",[176,270,271],{"class":223},"\"native:union\"",[176,273,274],{"class":186},", {\n",[176,276,278,281],{"class":178,"line":277},8,[176,279,280],{"class":223},"    \"INPUT\"",[176,282,283],{"class":186},": landuse,\n",[176,285,287,290],{"class":178,"line":286},9,[176,288,289],{"class":223},"    \"OVERLAY\"",[176,291,292],{"class":186},": zoning,\n",[176,294,296,299,302,305],{"class":178,"line":295},10,[176,297,298],{"class":223},"    \"OVERLAY_FIELDS_PREFIX\"",[176,300,301],{"class":186},": ",[176,303,304],{"class":223},"\"zone_\"",[176,306,307],{"class":186},",\n",[176,309,311,314,316,319],{"class":178,"line":310},11,[176,312,313],{"class":223},"    \"OUTPUT\"",[176,315,301],{"class":186},[176,317,318],{"class":223},"\"memory:landuse_u_zoning\"",[176,320,307],{"class":186},[176,322,324,327,330],{"class":178,"line":323},12,[176,325,326],{"class":186},"})[",[176,328,329],{"class":223},"\"OUTPUT\"",[176,331,233],{"class":186},[176,333,335],{"class":178,"line":334},13,[176,336,208],{"emptyLinePlaceholder":207},[176,338,340,343,346,349],{"class":178,"line":339},14,[176,341,342],{"class":230},"print",[176,344,345],{"class":186},"(pieces.featureCount(), ",[176,347,348],{"class":223},"\"pieces;\"",[176,350,351],{"class":186},", pieces.fields().names())\n",[176,353,355],{"class":178,"line":354},15,[176,356,357],{"class":186},"QgsProject.instance().addMapLayer(pieces)\n",[14,359,360,364,365,368,369,372,373,377],{},[361,362,363],"strong",{},"Breakdown:"," The prefix renames every overlay field — ",[27,366,367],{},"designation"," becomes ",[27,370,371],{},"zone_designation"," — so fields with the same name in both layers do not collide and the origin of every column is obvious. The output covers the combined extent of both layers. Unlike intersection, there is no field selection parameter, so very wide inputs produce very wide outputs; drop columns afterwards or refactor the inputs first with ",[21,374,376],{"href":375},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Frename-and-reorder-fields-pyqgis\u002F","rename and reorder fields",".",[142,379,381],{"id":380},"classify-pieces-by-origin","Classify pieces by origin",[14,383,384],{},"Every piece came from A only, B only, or both. Recording which turns the union into something easy to style and filter.",[167,386,388],{"className":169,"code":387,"language":171,"meta":172,"style":172},"from qgis.core import QgsField, edit\nfrom qgis.PyQt.QtCore import QVariant\n\ndef is_null(v):\n    return v is None or (hasattr(v, \"isNull\") and v.isNull())\n\nprov = pieces.dataProvider()\nprov.addAttributes([QgsField(\"origin\", QVariant.String, len=6)])\npieces.updateFields()\nidx = pieces.fields().indexOf(\"origin\")\n\nchanges = {}\nfor f in pieces.getFeatures():\n    in_a = not is_null(f[\"landuse_id\"])\n    in_b = not is_null(f[\"zone_zone_id\"])\n    changes[f.id()] = {idx: \"both\" if in_a and in_b else \"A\" if in_a else \"B\"}\nprov.changeAttributeValues(changes)\n",[27,389,390,401,413,417,429,467,471,481,504,509,524,528,538,552,571,587,630],{"__ignoreMap":172},[176,391,392,394,396,398],{"class":178,"line":179},[176,393,193],{"class":182},[176,395,196],{"class":186},[176,397,183],{"class":182},[176,399,400],{"class":186}," QgsField, edit\n",[176,402,403,405,408,410],{"class":178,"line":190},[176,404,193],{"class":182},[176,406,407],{"class":186}," qgis.PyQt.QtCore ",[176,409,183],{"class":182},[176,411,412],{"class":186}," QVariant\n",[176,414,415],{"class":178,"line":204},[176,416,208],{"emptyLinePlaceholder":207},[176,418,419,422,426],{"class":178,"line":211},[176,420,421],{"class":182},"def",[176,423,425],{"class":424},"svObZ"," is_null",[176,427,428],{"class":186},"(v):\n",[176,430,431,434,437,440,443,446,449,452,455,458,461,464],{"class":178,"line":236},[176,432,433],{"class":182},"    return",[176,435,436],{"class":186}," v ",[176,438,439],{"class":182},"is",[176,441,442],{"class":230}," None",[176,444,445],{"class":182}," or",[176,447,448],{"class":186}," (",[176,450,451],{"class":230},"hasattr",[176,453,454],{"class":186},"(v, ",[176,456,457],{"class":223},"\"isNull\"",[176,459,460],{"class":186},") ",[176,462,463],{"class":182},"and",[176,465,466],{"class":186}," v.isNull())\n",[176,468,469],{"class":178,"line":255},[176,470,208],{"emptyLinePlaceholder":207},[176,472,473,476,478],{"class":178,"line":260},[176,474,475],{"class":186},"prov ",[176,477,217],{"class":182},[176,479,480],{"class":186}," pieces.dataProvider()\n",[176,482,483,486,489,492,496,498,501],{"class":178,"line":277},[176,484,485],{"class":186},"prov.addAttributes([QgsField(",[176,487,488],{"class":223},"\"origin\"",[176,490,491],{"class":186},", QVariant.String, ",[176,493,495],{"class":494},"s9osk","len",[176,497,217],{"class":182},[176,499,500],{"class":230},"6",[176,502,503],{"class":186},")])\n",[176,505,506],{"class":178,"line":286},[176,507,508],{"class":186},"pieces.updateFields()\n",[176,510,511,514,516,519,521],{"class":178,"line":295},[176,512,513],{"class":186},"idx ",[176,515,217],{"class":182},[176,517,518],{"class":186}," pieces.fields().indexOf(",[176,520,488],{"class":223},[176,522,523],{"class":186},")\n",[176,525,526],{"class":178,"line":310},[176,527,208],{"emptyLinePlaceholder":207},[176,529,530,533,535],{"class":178,"line":323},[176,531,532],{"class":186},"changes ",[176,534,217],{"class":182},[176,536,537],{"class":186}," {}\n",[176,539,540,543,546,549],{"class":178,"line":334},[176,541,542],{"class":182},"for",[176,544,545],{"class":186}," f ",[176,547,548],{"class":182},"in",[176,550,551],{"class":186}," pieces.getFeatures():\n",[176,553,554,557,559,562,565,568],{"class":178,"line":339},[176,555,556],{"class":186},"    in_a ",[176,558,217],{"class":182},[176,560,561],{"class":182}," not",[176,563,564],{"class":186}," is_null(f[",[176,566,567],{"class":223},"\"landuse_id\"",[176,569,570],{"class":186},"])\n",[176,572,573,576,578,580,582,585],{"class":178,"line":354},[176,574,575],{"class":186},"    in_b ",[176,577,217],{"class":182},[176,579,561],{"class":182},[176,581,564],{"class":186},[176,583,584],{"class":223},"\"zone_zone_id\"",[176,586,570],{"class":186},[176,588,590,593,595,598,601,604,607,609,612,615,618,620,622,624,627],{"class":178,"line":589},16,[176,591,592],{"class":186},"    changes[f.id()] ",[176,594,217],{"class":182},[176,596,597],{"class":186}," {idx: ",[176,599,600],{"class":223},"\"both\"",[176,602,603],{"class":182}," if",[176,605,606],{"class":186}," in_a ",[176,608,463],{"class":182},[176,610,611],{"class":186}," in_b ",[176,613,614],{"class":182},"else",[176,616,617],{"class":223}," \"A\"",[176,619,603],{"class":182},[176,621,606],{"class":186},[176,623,614],{"class":182},[176,625,626],{"class":223}," \"B\"",[176,628,629],{"class":186},"}\n",[176,631,633],{"class":178,"line":632},17,[176,634,635],{"class":186},"prov.changeAttributeValues(changes)\n",[14,637,638,640],{},[361,639,363],{}," A key field from each input — here each layer's own id — is non-NULL exactly when the piece is covered by that layer. Classifying on keys rather than on optional attributes avoids misclassifying pieces where an attribute is legitimately empty. Pieces covered only by the zoning plan show where the land-use survey has gaps; pieces covered only by land use show areas outside any zone. Both are often more interesting than the overlap.",[142,642,644],{"id":643},"recompute-areas","Recompute areas",[14,646,647],{},"As with every overlay, area fields inherited from the inputs describe the original features, not the pieces.",[14,649,650],{},[34,651,654,657,660,663,666,673,679,684,688,692,695,700,704],{"viewBox":652,"role":37,"ariaLabel":653,"xmlns":39},"0 0 760 206","Inherited area fields describe whole original polygons, while a recomputed piece area is the only one that sums correctly",[41,655,656],{},"Area on pieces, not on originals",[45,658,659],{},"After union, each piece inherits area fields from the land-use polygon and the zone that cover it, describing those whole polygons. A new field computed from the piece's own geometry is the only area that can be summed. Summing it over all pieces equals the area of the combined footprint.",[49,661],{"x":51,"y":51,"width":52,"height":662,"fill":54},"206",[56,664,665],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Sum only what you measured on the piece",[49,667],{"x":668,"y":66,"width":669,"height":670,"rx":109,"fill":671,"stroke":672,"style":73},"24","340","130","#fdf2e2","#b91c1c",[56,674,678],{"x":675,"y":676,"style":677,"fill":672,"textAnchor":62},"194","89.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","inherited",[56,680,683],{"x":675,"y":681,"style":90,"fill":682,"textAnchor":62},"115.78","#2f3b35","landuse_area_m2",[56,685,687],{"x":675,"y":686,"style":90,"fill":682,"textAnchor":62},"141.78","zone_area_m2",[56,689,691],{"x":675,"y":690,"style":90,"fill":119,"textAnchor":62},"167.78","whole originals",[49,693],{"x":694,"y":66,"width":669,"height":670,"rx":109,"fill":85,"stroke":86,"style":73},"396",[56,696,699],{"x":697,"y":698,"style":677,"fill":101,"textAnchor":62},"566","102.78","recomputed",[56,701,703],{"x":697,"y":702,"style":90,"fill":682,"textAnchor":62},"128.78","piece_m2 = area($geometry)",[56,705,707],{"x":697,"y":706,"style":90,"fill":119,"textAnchor":62},"154.78","sums correctly",[167,709,711],{"className":169,"code":710,"language":171,"meta":172,"style":172},"measured = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": pieces, \"FIELD_NAME\": \"piece_m2\", \"FIELD_TYPE\": 0,\n    \"FIELD_LENGTH\": 14, \"FIELD_PRECISION\": 1, \"FORMULA\": \"area($geometry)\",\n    \"OUTPUT\": \"memory:union_measured\"})[\"OUTPUT\"]\ntotal = sum(f[\"piece_m2\"] for f in measured.getFeatures())\nprint(f\"combined footprint: {total \u002F 1e6:.2f} km²\")\n",[27,712,713,727,754,786,801,828],{"__ignoreMap":172},[176,714,715,718,720,722,725],{"class":178,"line":179},[176,716,717],{"class":186},"measured ",[176,719,217],{"class":182},[176,721,268],{"class":186},[176,723,724],{"class":223},"\"native:fieldcalculator\"",[176,726,274],{"class":186},[176,728,729,731,734,737,739,742,745,748,750,752],{"class":178,"line":190},[176,730,280],{"class":223},[176,732,733],{"class":186},": pieces, ",[176,735,736],{"class":223},"\"FIELD_NAME\"",[176,738,301],{"class":186},[176,740,741],{"class":223},"\"piece_m2\"",[176,743,744],{"class":186},", ",[176,746,747],{"class":223},"\"FIELD_TYPE\"",[176,749,301],{"class":186},[176,751,51],{"class":230},[176,753,307],{"class":186},[176,755,756,759,761,764,766,769,771,774,776,779,781,784],{"class":178,"line":204},[176,757,758],{"class":223},"    \"FIELD_LENGTH\"",[176,760,301],{"class":186},[176,762,763],{"class":230},"14",[176,765,744],{"class":186},[176,767,768],{"class":223},"\"FIELD_PRECISION\"",[176,770,301],{"class":186},[176,772,773],{"class":230},"1",[176,775,744],{"class":186},[176,777,778],{"class":223},"\"FORMULA\"",[176,780,301],{"class":186},[176,782,783],{"class":223},"\"area($geometry)\"",[176,785,307],{"class":186},[176,787,788,790,792,795,797,799],{"class":178,"line":211},[176,789,313],{"class":223},[176,791,301],{"class":186},[176,793,794],{"class":223},"\"memory:union_measured\"",[176,796,326],{"class":186},[176,798,329],{"class":223},[176,800,233],{"class":186},[176,802,803,806,808,811,814,816,819,821,823,825],{"class":178,"line":236},[176,804,805],{"class":186},"total ",[176,807,217],{"class":182},[176,809,810],{"class":230}," sum",[176,812,813],{"class":186},"(f[",[176,815,741],{"class":223},[176,817,818],{"class":186},"] ",[176,820,542],{"class":182},[176,822,545],{"class":186},[176,824,548],{"class":182},[176,826,827],{"class":186}," measured.getFeatures())\n",[176,829,830,832,835,838,841,844,846,849,852,855,858,861],{"class":178,"line":255},[176,831,342],{"class":230},[176,833,834],{"class":186},"(",[176,836,837],{"class":182},"f",[176,839,840],{"class":223},"\"combined footprint: ",[176,842,843],{"class":230},"{",[176,845,805],{"class":186},[176,847,848],{"class":182},"\u002F",[176,850,851],{"class":230}," 1e6",[176,853,854],{"class":182},":.2f",[176,856,857],{"class":230},"}",[176,859,860],{"class":223}," km²\"",[176,862,523],{"class":186},[14,864,865,867],{},[361,866,363],{}," The sum of piece areas is the area of the combined footprint of both layers, with every location counted once — a useful check, since it should equal the area of a dissolve of both layers together. Inherited area fields are harmless as long as nobody sums them; dropping them avoids temptation.",[142,869,871],{"id":870},"cross-tabulate-the-result","Cross-tabulate the result",[14,873,874],{},"The classic use of a union is a matrix: how much of each land-use class lies in each zoning designation, including land in no zone and zones with no surveyed land use.",[167,876,878],{"className":169,"code":877,"language":171,"meta":172,"style":172},"from collections import defaultdict\n\nmatrix = defaultdict(float)\nfor f in measured.getFeatures():\n    lu = f[\"landuse_class\"] if not is_null(f[\"landuse_class\"]) else \"(no survey)\"\n    zn = f[\"zone_designation\"] if not is_null(f[\"zone_designation\"]) else \"(no zone)\"\n    matrix[(lu, zn)] += f[\"piece_m2\"] \u002F 1e4\n\nzones = sorted({z for _, z in matrix})\nprint(\"land use\".ljust(18) + \"\".join(z[:12].rjust(13) for z in zones))\nfor lu in sorted({l for l, _ in matrix}):\n    print(lu[:18].ljust(18) + \"\".join(f\"{matrix.get((lu, z), 0):13.1f}\" for z in zones))\n",[27,879,880,892,896,911,922,954,983,1002,1006,1029,1076,1100],{"__ignoreMap":172},[176,881,882,884,887,889],{"class":178,"line":179},[176,883,193],{"class":182},[176,885,886],{"class":186}," collections ",[176,888,183],{"class":182},[176,890,891],{"class":186}," defaultdict\n",[176,893,894],{"class":178,"line":190},[176,895,208],{"emptyLinePlaceholder":207},[176,897,898,901,903,906,909],{"class":178,"line":204},[176,899,900],{"class":186},"matrix ",[176,902,217],{"class":182},[176,904,905],{"class":186}," defaultdict(",[176,907,908],{"class":230},"float",[176,910,523],{"class":186},[176,912,913,915,917,919],{"class":178,"line":211},[176,914,542],{"class":182},[176,916,545],{"class":186},[176,918,548],{"class":182},[176,920,921],{"class":186}," measured.getFeatures():\n",[176,923,924,927,929,932,935,937,940,942,944,946,949,951],{"class":178,"line":236},[176,925,926],{"class":186},"    lu ",[176,928,217],{"class":182},[176,930,931],{"class":186}," f[",[176,933,934],{"class":223},"\"landuse_class\"",[176,936,818],{"class":186},[176,938,939],{"class":182},"if",[176,941,561],{"class":182},[176,943,564],{"class":186},[176,945,934],{"class":223},[176,947,948],{"class":186},"]) ",[176,950,614],{"class":182},[176,952,953],{"class":223}," \"(no survey)\"\n",[176,955,956,959,961,963,966,968,970,972,974,976,978,980],{"class":178,"line":255},[176,957,958],{"class":186},"    zn ",[176,960,217],{"class":182},[176,962,931],{"class":186},[176,964,965],{"class":223},"\"zone_designation\"",[176,967,818],{"class":186},[176,969,939],{"class":182},[176,971,561],{"class":182},[176,973,564],{"class":186},[176,975,965],{"class":223},[176,977,948],{"class":186},[176,979,614],{"class":182},[176,981,982],{"class":223}," \"(no zone)\"\n",[176,984,985,988,991,993,995,997,999],{"class":178,"line":260},[176,986,987],{"class":186},"    matrix[(lu, zn)] ",[176,989,990],{"class":182},"+=",[176,992,931],{"class":186},[176,994,741],{"class":223},[176,996,818],{"class":186},[176,998,848],{"class":182},[176,1000,1001],{"class":230}," 1e4\n",[176,1003,1004],{"class":178,"line":277},[176,1005,208],{"emptyLinePlaceholder":207},[176,1007,1008,1011,1013,1016,1019,1021,1024,1026],{"class":178,"line":286},[176,1009,1010],{"class":186},"zones ",[176,1012,217],{"class":182},[176,1014,1015],{"class":230}," sorted",[176,1017,1018],{"class":186},"({z ",[176,1020,542],{"class":182},[176,1022,1023],{"class":186}," _, z ",[176,1025,548],{"class":182},[176,1027,1028],{"class":186}," matrix})\n",[176,1030,1031,1033,1035,1038,1041,1044,1046,1049,1052,1055,1058,1061,1064,1066,1068,1071,1073],{"class":178,"line":295},[176,1032,342],{"class":230},[176,1034,834],{"class":186},[176,1036,1037],{"class":223},"\"land use\"",[176,1039,1040],{"class":186},".ljust(",[176,1042,1043],{"class":230},"18",[176,1045,460],{"class":186},[176,1047,1048],{"class":182},"+",[176,1050,1051],{"class":223}," \"\"",[176,1053,1054],{"class":186},".join(z[:",[176,1056,1057],{"class":230},"12",[176,1059,1060],{"class":186},"].rjust(",[176,1062,1063],{"class":230},"13",[176,1065,460],{"class":186},[176,1067,542],{"class":182},[176,1069,1070],{"class":186}," z ",[176,1072,548],{"class":182},[176,1074,1075],{"class":186}," zones))\n",[176,1077,1078,1080,1083,1085,1087,1090,1092,1095,1097],{"class":178,"line":310},[176,1079,542],{"class":182},[176,1081,1082],{"class":186}," lu ",[176,1084,548],{"class":182},[176,1086,1015],{"class":230},[176,1088,1089],{"class":186},"({l ",[176,1091,542],{"class":182},[176,1093,1094],{"class":186}," l, _ ",[176,1096,548],{"class":182},[176,1098,1099],{"class":186}," matrix}):\n",[176,1101,1102,1105,1108,1110,1113,1115,1117,1119,1121,1124,1126,1129,1131,1134,1136,1139,1142,1144,1146,1149,1151,1153],{"class":178,"line":323},[176,1103,1104],{"class":230},"    print",[176,1106,1107],{"class":186},"(lu[:",[176,1109,1043],{"class":230},[176,1111,1112],{"class":186},"].ljust(",[176,1114,1043],{"class":230},[176,1116,460],{"class":186},[176,1118,1048],{"class":182},[176,1120,1051],{"class":223},[176,1122,1123],{"class":186},".join(",[176,1125,837],{"class":182},[176,1127,1128],{"class":223},"\"",[176,1130,843],{"class":230},[176,1132,1133],{"class":186},"matrix.get((lu, z), ",[176,1135,51],{"class":230},[176,1137,1138],{"class":186},")",[176,1140,1141],{"class":182},":13.1f",[176,1143,857],{"class":230},[176,1145,1128],{"class":223},[176,1147,1148],{"class":182}," for",[176,1150,1070],{"class":186},[176,1152,548],{"class":182},[176,1154,1075],{"class":186},[14,1156,1157,1159,1160,377],{},[361,1158,363],{}," Replacing NULLs with explicit labels such as \"(no zone)\" keeps uncovered areas in the table instead of silently dropping them. The printed matrix — hectares of each land-use class per zoning designation — shows, for example, how much residential land use falls in zones designated for industry. For a spreadsheet, build the same table as a pandas pivot as in ",[21,1161,1163],{"href":1162},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fanalyse-attribute-table-with-pandas-pyqgis\u002F","analysing an attribute table with pandas",[142,1165,1167],{"id":1166},"self-union-count-overlaps-within-one-layer","Self-union: count overlaps within one layer",[14,1169,1170],{},"Running union on a single layer, with no overlay, splits it wherever its own features overlap. Each resulting piece appears once per feature covering it, which makes counting overlap depth straightforward.",[14,1172,1173],{},[34,1174,1177,1180,1183,1186,1189,1192,1196,1200,1205,1209,1212,1216,1221,1226,1230],{"viewBox":1175,"role":37,"ariaLabel":1176,"xmlns":39},"0 0 760 216","A self-union of three overlapping buffers giving pieces covered by one, two or three features",[41,1178,1179],{},"Overlap depth from a self-union",[45,1181,1182],{},"Three overlapping buffer polygons in one layer. A self-union splits them into pieces at every boundary. Pieces with identical geometry are grouped; the size of each group is the number of features covering that piece: one where a single buffer lies, two where two overlap, three in the centre where all three overlap.",[49,1184],{"x":51,"y":51,"width":52,"height":1185,"fill":54},"216",[56,1187,1188],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"How many features cover each place?",[1190,1191],"circle",{"cx":68,"cy":670,"r":67,"fill":71,"stroke":72,"style":73},[1190,1193],{"cx":1194,"cy":670,"r":67,"fill":1195,"stroke":80,"style":73},"260","none",[1190,1197],{"cx":1198,"cy":83,"r":67,"fill":1195,"stroke":1199,"style":73},"230","#b45309",[56,1201,1204],{"x":1198,"y":1202,"style":1203,"fill":101,"textAnchor":62},"108","text-anchor:middle;font-size:14.0px;font-family:sans-serif;font-weight:bold","3",[56,1206,773],{"x":99,"y":1207,"style":1208,"fill":72,"textAnchor":62},"150","text-anchor:middle;font-size:12.0px;font-family:sans-serif",[56,1210,773],{"x":1211,"y":1207,"style":1208,"fill":80,"textAnchor":62},"300",[56,1213,1215],{"x":1198,"y":1214,"style":1208,"fill":61,"textAnchor":62},"180","2",[49,1217],{"x":1218,"y":67,"width":1219,"height":123,"rx":109,"fill":1220,"stroke":119,"style":73},"430","306","#fffdf7",[56,1222,1225],{"x":1223,"y":1224,"style":677,"fill":61,"textAnchor":62},"583","107.78","self-union",[56,1227,1229],{"x":1223,"y":1228,"style":90,"fill":119,"textAnchor":62},"133.78","group identical pieces",[56,1231,1233],{"x":1223,"y":1232,"style":90,"fill":119,"textAnchor":62},"159.78","count = overlap depth",[167,1235,1237],{"className":169,"code":1236,"language":171,"meta":172,"style":172},"self_u = processing.run(\"native:union\", {\n    \"INPUT\": QgsProject.instance().mapLayersByName(\"school_catchments\")[0],\n    \"OVERLAY\": None, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n\ngroups = defaultdict(int)\nfor f in self_u.getFeatures():\n    groups[f.geometry().asWkb().toHex().data()] += 1\nprint(max(groups.values()), \"catchments overlap at the most crowded place\")\n",[27,1238,1239,1252,1269,1293,1297,1311,1322,1332],{"__ignoreMap":172},[176,1240,1241,1244,1246,1248,1250],{"class":178,"line":179},[176,1242,1243],{"class":186},"self_u ",[176,1245,217],{"class":182},[176,1247,268],{"class":186},[176,1249,271],{"class":223},[176,1251,274],{"class":186},[176,1253,1254,1256,1259,1262,1264,1266],{"class":178,"line":190},[176,1255,280],{"class":223},[176,1257,1258],{"class":186},": QgsProject.instance().mapLayersByName(",[176,1260,1261],{"class":223},"\"school_catchments\"",[176,1263,227],{"class":186},[176,1265,51],{"class":230},[176,1267,1268],{"class":186},"],\n",[176,1270,1271,1273,1275,1278,1280,1282,1284,1287,1289,1291],{"class":178,"line":204},[176,1272,289],{"class":223},[176,1274,301],{"class":186},[176,1276,1277],{"class":230},"None",[176,1279,744],{"class":186},[176,1281,329],{"class":223},[176,1283,301],{"class":186},[176,1285,1286],{"class":223},"\"memory:\"",[176,1288,326],{"class":186},[176,1290,329],{"class":223},[176,1292,233],{"class":186},[176,1294,1295],{"class":178,"line":211},[176,1296,208],{"emptyLinePlaceholder":207},[176,1298,1299,1302,1304,1306,1309],{"class":178,"line":236},[176,1300,1301],{"class":186},"groups ",[176,1303,217],{"class":182},[176,1305,905],{"class":186},[176,1307,1308],{"class":230},"int",[176,1310,523],{"class":186},[176,1312,1313,1315,1317,1319],{"class":178,"line":255},[176,1314,542],{"class":182},[176,1316,545],{"class":186},[176,1318,548],{"class":182},[176,1320,1321],{"class":186}," self_u.getFeatures():\n",[176,1323,1324,1327,1329],{"class":178,"line":260},[176,1325,1326],{"class":186},"    groups[f.geometry().asWkb().toHex().data()] ",[176,1328,990],{"class":182},[176,1330,1331],{"class":230}," 1\n",[176,1333,1334,1336,1338,1341,1344,1347],{"class":178,"line":277},[176,1335,342],{"class":230},[176,1337,834],{"class":186},[176,1339,1340],{"class":230},"max",[176,1342,1343],{"class":186},"(groups.values()), ",[176,1345,1346],{"class":223},"\"catchments overlap at the most crowded place\"",[176,1348,523],{"class":186},[14,1350,1351,1353],{},[361,1352,363],{}," With no overlay, union resolves the layer against itself, producing duplicate pieces where features overlap — one per covering feature. Grouping pieces by identical geometry (here by their WKB) and counting gives the overlap depth for each piece: how many school catchments cover each address area, how many licence areas claim each square metre. Rounding coordinates before hashing guards against floating-point differences.",[142,1355,1357],{"id":1356},"compare-two-versions-of-a-classification","Compare two versions of a classification",[14,1359,1360],{},"Union is also the tool for comparing two classifications of the same area — this year's land-use survey against last year's, a contractor's habitat map against your own. The union's pieces carry both classes, so agreement is simply the share of area where they match.",[167,1362,1364],{"className":169,"code":1363,"language":171,"meta":172,"style":172},"old = QgsProject.instance().mapLayersByName(\"landuse_2025\")[0]\ncmp = processing.run(\"native:union\", {\n    \"INPUT\": landuse, \"OVERLAY\": old, \"OVERLAY_FIELDS_PREFIX\": \"old_\",\n    \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n\nsame = changed = 0.0\ntransitions = defaultdict(float)\nfor f in cmp.getFeatures():\n    a = f.geometry().area()\n    new_cls, old_cls = f[\"landuse_class\"], f[\"old_landuse_class\"]\n    if is_null(new_cls) or is_null(old_cls):\n        continue\n    if new_cls == old_cls:\n        same += a\n    else:\n        changed += a\n        transitions[(old_cls, new_cls)] += a \u002F 1e4\nprint(f\"agreement {same \u002F (same + changed):.1%}\")\nfor (o, n), ha in sorted(transitions.items(), key=lambda kv: -kv[1])[:5]:\n    print(f\"{o} → {n}: {ha:.1f} ha\")\n",[27,1365,1366,1384,1398,1421,1435,1439,1454,1467,1481,1491,1510,1524,1529,1542,1552,1560,1569,1583,1618,1659],{"__ignoreMap":172},[176,1367,1368,1371,1373,1375,1378,1380,1382],{"class":178,"line":179},[176,1369,1370],{"class":186},"old ",[176,1372,217],{"class":182},[176,1374,220],{"class":186},[176,1376,1377],{"class":223},"\"landuse_2025\"",[176,1379,227],{"class":186},[176,1381,51],{"class":230},[176,1383,233],{"class":186},[176,1385,1386,1389,1392,1394,1396],{"class":178,"line":190},[176,1387,1388],{"class":494},"cmp",[176,1390,1391],{"class":182}," =",[176,1393,268],{"class":186},[176,1395,271],{"class":223},[176,1397,274],{"class":186},[176,1399,1400,1402,1405,1408,1411,1414,1416,1419],{"class":178,"line":204},[176,1401,280],{"class":223},[176,1403,1404],{"class":186},": landuse, ",[176,1406,1407],{"class":223},"\"OVERLAY\"",[176,1409,1410],{"class":186},": old, ",[176,1412,1413],{"class":223},"\"OVERLAY_FIELDS_PREFIX\"",[176,1415,301],{"class":186},[176,1417,1418],{"class":223},"\"old_\"",[176,1420,307],{"class":186},[176,1422,1423,1425,1427,1429,1431,1433],{"class":178,"line":211},[176,1424,313],{"class":223},[176,1426,301],{"class":186},[176,1428,1286],{"class":223},[176,1430,326],{"class":186},[176,1432,329],{"class":223},[176,1434,233],{"class":186},[176,1436,1437],{"class":178,"line":236},[176,1438,208],{"emptyLinePlaceholder":207},[176,1440,1441,1444,1446,1449,1451],{"class":178,"line":255},[176,1442,1443],{"class":186},"same ",[176,1445,217],{"class":182},[176,1447,1448],{"class":186}," changed ",[176,1450,217],{"class":182},[176,1452,1453],{"class":230}," 0.0\n",[176,1455,1456,1459,1461,1463,1465],{"class":178,"line":260},[176,1457,1458],{"class":186},"transitions ",[176,1460,217],{"class":182},[176,1462,905],{"class":186},[176,1464,908],{"class":230},[176,1466,523],{"class":186},[176,1468,1469,1471,1473,1475,1478],{"class":178,"line":277},[176,1470,542],{"class":182},[176,1472,545],{"class":186},[176,1474,548],{"class":182},[176,1476,1477],{"class":494}," cmp",[176,1479,1480],{"class":186},".getFeatures():\n",[176,1482,1483,1486,1488],{"class":178,"line":286},[176,1484,1485],{"class":186},"    a ",[176,1487,217],{"class":182},[176,1489,1490],{"class":186}," f.geometry().area()\n",[176,1492,1493,1496,1498,1500,1502,1505,1508],{"class":178,"line":295},[176,1494,1495],{"class":186},"    new_cls, old_cls ",[176,1497,217],{"class":182},[176,1499,931],{"class":186},[176,1501,934],{"class":223},[176,1503,1504],{"class":186},"], f[",[176,1506,1507],{"class":223},"\"old_landuse_class\"",[176,1509,233],{"class":186},[176,1511,1512,1515,1518,1521],{"class":178,"line":310},[176,1513,1514],{"class":182},"    if",[176,1516,1517],{"class":186}," is_null(new_cls) ",[176,1519,1520],{"class":182},"or",[176,1522,1523],{"class":186}," is_null(old_cls):\n",[176,1525,1526],{"class":178,"line":323},[176,1527,1528],{"class":182},"        continue\n",[176,1530,1531,1533,1536,1539],{"class":178,"line":334},[176,1532,1514],{"class":182},[176,1534,1535],{"class":186}," new_cls ",[176,1537,1538],{"class":182},"==",[176,1540,1541],{"class":186}," old_cls:\n",[176,1543,1544,1547,1549],{"class":178,"line":339},[176,1545,1546],{"class":186},"        same ",[176,1548,990],{"class":182},[176,1550,1551],{"class":186}," a\n",[176,1553,1554,1557],{"class":178,"line":354},[176,1555,1556],{"class":182},"    else",[176,1558,1559],{"class":186},":\n",[176,1561,1562,1565,1567],{"class":178,"line":589},[176,1563,1564],{"class":186},"        changed ",[176,1566,990],{"class":182},[176,1568,1551],{"class":186},[176,1570,1571,1574,1576,1579,1581],{"class":178,"line":632},[176,1572,1573],{"class":186},"        transitions[(old_cls, new_cls)] ",[176,1575,990],{"class":182},[176,1577,1578],{"class":186}," a ",[176,1580,848],{"class":182},[176,1582,1001],{"class":230},[176,1584,1586,1588,1590,1592,1595,1597,1599,1601,1604,1606,1609,1612,1614,1616],{"class":178,"line":1585},18,[176,1587,342],{"class":230},[176,1589,834],{"class":186},[176,1591,837],{"class":182},[176,1593,1594],{"class":223},"\"agreement ",[176,1596,843],{"class":230},[176,1598,1443],{"class":186},[176,1600,848],{"class":182},[176,1602,1603],{"class":186}," (same ",[176,1605,1048],{"class":182},[176,1607,1608],{"class":186}," changed)",[176,1610,1611],{"class":182},":.1%",[176,1613,857],{"class":230},[176,1615,1128],{"class":223},[176,1617,523],{"class":186},[176,1619,1621,1623,1626,1628,1630,1633,1636,1639,1642,1645,1648,1650,1653,1656],{"class":178,"line":1620},19,[176,1622,542],{"class":182},[176,1624,1625],{"class":186}," (o, n), ha ",[176,1627,548],{"class":182},[176,1629,1015],{"class":230},[176,1631,1632],{"class":186},"(transitions.items(), ",[176,1634,1635],{"class":494},"key",[176,1637,1638],{"class":182},"=lambda",[176,1640,1641],{"class":186}," kv: ",[176,1643,1644],{"class":182},"-",[176,1646,1647],{"class":186},"kv[",[176,1649,773],{"class":230},[176,1651,1652],{"class":186},"])[:",[176,1654,1655],{"class":230},"5",[176,1657,1658],{"class":186},"]:\n",[176,1660,1662,1664,1666,1668,1670,1672,1675,1677,1680,1682,1685,1687,1689,1691,1694,1697,1699,1702],{"class":178,"line":1661},20,[176,1663,1104],{"class":230},[176,1665,834],{"class":186},[176,1667,837],{"class":182},[176,1669,1128],{"class":223},[176,1671,843],{"class":230},[176,1673,1674],{"class":186},"o",[176,1676,857],{"class":230},[176,1678,1679],{"class":223}," → ",[176,1681,843],{"class":230},[176,1683,1684],{"class":186},"n",[176,1686,857],{"class":230},[176,1688,301],{"class":223},[176,1690,843],{"class":230},[176,1692,1693],{"class":186},"ha",[176,1695,1696],{"class":182},":.1f",[176,1698,857],{"class":230},[176,1700,1701],{"class":223}," ha\"",[176,1703,523],{"class":186},[14,1705,1706,1708],{},[361,1707,363],{}," Pieces covered by both versions are compared class by class; pieces covered by only one are skipped, since they are coverage changes rather than reclassifications. The agreement percentage summarises how stable the classification is, and the largest transitions — farmland to residential, forest to clear-cut — are usually the real story. Boundary noise between the two versions inflates the changed area with slivers, so filter slivers before computing agreement, as described below.",[142,1710,1712],{"id":1711},"deal-with-slivers-and-null-attributes","Deal with slivers and NULL attributes",[14,1714,1715],{},"Union generates the most slivers of any overlay, because every boundary of both layers cuts every piece. Thin pieces along near-coincident boundaries are noise and inflate the \"A only\" and \"B only\" categories.",[167,1717,1719],{"className":169,"code":1718,"language":171,"meta":172,"style":172},"import math\n\ndef thinness(g):\n    p = g.length()\n    return 4 * math.pi * g.area() \u002F (p * p) if p else 0\n\nnoise = [f.id() for f in measured.getFeatures()\n         if f[\"piece_m2\"] \u003C 50 or thinness(f.geometry()) \u003C 0.05]\nwith edit(measured):\n    measured.deleteFeatures(noise)\nprint(len(noise), \"sliver pieces removed\")\n",[27,1720,1721,1728,1732,1742,1752,1791,1795,1814,1843,1851,1856],{"__ignoreMap":172},[176,1722,1723,1725],{"class":178,"line":179},[176,1724,183],{"class":182},[176,1726,1727],{"class":186}," math\n",[176,1729,1730],{"class":178,"line":190},[176,1731,208],{"emptyLinePlaceholder":207},[176,1733,1734,1736,1739],{"class":178,"line":204},[176,1735,421],{"class":182},[176,1737,1738],{"class":424}," thinness",[176,1740,1741],{"class":186},"(g):\n",[176,1743,1744,1747,1749],{"class":178,"line":211},[176,1745,1746],{"class":186},"    p ",[176,1748,217],{"class":182},[176,1750,1751],{"class":186}," g.length()\n",[176,1753,1754,1756,1759,1762,1765,1768,1771,1773,1776,1778,1781,1783,1786,1788],{"class":178,"line":236},[176,1755,433],{"class":182},[176,1757,1758],{"class":230}," 4",[176,1760,1761],{"class":182}," *",[176,1763,1764],{"class":186}," math.pi ",[176,1766,1767],{"class":182},"*",[176,1769,1770],{"class":186}," g.area() ",[176,1772,848],{"class":182},[176,1774,1775],{"class":186}," (p ",[176,1777,1767],{"class":182},[176,1779,1780],{"class":186}," p) ",[176,1782,939],{"class":182},[176,1784,1785],{"class":186}," p ",[176,1787,614],{"class":182},[176,1789,1790],{"class":230}," 0\n",[176,1792,1793],{"class":178,"line":255},[176,1794,208],{"emptyLinePlaceholder":207},[176,1796,1797,1800,1802,1805,1807,1809,1811],{"class":178,"line":260},[176,1798,1799],{"class":186},"noise ",[176,1801,217],{"class":182},[176,1803,1804],{"class":186}," [f.id() ",[176,1806,542],{"class":182},[176,1808,545],{"class":186},[176,1810,548],{"class":182},[176,1812,1813],{"class":186}," measured.getFeatures()\n",[176,1815,1816,1819,1821,1823,1825,1828,1831,1833,1836,1838,1841],{"class":178,"line":277},[176,1817,1818],{"class":182},"         if",[176,1820,931],{"class":186},[176,1822,741],{"class":223},[176,1824,818],{"class":186},[176,1826,1827],{"class":182},"\u003C",[176,1829,1830],{"class":230}," 50",[176,1832,445],{"class":182},[176,1834,1835],{"class":186}," thinness(f.geometry()) ",[176,1837,1827],{"class":182},[176,1839,1840],{"class":230}," 0.05",[176,1842,233],{"class":186},[176,1844,1845,1848],{"class":178,"line":286},[176,1846,1847],{"class":182},"with",[176,1849,1850],{"class":186}," edit(measured):\n",[176,1852,1853],{"class":178,"line":295},[176,1854,1855],{"class":186},"    measured.deleteFeatures(noise)\n",[176,1857,1858,1860,1862,1864,1867,1870],{"class":178,"line":310},[176,1859,342],{"class":230},[176,1861,834],{"class":186},[176,1863,495],{"class":230},[176,1865,1866],{"class":186},"(noise), ",[176,1868,1869],{"class":223},"\"sliver pieces removed\"",[176,1871,523],{"class":186},[14,1873,1874,1876,1877,1880,1881,377],{},[361,1875,363],{}," Dropping slivers removes small areas from the totals; for most cross-tabulations that is the right trade, but report how much area was removed. An alternative that loses nothing is to merge each sliver into its largest neighbour with ",[27,1878,1879],{},"native:eliminateselectedpolygons",", which keeps the total area intact while removing the noise from the table. Snapping the layers to each other before the union is the cleanest fix, as covered in ",[21,1882,1884],{"href":1883},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fsnap-geometries-to-layer-pyqgis\u002F","snapping geometries",[142,1886,1888],{"id":1887},"qgis-version-compatibility","QGIS version compatibility",[14,1890,1891,1893,1894,1896,1897,1900,1901,1904,1905,377],{},[27,1892,29],{}," with an optional overlay and field prefix works on QGIS 3.34 LTR, 3.40 LTR and QGIS 4; the prefix parameter has existed since 3.8. ",[27,1895,1879],{}," is available on all current releases. On QGIS 4, ",[27,1898,1899],{},"QgsField"," takes ",[27,1902,1903],{},"QMetaType.Type.QString",", and NULL attributes arrive as ",[27,1906,1277],{},[142,1908,1910],{"id":1909},"troubleshooting","Troubleshooting",[147,1912,1913,1922,1928,1934],{},[150,1914,1915,1918,1919,377],{},[361,1916,1917],{},"Field names collide or are renamed oddly."," Set ",[27,1920,1921],{},"OVERLAY_FIELDS_PREFIX",[150,1923,1924,1927],{},[361,1925,1926],{},"The matrix is missing uncovered areas."," NULL attributes were skipped; label them explicitly.",[150,1929,1930,1933],{},[361,1931,1932],{},"Piece counts are huge."," Many slivers along near-coincident boundaries; filter, eliminate or snap.",[150,1935,1936,1939],{},[361,1937,1938],{},"Union fails with a topology error."," Fix invalid geometries in both inputs first.",[142,1941,1943],{"id":1942},"conclusion","Conclusion",[14,1945,1946,1947,1949],{},"Run ",[27,1948,29],{}," with an overlay prefix, classify pieces as A only, B only or both from each layer's key, recompute piece areas, cross-tabulate with explicit labels for uncovered areas, use self-union to count overlap depth, and remove or eliminate slivers before reporting.",[142,1951,1953],{"id":1952},"frequently-asked-questions","Frequently Asked Questions",[14,1955,1956,1959],{},[361,1957,1958],{},"How is union different from merge?","\nMerge stacks features without splitting them, so overlaps remain; union splits at every boundary so no two pieces overlap.",[14,1961,1962,1965],{},[361,1963,1964],{},"Can I union more than two layers?","\nChain unions, or merge all layers and self-union the result.",[14,1967,1968,1971],{},[361,1969,1970],{},"Does union work with lines?","\nIt is designed for polygons; for lines, use split with lines or node the network instead.",[14,1973,1974,1977],{},[361,1975,1976],{},"Why are there pieces with all attributes NULL?","\nOnly in self-union with no overlay are all pieces from the one layer; in a two-layer union every piece belongs to at least one input. All-NULL pieces usually come from invalid input geometry.",[142,1979,1981],{"id":1980},"related","Related",[147,1983,1984,1989,1995,2001,2007],{},[150,1985,1986,1988],{},[21,1987,24],{"href":23}," — the guide this recipe belongs to",[150,1990,1991],{},[21,1992,1994],{"href":1993},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fintersect-two-vector-layers-pyqgis\u002F","Intersect Two Vector Layers in PyQGIS",[150,1996,1997],{},[21,1998,2000],{"href":1999},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Ferase-features-with-difference-pyqgis\u002F","Erase Features with Difference in PyQGIS",[150,2002,2003],{},[21,2004,2006],{"href":2005},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fdissolve-features-by-attribute-pyqgis\u002F","Dissolve Features by Attribute in PyQGIS",[150,2008,2009],{},[21,2010,2012],{"href":2011},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Ffind-gaps-and-overlaps-between-polygons-pyqgis\u002F","Find Gaps and Overlaps Between Polygons in PyQGIS",[2014,2015,2016],"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":172,"searchDepth":190,"depth":190,"links":2018},[2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031],{"id":144,"depth":190,"text":145},{"id":161,"depth":190,"text":162},{"id":380,"depth":190,"text":381},{"id":643,"depth":190,"text":644},{"id":870,"depth":190,"text":871},{"id":1166,"depth":190,"text":1167},{"id":1356,"depth":190,"text":1357},{"id":1711,"depth":190,"text":1712},{"id":1887,"depth":190,"text":1888},{"id":1909,"depth":190,"text":1910},{"id":1942,"depth":190,"text":1943},{"id":1952,"depth":190,"text":1953},{"id":1980,"depth":190,"text":1981},"Combine two polygon layers into one layer of non-overlapping pieces with native:union — attributes from both sides where they overlap and NULLs where they do not, classifying pieces by origin, recomputing areas, self-union for overlap counts, and turning the result into a cross-tabulation.","md",{"slug":2035,"type":2036,"breadcrumb":2037,"datePublished":2038,"dateModified":2038},"union-overlay-layers-pyqgis","article","Union Overlay of Layers","2026-10-02","\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Funion-overlay-layers-pyqgis",{"title":5,"description":2032},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Funion-overlay-layers-pyqgis\u002Findex","tTsi8c4XMIke50yfQ0gvbIYo5Mor32YgTB6nlcFfxYk",1790966266067]