[{"data":1,"prerenderedAt":1599},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fdissolve-features-by-attribute-pyqgis":3},{"id":4,"title":5,"body":6,"description":1588,"extension":1589,"meta":1590,"navigation":242,"path":1595,"seo":1596,"stem":1597,"__hash__":1598},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fdissolve-features-by-attribute-pyqgis\u002Findex.md","Dissolve Features by Attribute in PyQGIS",{"type":7,"value":8,"toc":1575},"minimark",[9,13,22,36,185,190,212,216,320,345,352,356,411,416,420,430,706,757,839,843,846,981,992,996,999,1220,1241,1244,1324,1328,1334,1404,1408,1463,1467,1475,1479,1497,1503,1512,1518,1528,1534,1540,1544,1571],[10,11,5],"h1",{"id":12},"dissolve-features-by-attribute-in-pyqgis",[14,15,16,17,21],"p",{},"Dissolving is how a thousand parcels become twelve wards, or how a land-cover raster's polygonised output stops being ten thousand adjacent squares. The operation itself is one algorithm call. What makes it worth a page is what dissolve ",[18,19,20],"em",{},"loses",": attributes that were meaningful per feature, boundaries that were nearly-but-not-quite shared, and a distinction between merging geometry and summarising data that catches people at exactly the wrong moment.",[14,23,24,25,30,31,35],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","Vector Data Manipulation in PyQGIS",". It covers dissolving by one or several fields, keeping useful aggregates, cleaning the slivers that imprecise boundaries leave behind, and knowing when ",[32,33,34],"code",{},"native:aggregate"," is the algorithm you actually wanted.",[14,37,38],{},[39,40,45,49,53,60,77,86,96,102,146,151,157,161,164,167,170,173,180],"svg",{"viewBox":41,"role":42,"ariaLabel":43,"xmlns":44},"0 0 760 268","img","Twelve parcels coloured by ward dissolving into three ward polygons, with the attribute table shrinking from twelve rows to three and per-parcel attributes being lost","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[46,47,48],"title",{},"What dissolve does to geometry and to the table",[50,51,52],"desc",{},"On the left, twelve small polygons are grouped into three colours by their ward attribute, with a twelve-row attribute table. On the right, each colour group has become a single polygon with the internal boundaries removed, and the table has three rows. A note marks that the per-parcel reference and area columns no longer have a meaningful value.",[54,55],"rect",{"x":56,"y":56,"width":57,"height":58,"fill":59},"0","760","268","#f6f3ea",[61,62,63],"defs",{},[64,65,72],"marker",{"id":66,"viewBox":67,"refX":68,"refY":69,"markerWidth":70,"markerHeight":70,"orient":71},"disArrow","0 0 10 10","8","5","7","auto-start-reverse",[73,74],"path",{"d":75,"fill":76},"M0 0 L10 5 L0 10 z","#2f3b35",[78,79,85],"text",{"x":80,"y":81,"style":82,"fill":83,"textAnchor":84},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Boundaries merge; per-feature attributes do not survive",[54,87],{"x":88,"y":89,"width":90,"height":91,"rx":92,"fill":93,"stroke":94,"style":95},"16","48","308","188","10","#fffdf7","#59645f","stroke-width:2",[78,97,101],{"x":98,"y":99,"style":100,"fill":83,"textAnchor":84},"170","72","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","12 parcels, 3 wards",[103,104,106,113,116,120,123,126,129,131,133,136,138,143],"g",{"stroke":94,"style":105},"stroke-width:1.5",[54,107],{"x":108,"y":109,"width":110,"height":108,"fill":111,"fillOpacity":112},"44","88","52","#2563eb",0.22,[54,114],{"x":115,"y":109,"width":110,"height":108,"fill":111,"fillOpacity":112},"96",[54,117],{"x":118,"y":109,"width":110,"height":108,"fill":119,"fillOpacity":112},"148","#0f766e",[54,121],{"x":122,"y":109,"width":110,"height":108,"fill":119,"fillOpacity":112},"200",[54,124],{"x":125,"y":109,"width":110,"height":108,"fill":119,"fillOpacity":112},"252",[54,127],{"x":108,"y":128,"width":110,"height":108,"fill":111,"fillOpacity":112},"132",[54,130],{"x":115,"y":128,"width":110,"height":108,"fill":111,"fillOpacity":112},[54,132],{"x":118,"y":128,"width":110,"height":108,"fill":119,"fillOpacity":112},[54,134],{"x":122,"y":128,"width":110,"height":108,"fill":135,"fillOpacity":112},"#b45309",[54,137],{"x":125,"y":128,"width":110,"height":108,"fill":135,"fillOpacity":112},[54,139],{"x":108,"y":140,"width":141,"height":142,"fill":111,"fillOpacity":112},"176","104","34",[54,144],{"x":118,"y":140,"width":145,"height":142,"fill":135,"fillOpacity":112},"156",[78,147,150],{"x":98,"y":148,"style":149,"fill":76,"textAnchor":84},"228","text-anchor:middle;font-size:11px;font-family:sans-serif","every internal edge is stored twice",[54,152],{"x":153,"y":89,"width":90,"height":91,"rx":92,"fill":154,"stroke":155,"style":156},"436","#edf8e9","#15803d","stroke-width:2.5",[78,158,160],{"x":159,"y":99,"style":100,"fill":155,"textAnchor":84},"590","3 wards",[73,162],{"d":163,"fill":111,"fillOpacity":112,"stroke":111,"style":95},"M464 88 L568 88 L568 132 L568 176 L568 210 L464 210 Z",[73,165],{"d":166,"fill":119,"fillOpacity":112,"stroke":119,"style":95},"M568 88 L724 88 L724 132 L620 132 L620 176 L568 176 Z",[73,168],{"d":169,"fill":135,"fillOpacity":112,"stroke":135,"style":95},"M620 132 L724 132 L724 210 L568 210 L568 176 L620 176 Z",[78,171,172],{"x":159,"y":148,"style":149,"fill":155,"textAnchor":84},"one boundary per ward, no internal edges",[174,175],"line",{"x1":176,"y1":177,"x2":178,"y2":177,"stroke":76,"style":179},"330","142","430","stroke-width:2;marker-end:url(#disArrow)",[78,181,184],{"x":80,"y":182,"style":183,"fill":76,"textAnchor":84},"134","text-anchor:middle;font-size:10px;font-family:sans-serif","dissolve",[186,187,189],"h2",{"id":188},"prerequisites","Prerequisites",[191,192,193,201,204],"ul",{},[194,195,196,200],"li",{},[197,198,199],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[194,202,203],{},"A polygon or line layer with a field that groups the features.",[194,205,206,207,211],{},"Valid geometry. Dissolve on self-intersecting polygons produces unpredictable results — repair first with ",[26,208,210],{"href":209},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Ffix-invalid-geometries-pyqgis\u002F","Find and Fix Invalid Geometries",".",[186,213,215],{"id":214},"dissolve-by-one-field","Dissolve by one field",[217,218,223],"pre",{"className":219,"code":220,"language":221,"meta":222,"style":222},"language-python shiki shiki-themes github-dark","import processing\n\nprocessing.run(\"native:dissolve\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"FIELD\": [\"ward_code\"],\n    \"SEPARATE_DISJOINT\": False,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fwards.gpkg\",\n})\n","python","",[32,224,225,237,244,257,272,287,301,314],{"__ignoreMap":222},[226,227,229,233],"span",{"class":174,"line":228},1,[226,230,232],{"class":231},"snl16","import",[226,234,236],{"class":235},"s95oV"," processing\n",[226,238,240],{"class":174,"line":239},2,[226,241,243],{"emptyLinePlaceholder":242},true,"\n",[226,245,247,250,254],{"class":174,"line":246},3,[226,248,249],{"class":235},"processing.run(",[226,251,253],{"class":252},"sU2Wk","\"native:dissolve\"",[226,255,256],{"class":235},", {\n",[226,258,260,263,266,269],{"class":174,"line":259},4,[226,261,262],{"class":252},"    \"INPUT\"",[226,264,265],{"class":235},": ",[226,267,268],{"class":252},"\"\u002Fdata\u002Fparcels.gpkg|layername=parcels\"",[226,270,271],{"class":235},",\n",[226,273,275,278,281,284],{"class":174,"line":274},5,[226,276,277],{"class":252},"    \"FIELD\"",[226,279,280],{"class":235},": [",[226,282,283],{"class":252},"\"ward_code\"",[226,285,286],{"class":235},"],\n",[226,288,290,293,295,299],{"class":174,"line":289},6,[226,291,292],{"class":252},"    \"SEPARATE_DISJOINT\"",[226,294,265],{"class":235},[226,296,298],{"class":297},"sDLfK","False",[226,300,271],{"class":235},[226,302,304,307,309,312],{"class":174,"line":303},7,[226,305,306],{"class":252},"    \"OUTPUT\"",[226,308,265],{"class":235},[226,310,311],{"class":252},"\"\u002Fdata\u002Foutput\u002Fwards.gpkg\"",[226,313,271],{"class":235},[226,315,317],{"class":174,"line":316},8,[226,318,319],{"class":235},"})\n",[14,321,322,325,326,329,330,333,334,337,338,340,341,344],{},[197,323,324],{},"Breakdown:"," ",[32,327,328],{},"FIELD"," takes a ",[197,331,332],{},"list",", even for a single field — passing a bare string is the most common cause of \"no attribute grouping happened\", because an empty or unrecognised field list dissolves everything into one feature. ",[32,335,336],{},"SEPARATE_DISJOINT"," decides what happens when a group's polygons do not touch: ",[32,339,298],{}," produces one multipart feature per group, ",[32,342,343],{},"True"," splits it into separate single-part features. Multipart is right for administrative areas; separate parts are right when each patch is a thing in its own right, such as woodland blocks.",[14,346,347,348,351],{},"The output keeps the grouping field and the attribute values from the ",[18,349,350],{},"first"," feature in each group for every other column. Those values are arbitrary — the first feature is whichever the provider returned first — so anything you care about must be aggregated deliberately.",[186,353,355],{"id":354},"dissolve-by-several-fields","Dissolve by several fields",[217,357,359],{"className":219,"code":358,"language":221,"meta":222,"style":222},"processing.run(\"native:dissolve\", {\n    \"INPUT\": \"\u002Fdata\u002Flandcover.gpkg|layername=landcover\",\n    \"FIELD\": [\"ward_code\", \"cover_class\"],\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fcover_by_ward.gpkg\",\n})\n",[32,360,361,369,380,396,407],{"__ignoreMap":222},[226,362,363,365,367],{"class":174,"line":228},[226,364,249],{"class":235},[226,366,253],{"class":252},[226,368,256],{"class":235},[226,370,371,373,375,378],{"class":174,"line":239},[226,372,262],{"class":252},[226,374,265],{"class":235},[226,376,377],{"class":252},"\"\u002Fdata\u002Flandcover.gpkg|layername=landcover\"",[226,379,271],{"class":235},[226,381,382,384,386,388,391,394],{"class":174,"line":246},[226,383,277],{"class":252},[226,385,280],{"class":235},[226,387,283],{"class":252},[226,389,390],{"class":235},", ",[226,392,393],{"class":252},"\"cover_class\"",[226,395,286],{"class":235},[226,397,398,400,402,405],{"class":174,"line":259},[226,399,306],{"class":252},[226,401,265],{"class":235},[226,403,404],{"class":252},"\"\u002Fdata\u002Foutput\u002Fcover_by_ward.gpkg\"",[226,406,271],{"class":235},[226,408,409],{"class":174,"line":274},[226,410,319],{"class":235},[14,412,413,415],{},[197,414,324],{}," Multiple fields dissolve on the combination, producing one feature per distinct pair — every land-cover class within every ward. This is the shape most reporting needs, and it is why dissolving by ward alone then joining classes back rarely works: the geometry has already been merged across classes.",[186,417,419],{"id":418},"keep-the-numbers-you-need","Keep the numbers you need",[14,421,422,423,425,426,429],{},"Dissolve merges shapes; it does not sum anything. When totals matter, use ",[32,424,34],{},", which dissolves ",[18,427,428],{},"and"," aggregates in one pass.",[217,431,433],{"className":219,"code":432,"language":221,"meta":222,"style":222},"processing.run(\"native:aggregate\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"GROUP_BY\": '\"ward_code\"',\n    \"AGGREGATES\": [\n        {\"aggregate\": \"first_value\", \"delimiter\": \",\", \"input\": '\"ward_code\"',\n         \"length\": 10, \"name\": \"ward_code\", \"precision\": 0, \"type\": 10},\n        {\"aggregate\": \"sum\", \"delimiter\": \",\", \"input\": '\"area_m2\"',\n         \"length\": 20, \"name\": \"total_area\", \"precision\": 2, \"type\": 6},\n        {\"aggregate\": \"count\", \"delimiter\": \",\", \"input\": '\"parcel_ref\"',\n         \"length\": 10, \"name\": \"parcel_count\", \"precision\": 0, \"type\": 4},\n    ],\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fwards_summary.gpkg\",\n})\n",[32,434,435,444,454,466,474,508,547,577,615,646,683,689,701],{"__ignoreMap":222},[226,436,437,439,442],{"class":174,"line":228},[226,438,249],{"class":235},[226,440,441],{"class":252},"\"native:aggregate\"",[226,443,256],{"class":235},[226,445,446,448,450,452],{"class":174,"line":239},[226,447,262],{"class":252},[226,449,265],{"class":235},[226,451,268],{"class":252},[226,453,271],{"class":235},[226,455,456,459,461,464],{"class":174,"line":246},[226,457,458],{"class":252},"    \"GROUP_BY\"",[226,460,265],{"class":235},[226,462,463],{"class":252},"'\"ward_code\"'",[226,465,271],{"class":235},[226,467,468,471],{"class":174,"line":259},[226,469,470],{"class":252},"    \"AGGREGATES\"",[226,472,473],{"class":235},": [\n",[226,475,476,479,482,484,487,489,492,494,497,499,502,504,506],{"class":174,"line":274},[226,477,478],{"class":235},"        {",[226,480,481],{"class":252},"\"aggregate\"",[226,483,265],{"class":235},[226,485,486],{"class":252},"\"first_value\"",[226,488,390],{"class":235},[226,490,491],{"class":252},"\"delimiter\"",[226,493,265],{"class":235},[226,495,496],{"class":252},"\",\"",[226,498,390],{"class":235},[226,500,501],{"class":252},"\"input\"",[226,503,265],{"class":235},[226,505,463],{"class":252},[226,507,271],{"class":235},[226,509,510,513,515,517,519,522,524,526,528,531,533,535,537,540,542,544],{"class":174,"line":289},[226,511,512],{"class":252},"         \"length\"",[226,514,265],{"class":235},[226,516,92],{"class":297},[226,518,390],{"class":235},[226,520,521],{"class":252},"\"name\"",[226,523,265],{"class":235},[226,525,283],{"class":252},[226,527,390],{"class":235},[226,529,530],{"class":252},"\"precision\"",[226,532,265],{"class":235},[226,534,56],{"class":297},[226,536,390],{"class":235},[226,538,539],{"class":252},"\"type\"",[226,541,265],{"class":235},[226,543,92],{"class":297},[226,545,546],{"class":235},"},\n",[226,548,549,551,553,555,558,560,562,564,566,568,570,572,575],{"class":174,"line":303},[226,550,478],{"class":235},[226,552,481],{"class":252},[226,554,265],{"class":235},[226,556,557],{"class":252},"\"sum\"",[226,559,390],{"class":235},[226,561,491],{"class":252},[226,563,265],{"class":235},[226,565,496],{"class":252},[226,567,390],{"class":235},[226,569,501],{"class":252},[226,571,265],{"class":235},[226,573,574],{"class":252},"'\"area_m2\"'",[226,576,271],{"class":235},[226,578,579,581,583,586,588,590,592,595,597,599,601,604,606,608,610,613],{"class":174,"line":316},[226,580,512],{"class":252},[226,582,265],{"class":235},[226,584,585],{"class":297},"20",[226,587,390],{"class":235},[226,589,521],{"class":252},[226,591,265],{"class":235},[226,593,594],{"class":252},"\"total_area\"",[226,596,390],{"class":235},[226,598,530],{"class":252},[226,600,265],{"class":235},[226,602,603],{"class":297},"2",[226,605,390],{"class":235},[226,607,539],{"class":252},[226,609,265],{"class":235},[226,611,612],{"class":297},"6",[226,614,546],{"class":235},[226,616,618,620,622,624,627,629,631,633,635,637,639,641,644],{"class":174,"line":617},9,[226,619,478],{"class":235},[226,621,481],{"class":252},[226,623,265],{"class":235},[226,625,626],{"class":252},"\"count\"",[226,628,390],{"class":235},[226,630,491],{"class":252},[226,632,265],{"class":235},[226,634,496],{"class":252},[226,636,390],{"class":235},[226,638,501],{"class":252},[226,640,265],{"class":235},[226,642,643],{"class":252},"'\"parcel_ref\"'",[226,645,271],{"class":235},[226,647,649,651,653,655,657,659,661,664,666,668,670,672,674,676,678,681],{"class":174,"line":648},10,[226,650,512],{"class":252},[226,652,265],{"class":235},[226,654,92],{"class":297},[226,656,390],{"class":235},[226,658,521],{"class":252},[226,660,265],{"class":235},[226,662,663],{"class":252},"\"parcel_count\"",[226,665,390],{"class":235},[226,667,530],{"class":252},[226,669,265],{"class":235},[226,671,56],{"class":297},[226,673,390],{"class":235},[226,675,539],{"class":252},[226,677,265],{"class":235},[226,679,680],{"class":297},"4",[226,682,546],{"class":235},[226,684,686],{"class":174,"line":685},11,[226,687,688],{"class":235},"    ],\n",[226,690,692,694,696,699],{"class":174,"line":691},12,[226,693,306],{"class":252},[226,695,265],{"class":235},[226,697,698],{"class":252},"\"\u002Fdata\u002Foutput\u002Fwards_summary.gpkg\"",[226,700,271],{"class":235},[226,702,704],{"class":174,"line":703},13,[226,705,319],{"class":235},[14,707,708,325,710,713,714,717,718,721,722,725,726,729,730,390,733,390,736,390,739,390,742,390,745,748,749,752,753,756],{},[197,709,324],{},[32,711,712],{},"GROUP_BY"," is an ",[197,715,716],{},"expression",", so the field name is quoted — and it can be any expression, which means grouping by ",[32,719,720],{},"left(\"ward_code\", 2)"," or by a computed category is available for free. Each entry in ",[32,723,724],{},"AGGREGATES"," defines one output field: ",[32,727,728],{},"aggregate"," is the function (",[32,731,732],{},"sum",[32,734,735],{},"mean",[32,737,738],{},"count",[32,740,741],{},"concatenate",[32,743,744],{},"first_value",[32,746,747],{},"maximum","…), ",[32,750,751],{},"input"," is an expression over the source, and ",[32,754,755],{},"type"," is the QVariant type code — 10 for string, 6 for double, 4 for long integer. The dictionary is verbose, and building it programmatically from a small list of tuples is worth doing once in any project that dissolves regularly.",[14,758,759],{},[39,760,763,766,769,772,775,778,781,785,789,793,797,801,805,809,812,817,821,825,828,830,833,836],{"viewBox":761,"role":42,"ariaLabel":762,"xmlns":44},"0 0 760 250","Comparison of dissolve and aggregate showing that dissolve keeps arbitrary attribute values from the first feature while aggregate computes sums and counts",[46,764,765],{},"Dissolve keeps one arbitrary row; aggregate computes",[50,767,768],{},"Three input parcels with areas and references are shown. Dissolve produces one feature whose area field still holds the first parcel's area, which is misleading. Aggregate produces one feature whose total area is the sum and whose count field records how many parcels contributed.",[54,770],{"x":56,"y":56,"width":57,"height":771,"fill":59},"250",[78,773,774],{"x":80,"y":81,"style":82,"fill":83,"textAnchor":84},"The attribute left behind by dissolve is not a total",[54,776],{"x":88,"y":89,"width":777,"height":140,"rx":92,"fill":93,"stroke":94,"style":95},"216",[78,779,751],{"x":780,"y":99,"style":100,"fill":83,"textAnchor":84},"124",[78,782,784],{"x":780,"y":783,"style":149,"fill":76,"textAnchor":84},"102","P-101 · 1 200 m²",[78,786,788],{"x":780,"y":787,"style":149,"fill":76,"textAnchor":84},"128","P-102 · 3 400 m²",[78,790,792],{"x":780,"y":791,"style":149,"fill":76,"textAnchor":84},"154","P-103 · 900 m²",[78,794,796],{"x":780,"y":795,"style":149,"fill":94,"textAnchor":84},"192","all in ward W3",[54,798],{"x":799,"y":89,"width":777,"height":140,"rx":92,"fill":800,"stroke":135,"style":156},"264","#fdf2e2",[78,802,804],{"x":803,"y":99,"style":100,"fill":135,"textAnchor":84},"372","native:dissolve",[78,806,808],{"x":803,"y":807,"style":149,"fill":76,"textAnchor":84},"106","geometry: merged",[78,810,811],{"x":803,"y":182,"style":149,"fill":76,"textAnchor":84},"parcel_ref: P-101",[78,813,816],{"x":803,"y":814,"style":149,"fill":815,"textAnchor":84},"160","#b91c1c","area_m2: 1 200",[78,818,820],{"x":803,"y":819,"style":149,"fill":135,"textAnchor":84},"196","looks like a total, is not",[54,822],{"x":823,"y":89,"width":824,"height":140,"rx":92,"fill":154,"stroke":155,"style":156},"512","232",[78,826,34],{"x":827,"y":99,"style":100,"fill":155,"textAnchor":84},"628",[78,829,808],{"x":827,"y":807,"style":149,"fill":76,"textAnchor":84},[78,831,832],{"x":827,"y":182,"style":149,"fill":76,"textAnchor":84},"total_area: 5 500",[78,834,835],{"x":827,"y":814,"style":149,"fill":76,"textAnchor":84},"parcel_count: 3",[78,837,838],{"x":827,"y":819,"style":149,"fill":155,"textAnchor":84},"the numbers mean something",[186,840,842],{"id":841},"clean-up-what-imprecise-boundaries-leave","Clean up what imprecise boundaries leave",[14,844,845],{},"Data digitised from different sources rarely shares vertices exactly. Two polygons that look adjacent may be a millimetre apart, and dissolve will faithfully keep the gap — producing a result with hairline slivers running through it.",[217,847,849],{"className":219,"code":848,"language":221,"meta":222,"style":222},"snapped = processing.run(\"native:snapgeometries\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"REFERENCE_LAYER\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"TOLERANCE\": 0.05,\n    \"BEHAVIOR\": 1,                  # prefer aligning nodes, insert extra vertices\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nprocessing.run(\"native:dissolve\", {\n    \"INPUT\": snapped, \"FIELD\": [\"ward_code\"],\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fwards.gpkg\",\n})\n",[32,850,851,867,877,888,900,917,928,939,943,951,967,977],{"__ignoreMap":222},[226,852,853,856,859,862,865],{"class":174,"line":228},[226,854,855],{"class":235},"snapped ",[226,857,858],{"class":231},"=",[226,860,861],{"class":235}," processing.run(",[226,863,864],{"class":252},"\"native:snapgeometries\"",[226,866,256],{"class":235},[226,868,869,871,873,875],{"class":174,"line":239},[226,870,262],{"class":252},[226,872,265],{"class":235},[226,874,268],{"class":252},[226,876,271],{"class":235},[226,878,879,882,884,886],{"class":174,"line":246},[226,880,881],{"class":252},"    \"REFERENCE_LAYER\"",[226,883,265],{"class":235},[226,885,268],{"class":252},[226,887,271],{"class":235},[226,889,890,893,895,898],{"class":174,"line":259},[226,891,892],{"class":252},"    \"TOLERANCE\"",[226,894,265],{"class":235},[226,896,897],{"class":297},"0.05",[226,899,271],{"class":235},[226,901,902,905,907,910,913],{"class":174,"line":274},[226,903,904],{"class":252},"    \"BEHAVIOR\"",[226,906,265],{"class":235},[226,908,909],{"class":297},"1",[226,911,912],{"class":235},",                  ",[226,914,916],{"class":915},"sjoCn","# prefer aligning nodes, insert extra vertices\n",[226,918,919,921,923,926],{"class":174,"line":289},[226,920,306],{"class":252},[226,922,265],{"class":235},[226,924,925],{"class":252},"\"TEMPORARY_OUTPUT\"",[226,927,271],{"class":235},[226,929,930,933,936],{"class":174,"line":303},[226,931,932],{"class":235},"})[",[226,934,935],{"class":252},"\"OUTPUT\"",[226,937,938],{"class":235},"]\n",[226,940,941],{"class":174,"line":316},[226,942,243],{"emptyLinePlaceholder":242},[226,944,945,947,949],{"class":174,"line":617},[226,946,249],{"class":235},[226,948,253],{"class":252},[226,950,256],{"class":235},[226,952,953,955,958,961,963,965],{"class":174,"line":648},[226,954,262],{"class":252},[226,956,957],{"class":235},": snapped, ",[226,959,960],{"class":252},"\"FIELD\"",[226,962,280],{"class":235},[226,964,283],{"class":252},[226,966,286],{"class":235},[226,968,969,971,973,975],{"class":174,"line":685},[226,970,306],{"class":252},[226,972,265],{"class":235},[226,974,311],{"class":252},[226,976,271],{"class":235},[226,978,979],{"class":174,"line":691},[226,980,319],{"class":235},[14,982,983,985,986,989,990,211],{},[197,984,324],{}," Snapping the layer to itself with a small tolerance pulls near-coincident vertices together so the shared boundaries genuinely coincide. Five centimetres is a reasonable starting tolerance for cadastral data in metres; too large and real detail collapses, so check the vertex count before and after. Dissolving the snapped copy then produces clean boundaries. If slivers survive, run ",[32,987,988],{},"native:fixgeometries"," on the result and inspect with the checks in ",[26,991,210],{"href":209},[186,993,995],{"id":994},"prove-the-result-before-publishing-it","Prove the result before publishing it",[14,997,998],{},"Dissolve is one of the few operations with an arithmetic check available: the total area of the output should equal the total area of the input, because merging shapes moves no ground.",[217,1000,1002],{"className":219,"code":1001,"language":221,"meta":222,"style":222},"from qgis.core import QgsVectorLayer\n\ndef total_area(path):\n    layer = QgsVectorLayer(path, \"check\", \"ogr\")\n    return sum(f.geometry().area() for f in layer.getFeatures()), layer.featureCount()\n\nsource_area, source_count = total_area(\"\u002Fdata\u002Fparcels.gpkg|layername=parcels\")\nresult_area, result_count = total_area(\"\u002Fdata\u002Foutput\u002Fwards.gpkg|layername=wards\")\n\ndifference = abs(result_area - source_area) \u002F source_area\nprint(f\"{source_count} → {result_count} features, area differs by {difference:.4%}\")\nif difference > 0.0001:\n    raise RuntimeError(\"area changed by more than 0.01% — check for slivers or overlaps\")\n",[32,1003,1004,1017,1021,1033,1054,1077,1081,1095,1109,1113,1138,1188,1205],{"__ignoreMap":222},[226,1005,1006,1009,1012,1014],{"class":174,"line":228},[226,1007,1008],{"class":231},"from",[226,1010,1011],{"class":235}," qgis.core ",[226,1013,232],{"class":231},[226,1015,1016],{"class":235}," QgsVectorLayer\n",[226,1018,1019],{"class":174,"line":239},[226,1020,243],{"emptyLinePlaceholder":242},[226,1022,1023,1026,1030],{"class":174,"line":246},[226,1024,1025],{"class":231},"def",[226,1027,1029],{"class":1028},"svObZ"," total_area",[226,1031,1032],{"class":235},"(path):\n",[226,1034,1035,1038,1040,1043,1046,1048,1051],{"class":174,"line":259},[226,1036,1037],{"class":235},"    layer ",[226,1039,858],{"class":231},[226,1041,1042],{"class":235}," QgsVectorLayer(path, ",[226,1044,1045],{"class":252},"\"check\"",[226,1047,390],{"class":235},[226,1049,1050],{"class":252},"\"ogr\"",[226,1052,1053],{"class":235},")\n",[226,1055,1056,1059,1062,1065,1068,1071,1074],{"class":174,"line":274},[226,1057,1058],{"class":231},"    return",[226,1060,1061],{"class":297}," sum",[226,1063,1064],{"class":235},"(f.geometry().area() ",[226,1066,1067],{"class":231},"for",[226,1069,1070],{"class":235}," f ",[226,1072,1073],{"class":231},"in",[226,1075,1076],{"class":235}," layer.getFeatures()), layer.featureCount()\n",[226,1078,1079],{"class":174,"line":289},[226,1080,243],{"emptyLinePlaceholder":242},[226,1082,1083,1086,1088,1091,1093],{"class":174,"line":303},[226,1084,1085],{"class":235},"source_area, source_count ",[226,1087,858],{"class":231},[226,1089,1090],{"class":235}," total_area(",[226,1092,268],{"class":252},[226,1094,1053],{"class":235},[226,1096,1097,1100,1102,1104,1107],{"class":174,"line":316},[226,1098,1099],{"class":235},"result_area, result_count ",[226,1101,858],{"class":231},[226,1103,1090],{"class":235},[226,1105,1106],{"class":252},"\"\u002Fdata\u002Foutput\u002Fwards.gpkg|layername=wards\"",[226,1108,1053],{"class":235},[226,1110,1111],{"class":174,"line":617},[226,1112,243],{"emptyLinePlaceholder":242},[226,1114,1115,1118,1120,1123,1126,1129,1132,1135],{"class":174,"line":648},[226,1116,1117],{"class":235},"difference ",[226,1119,858],{"class":231},[226,1121,1122],{"class":297}," abs",[226,1124,1125],{"class":235},"(result_area ",[226,1127,1128],{"class":231},"-",[226,1130,1131],{"class":235}," source_area) ",[226,1133,1134],{"class":231},"\u002F",[226,1136,1137],{"class":235}," source_area\n",[226,1139,1140,1143,1146,1149,1152,1155,1158,1161,1164,1166,1169,1171,1174,1176,1179,1182,1184,1186],{"class":174,"line":685},[226,1141,1142],{"class":297},"print",[226,1144,1145],{"class":235},"(",[226,1147,1148],{"class":231},"f",[226,1150,1151],{"class":252},"\"",[226,1153,1154],{"class":297},"{",[226,1156,1157],{"class":235},"source_count",[226,1159,1160],{"class":297},"}",[226,1162,1163],{"class":252}," → ",[226,1165,1154],{"class":297},[226,1167,1168],{"class":235},"result_count",[226,1170,1160],{"class":297},[226,1172,1173],{"class":252}," features, area differs by ",[226,1175,1154],{"class":297},[226,1177,1178],{"class":235},"difference",[226,1180,1181],{"class":231},":.4%",[226,1183,1160],{"class":297},[226,1185,1151],{"class":252},[226,1187,1053],{"class":235},[226,1189,1190,1193,1196,1199,1202],{"class":174,"line":691},[226,1191,1192],{"class":231},"if",[226,1194,1195],{"class":235}," difference ",[226,1197,1198],{"class":231},">",[226,1200,1201],{"class":297}," 0.0001",[226,1203,1204],{"class":235},":\n",[226,1206,1207,1210,1213,1215,1218],{"class":174,"line":703},[226,1208,1209],{"class":231},"    raise",[226,1211,1212],{"class":297}," RuntimeError",[226,1214,1145],{"class":235},[226,1216,1217],{"class":252},"\"area changed by more than 0.01% — check for slivers or overlaps\"",[226,1219,1053],{"class":235},[14,1221,1222,1224,1225,1228,1229,1232,1233,1236,1237,211],{},[197,1223,324],{}," A difference of a few thousandths of a percent is floating-point noise and unavoidable. Anything larger has a cause worth finding: the output being ",[18,1226,1227],{},"smaller"," usually means slivers were lost where boundaries did not quite meet, and the output being ",[18,1230,1231],{},"larger"," means the inputs overlapped, so the shared area was counted twice before and once after. Both are data-quality findings rather than dissolve bugs, and both are much cheaper to discover here than in a report three weeks later. Note that ",[32,1234,1235],{},"geometry().area()"," is in the layer's map units, so this check belongs in a projected CRS — in degrees the number is meaningless, as explained in ",[26,1238,1240],{"href":1239},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fpyqgis-script-to-calculate-polygon-areas\u002F","PyQGIS Script to Calculate Polygon Areas",[14,1242,1243],{},"The feature-count line is worth printing even when the areas agree. A dissolve producing exactly as many features as it consumed means the grouping field had a distinct value per row — usually whitespace or case differences that should have been normalised first.",[14,1245,1246],{},[39,1247,1250,1253,1256,1259,1262,1266,1270,1275,1279,1281,1284,1287,1289,1293,1296,1298,1300,1303,1306,1308,1311,1314,1316,1318,1321],{"viewBox":1248,"role":42,"ariaLabel":1249,"xmlns":44},"0 0 760 244","Three dissolve outcomes compared by total area: an exact match, a smaller total indicating lost slivers, and a larger total indicating overlapping inputs",[46,1251,1252],{},"What a change in total area is telling you",[50,1254,1255],{},"Comparing the total area before and after a dissolve gives three outcomes. An identical total means the merge was clean. A smaller total means slivers were lost where boundaries did not coincide. A larger total means the inputs overlapped, so shared ground was counted twice before the merge and once after.",[54,1257],{"x":56,"y":56,"width":57,"height":1258,"fill":59},"244",[78,1260,1261],{"x":80,"y":81,"style":82,"fill":83,"textAnchor":84},"Merging shapes should not move any ground",[54,1263],{"x":88,"y":110,"width":1264,"height":1265,"rx":92,"fill":154,"stroke":155,"style":156},"236","164",[78,1267,1269],{"x":182,"y":1268,"style":100,"fill":155,"textAnchor":84},"78","identical",[54,1271],{"x":108,"y":1272,"width":1273,"height":1274,"rx":680,"fill":93,"stroke":94,"style":105},"98","180","30",[78,1276,1278],{"x":182,"y":1277,"style":149,"fill":76,"textAnchor":84},"118","before 4 812 400 m²",[54,1280],{"x":108,"y":182,"width":1273,"height":1274,"rx":680,"fill":93,"stroke":94,"style":105},[78,1282,1283],{"x":182,"y":791,"style":149,"fill":76,"textAnchor":84},"after 4 812 400 m²",[78,1285,1286],{"x":182,"y":795,"style":149,"fill":155,"textAnchor":84},"clean merge",[54,1288],{"x":799,"y":110,"width":1264,"height":1265,"rx":92,"fill":800,"stroke":135,"style":156},[78,1290,1292],{"x":1291,"y":1268,"style":100,"fill":135,"textAnchor":84},"382","smaller after",[54,1294],{"x":1295,"y":1272,"width":1273,"height":1274,"rx":680,"fill":93,"stroke":94,"style":105},"292",[78,1297,1278],{"x":1291,"y":1277,"style":149,"fill":76,"textAnchor":84},[54,1299],{"x":1295,"y":182,"width":1273,"height":1274,"rx":680,"fill":93,"stroke":135,"style":105},[78,1301,1302],{"x":1291,"y":791,"style":149,"fill":76,"textAnchor":84},"after 4 809 100 m²",[78,1304,1305],{"x":1291,"y":795,"style":149,"fill":135,"textAnchor":84},"slivers lost — snap first",[54,1307],{"x":823,"y":110,"width":824,"height":1265,"rx":92,"fill":800,"stroke":815,"style":156},[78,1309,1310],{"x":827,"y":1268,"style":100,"fill":815,"textAnchor":84},"larger after",[54,1312],{"x":1313,"y":1272,"width":140,"height":1274,"rx":680,"fill":93,"stroke":94,"style":105},"540",[78,1315,1278],{"x":827,"y":1277,"style":149,"fill":76,"textAnchor":84},[54,1317],{"x":1313,"y":182,"width":140,"height":1274,"rx":680,"fill":93,"stroke":815,"style":105},[78,1319,1320],{"x":827,"y":791,"style":149,"fill":76,"textAnchor":84},"after 4 861 900 m²",[78,1322,1323],{"x":827,"y":795,"style":149,"fill":815,"textAnchor":84},"inputs overlapped",[186,1325,1327],{"id":1326},"qgis-version-compatibility","QGIS version compatibility",[14,1329,1330,1331,1333],{},"The examples target ",[197,1332,199],{}," (Python 3.12).",[1335,1336,1337,1353],"table",{},[1338,1339,1340],"thead",{},[1341,1342,1343,1347,1350],"tr",{},[1344,1345,1346],"th",{},"QGIS version",[1344,1348,1349],{},"Python",[1344,1351,1352],{},"Notes",[1354,1355,1356,1370,1380,1391],"tbody",{},[1341,1357,1358,1362,1365],{},[1359,1360,1361],"td",{},"3.22 LTR",[1359,1363,1364],{},"3.9",[1359,1366,1367,1369],{},[32,1368,336],{}," not available; dissolve always produces multipart output.",[1341,1371,1372,1375,1377],{},[1359,1373,1374],{},"3.28 LTR",[1359,1376,1364],{},[1359,1378,1379],{},"Behaviour matches this page.",[1341,1381,1382,1385,1388],{},[1359,1383,1384],{},"3.34 LTR",[1359,1386,1387],{},"3.12",[1359,1389,1390],{},"Baseline for this page.",[1341,1392,1393,1396,1398],{},[1359,1394,1395],{},"3.40 \u002F 3.44",[1359,1397,1387],{},[1359,1399,1400,1401,1403],{},"Unchanged; ",[32,1402,34],{}," gained additional aggregate functions.",[186,1405,1407],{"id":1406},"troubleshooting","Troubleshooting",[191,1409,1410,1418,1426,1432,1443,1457],{},[194,1411,1412,325,1415,1417],{},[197,1413,1414],{},"Everything collapsed into one feature.",[32,1416,328],{}," was empty, a bare string, or named a field that does not exist. It must be a list of exact field names.",[194,1419,1420,1423,1424,211],{},[197,1421,1422],{},"The area attribute is wrong."," Dissolve kept the first feature's value. Recalculate after dissolving, or use ",[32,1425,34],{},[194,1427,1428,1431],{},[197,1429,1430],{},"Hairline gaps run through the result."," Boundaries were not exactly shared. Snap first.",[194,1433,1434,325,1437,1439,1440,1442],{},[197,1435,1436],{},"The output has far more features than groups.",[32,1438,336],{}," is ",[32,1441,343],{},", or the grouping field has leading or trailing whitespace producing distinct values that look identical. Trim before dissolving.",[194,1444,1445,1448,1449,1452,1453,211],{},[197,1446,1447],{},"The operation is very slow."," Dissolve is geometry-heavy. Filter to the features you need first, and consider dissolving in a database where PostGIS's ",[32,1450,1451],{},"ST_Union"," handles it — see ",[26,1454,1456],{"href":1455},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fload-postgis-query-layer-pyqgis\u002F","Load a PostGIS Query Layer in PyQGIS",[194,1458,1459,1462],{},[197,1460,1461],{},"The result has null geometry for one group."," An invalid input geometry in that group. Fix geometries first.",[186,1464,1466],{"id":1465},"conclusion","Conclusion",[14,1468,1469,1471,1472,1474],{},[32,1470,804],{}," merges geometry by one or more fields and keeps an arbitrary attribute row per group; ",[32,1473,34],{}," does the same merge while computing sums, counts and other summaries you can defend. Snap near-coincident boundaries before dissolving, pass field names as a list, and recalculate any area or length that mattered.",[186,1476,1478],{"id":1477},"frequently-asked-questions","Frequently Asked Questions",[14,1480,1481,1484,1485,1488,1489,1492,1493,211],{},[197,1482,1483],{},"What is the difference between dissolve and merge?","\nDissolve combines features ",[18,1486,1487],{},"within"," one layer by attribute. Merging combines ",[18,1490,1491],{},"several layers"," into one without touching geometry — see ",[26,1494,1496],{"href":1495},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis\u002F","Merge Multiple Shapefiles in PyQGIS",[14,1498,1499,1502],{},[197,1500,1501],{},"Can I dissolve lines?","\nYes. Lines sharing the grouping value become one multipart line, which is how a road network is reduced to one feature per route number.",[14,1504,1505,1508,1509,1511],{},[197,1506,1507],{},"How do I dissolve everything into a single feature?","\nPass an empty ",[32,1510,328],{}," list. That is the intended behaviour, and the reason an accidental empty list is so easy to miss.",[14,1513,1514,1517],{},[197,1515,1516],{},"Does dissolve remove interior rings?","\nNo. Holes that are genuinely holes survive. A hole that exists only because two neighbours did not quite meet is a sliver, and snapping is the fix.",[14,1519,1520,1523,1524,1527],{},[197,1521,1522],{},"Why did my dissolve produce a multipart feature I did not expect?","\nBecause the group's polygons do not touch. That is the correct representation of a ward split by a river, and ",[32,1525,1526],{},"SEPARATE_DISJOINT: True"," is the option to reach for when each part should be its own feature instead.",[14,1529,1530,1533],{},[197,1531,1532],{},"Can I dissolve and keep the original features too?","\nNot in one call. Run the dissolve to a new layer and keep the source — which is the normal arrangement, since the dissolved layer is usually for display or reporting while the detailed one remains authoritative.",[14,1535,1536,1539],{},[197,1537,1538],{},"Should I dissolve before or after reprojecting?","\nBefore, if the source CRS is the one the data was digitised in — snapping tolerances are meaningful there. Reproject the dissolved result afterwards.",[186,1541,1543],{"id":1542},"related","Related",[191,1545,1546,1551,1557,1561,1567],{},[194,1547,1548,1550],{},[26,1549,29],{"href":28}," — the guide this recipe belongs to",[194,1552,1553],{},[26,1554,1556],{"href":1555},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fspatial-join-points-in-polygons-pyqgis\u002F","Spatial Join Points to Polygons in PyQGIS",[194,1558,1559],{},[26,1560,1496],{"href":1495},[194,1562,1563],{},[26,1564,1566],{"href":1565},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002F","Count Features by Attribute in PyQGIS",[194,1568,1569],{},[26,1570,1240],{"href":1239},[1572,1573,1574],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":222,"searchDepth":239,"depth":239,"links":1576},[1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587],{"id":188,"depth":239,"text":189},{"id":214,"depth":239,"text":215},{"id":354,"depth":239,"text":355},{"id":418,"depth":239,"text":419},{"id":841,"depth":239,"text":842},{"id":994,"depth":239,"text":995},{"id":1326,"depth":239,"text":1327},{"id":1406,"depth":239,"text":1407},{"id":1465,"depth":239,"text":1466},{"id":1477,"depth":239,"text":1478},{"id":1542,"depth":239,"text":1543},"Merge polygons that share an attribute with native:dissolve, keep the aggregates you need, avoid slivers from imprecise boundaries, and choose between dissolve and aggregate.","md",{"slug":1591,"type":1592,"breadcrumb":1593,"datePublished":1594,"dateModified":1594},"dissolve-features-by-attribute-pyqgis","article","Dissolve by Attribute","2026-08-10","\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fdissolve-features-by-attribute-pyqgis",{"title":5,"description":1588},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Fdissolve-features-by-attribute-pyqgis\u002Findex","SCovFU2JkwdJ5VZj_vseHlr1QxUW8Seg0N5a0XSGetM",1786401338550]