Convert Multipart to Singlepart Geometries in PyQGIS
A multipart feature is one row whose geometry is several disconnected shapes: an island group as one country, a dual carriageway as one road, three separate woods as one land parcel. It is a perfectly valid model and it breaks a surprising number of assumptions — that a feature has one centroid, that a length is a distance you could walk, that clicking a shape selects only what you clicked.
This recipe belongs to Geometry Operations & Predicates in PyQGIS. It covers exploding features into parts, checking whether a layer is multipart at all, what happens to attributes when one row becomes seven, aggregating back, and the operations that quietly require singlepart input.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- A layer that may or may not be multipart. Shapefiles and many GeoPackages declare a multi geometry type regardless of whether any feature actually has more than one part.
- A copy of the data, or a fresh output. Exploding changes the row count, and a stored id is no longer unique afterwards.
Check first
Declaring a layer multipart and containing multipart features are different things, and the check is worth making before running anything.
from qgis.core import QgsProject, QgsWkbTypes
layer = QgsProject.instance().mapLayersByName("woodland")[0]
declared = QgsWkbTypes.isMultiType(layer.wkbType())
actual = sum(1 for f in layer.getFeatures() if f.geometry().isMultipart()
and len(f.geometry().asGeometryCollection()) > 1)
print(f"declared multi: {declared}, features with >1 part: {actual}")
Breakdown: isMultiType() reads the layer's declared geometry type from the provider, which for a shapefile is almost always multi because the format has no singlepart polygon type. isMultipart() on a geometry says whether that geometry is a multi type, which can still be true with exactly one part — hence counting the parts rather than trusting the flag. A layer where declared is True and actual is 0 needs no explosion, and running one anyway costs a full rewrite for nothing.
Explode
The algorithm is a single call and does exactly what it says.
import processing
processing.run("native:multiparttosingleparts", {
"INPUT": layer,
"OUTPUT": "/data/output/woodland_parts.gpkg",
})
Breakdown: Every part becomes its own feature with a full copy of the original attributes and a new feature id. Nothing is aggregated, averaged or divided — which is the source of the classic error where a stored area or population field is now repeated across every part and any sum over the layer is inflated. Recalculating derived fields immediately after exploding is not optional:
processing.run("native:fieldcalculator", {
"INPUT": "/data/output/woodland_parts.gpkg",
"FIELD_NAME": "area_ha",
"FIELD_TYPE": 0,
"FIELD_LENGTH": 12,
"FIELD_PRECISION": 3,
"FORMULA": "$area / 10000",
"OUTPUT": "/data/output/woodland_parts_fixed.gpkg",
})
Breakdown: $area is computed from the geometry in the layer's CRS units, so this only produces hectares if the layer is in a metric projected CRS — in EPSG:4326 it produces square degrees, which are meaningless. FIELD_TYPE: 0 is a double. Recalculating rather than dividing by the part count is important because parts are rarely equal, and dividing a total by three assigns the same area to a five-hectare wood and a thirty-hectare one.
Keeping a link to the parent
Nothing in the exploded output records which feature a part came from, and that link is almost always wanted later — to join a summary back, to report which parcel a stray fragment belongs to, or to aggregate correctly. Adding it before exploding costs one algorithm call.
import processing
with_parent = processing.run("native:fieldcalculator", {
"INPUT": layer,
"FIELD_NAME": "parent_fid",
"FIELD_TYPE": 1,
"FIELD_LENGTH": 10,
"FORMULA": "$id",
"OUTPUT": "TEMPORARY_OUTPUT",
})["OUTPUT"]
exploded = processing.run("native:multiparttosingleparts", {
"INPUT": with_parent,
"OUTPUT": "TEMPORARY_OUTPUT",
})["OUTPUT"]
numbered = processing.run("native:fieldcalculator", {
"INPUT": exploded,
"FIELD_NAME": "part_area_ha",
"FIELD_TYPE": 0,
"FIELD_LENGTH": 12,
"FIELD_PRECISION": 3,
"FORMULA": "$area / 10000",
"OUTPUT": "/data/output/woodland_parts.gpkg",
})["OUTPUT"]
Breakdown: $id is the provider's feature id, which is stable for a file-based layer within a session but not guaranteed across a rewrite — so capturing it into a real field is exactly the point. FIELD_TYPE: 1 is an integer. Chaining three algorithms through TEMPORARY_OUTPUT means only the final file is written, which is the pattern described in chaining processing algorithms. Naming the recomputed field part_area_ha rather than overwriting area_ha keeps both numbers visible, so anyone reading the table can see that the parent total and the part areas are different things.
With the parent id present, aggregating back is a dissolve or aggregate on parent_fid, and a sanity check becomes possible: the sum of part_area_ha grouped by parent_fid should match the parent's original area to within rounding.
Going the other way
Aggregating parts back into multipart features is a dissolve on a key field.
processing.run("native:dissolve", {
"INPUT": "/data/output/woodland_parts_fixed.gpkg",
"FIELD": ["parcel_ref"],
"SEPARATE_DISJOINT": False,
"OUTPUT": "/data/output/woodland_merged.gpkg",
})
Breakdown: FIELD groups by one or more attributes and produces one feature per distinct combination, with disconnected parts held as a multipart geometry. SEPARATE_DISJOINT: False is what keeps them multipart; setting it True splits disjoint results back into singleparts and undoes the point of the exercise. Attributes other than the grouping fields are taken from an arbitrary feature in each group, so numeric totals must be rebuilt afterwards — native:aggregate is the algorithm that lets you specify a summary function per field and is the better choice when the attributes matter as much as the geometry.
Exploding in memory, without writing a file
Where the split is an intermediate step, materialising it in memory keeps the disk clean.
from qgis.core import QgsFeature, QgsVectorLayer, QgsProject
parts = QgsVectorLayer(
f"Polygon?crs={layer.crs().authid()}", "parts", "memory"
)
parts.dataProvider().addAttributes(layer.fields())
parts.updateFields()
buffer = []
for feature in layer.getFeatures():
for geometry in feature.geometry().asGeometryCollection():
part = QgsFeature(parts.fields())
part.setAttributes(feature.attributes())
part.setGeometry(geometry)
buffer.append(part)
parts.dataProvider().addFeatures(buffer)
parts.updateExtents()
Breakdown: asGeometryCollection() returns a list of single geometries and works on both multipart and singlepart input — a singlepart geometry yields a one-element list, so no branch is needed. Collecting into a list and calling addFeatures() once is far faster than adding one at a time, because each call is a provider transaction. updateExtents() at the end refreshes the layer's cached bounding box, without which the layer reports an empty extent and zooming to it does nothing.
What actually needs singlepart input
Several common operations behave oddly or fail on multipart data, and knowing which saves a lot of confused debugging.
Centroid-based labelling and any geometry generator using centroid() produce one point per feature, which for an island group lands in the sea. Length and perimeter sum across parts, so a "longest road" query returns the road with the most fragments. Many topology and network algorithms require singlepart lines and either error or silently use only the first part. And an elevation profile along a multipart route jumps between disconnected segments.
The rule of thumb: explode before anything that reasons about a feature as a single connected shape, and aggregate afterwards if the deliverable needs the original rows back.
QGIS version compatibility
The examples target QGIS 3.34 LTR (Python 3.12).
| QGIS version | Python | Notes |
|---|---|---|
| 3.16 LTR | 3.7 | native:multiparttosingleparts and native:dissolve present. |
| 3.22 LTR | 3.9 | native:aggregate offers per-field summary functions when dissolving. |
| 3.28 LTR | 3.9 | SEPARATE_DISJOINT added to the dissolve algorithm. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40+ | 3.12 | Improved handling of mixed single and multi geometries in one layer. |
Troubleshooting
- The layer totals tripled. Stored area or population fields were copied to every part. Recalculate them.
- Explode produced the same number of features. No feature had more than one part. Check with
asGeometryCollection()rather than the declared type. - Dissolve produced singleparts.
SEPARATE_DISJOINTisTrue. Set itFalse. - Attributes are wrong after dissolving. Non-grouping fields come from an arbitrary member. Use
native:aggregatewith explicit functions. - The memory layer has no extent.
updateExtents()was not called after adding features. - A centroid landed outside the shape. That is correct for a multipart or concave geometry. Explode first, or use
point_on_surface().
Conclusion
Check whether the layer really has multipart features before exploding, recalculate every derived field immediately afterwards, and use native:aggregate rather than native:dissolve when the attributes matter. Explode before anything that treats a feature as one connected shape, and remember that the geometry round trip is lossless while the attribute round trip is not.
Frequently Asked Questions
Can I explode only some features? Yes — filter first with a subset string or a selection, run the algorithm on the selection, and merge the result with the untouched remainder. There is no per-feature option in the algorithm itself.
Does exploding change the geometry at all? No. Each part is copied verbatim, so vertices, precision and validity are unchanged. Any invalid geometry in the input is still invalid in the output.
How do I keep a link back to the original feature? Add a field holding the original id before exploding. Nothing in the output records the parentage otherwise, and the new feature ids are unrelated to the old ones.
Should I store data as singlepart or multipart? Singlepart where each shape is independently meaningful, multipart where the parts genuinely are one thing — an archipelago that is one administrative unit. Storing multipart for convenience and exploding on every use is the arrangement that causes the most trouble.