QGIS – How to Merge Adjacent Lines in QGIS

dissolvelinepyqgispythonqgis

In QGIS, I want to merge features lines that same attribute value and are adjacent. What characterizes a line to be adjacent to another is the final vertex of a line to be coincident with the initial vertex of another line.

The Dissolve command does not consider the adjacency criterion. The tool merged features that have equal attribute value. The figure shows an example, where numbers represent the IDs of each line. There are 3 lines that have the attribute value equal to 17. That way, if they have the same attribute and are adjacent then should be merged. There are also 2 lines that have the attribute value equal to 22, but they are not adjacent and so should not be merged.

The problem of the Dissolve tool in QGIS is that the criterion is based only on the value of the attribute. In this way, when executing the tool, the lines 22 will be unified creating multipart.

Another tool is the Singleparts to multipart which also does not consider adjacency. Would there be another way to merge lines in QGIS?

I know that in ArcGIS the Dissolve tool considers adjacency if "Create multipart features" option is unchecked. I'm looking for something similar.

enter image description here

Best Answer

You may try to use this code as a new script from Processing Toolbox (there's a pure Python function and I isolated it for the sake of clearness):

##Lines=vector line

from qgis.core import *

def find_adjacent(selected_ids): # for finding adjacent features
    outlist = []
    outinds = []
    outset = set()
    for j, l in enumerate(selected_ids):
        as_set = set(l)
        inds = []
        for k in outset.copy():
            if outlist[k] & as_set:
                outset.remove(k)
                as_set |= outlist[k]
                inds.extend(outinds[k])
        outset.add(j)
        outlist.append(as_set)
        outinds.append(inds + [j])
    outinds = [outinds[j] for j in outset]
    del outset, outlist
    result = [[selected_ids[j] for j in k] for k in outinds]
    return result

layer = processing.getObject(Lines)
crs = layer.crs().toWkt()

# Create the output layer
outLayer = QgsVectorLayer('Linestring?crs='+ crs, 'snapped' , 'memory')
prov = outLayer.dataProvider()
fields = layer.pendingFields()
prov.addAttributes(fields)
outLayer.updateFields()

already_processed = []
for feat in layer.getFeatures():
    attrs = feat.attributes()
    geom = feat.geometry()
    curr_id = feat["ID"]
    if curr_id not in already_processed:
        query = '"ID" = %s' % (curr_id)
        selection = layer.getFeatures(QgsFeatureRequest().setFilterExpression(query))
        selected_ids = [k.geometry().asPolyline() for k in selection]
        adjacent_feats = find_adjacent(selected_ids)
        for f in adjacent_feats:
            first = True
            for x in xrange(0, len(f)):
                geom = (QgsGeometry.fromPolyline([QgsPoint(w) for w in f[x]]))
                if first:
                    outFeat = QgsFeature()
                    outFeat.setGeometry(geom)
                    outGeom = outFeat.geometry()
                    first = False
                else:
                    outGeom = outGeom.combine(geom)
            outFeat.setAttributes(attrs)
            outFeat.setGeometry(outGeom)
            prov.addFeatures([outFeat])
        already_processed.append(curr_id)
    else:
        continue

# Add the layer to the Layers panel
QgsMapLayerRegistry.instance().addMapLayer(outLayer)

You only need to select your input layer and the dissolving-by-adjacency operation is automatically done. I assumed to order the features using the ID field (I called it "ID", but remember to change it with the real name in the query = '"ID" = %s' % (curr_id) line ). The result is a new linestring memory layer.

Let me explain it with an example. Starting from this layer (12 features, one label for each feature):

enter image description here

I obtain a new memory layer with 9 features (one label for each feature):

enter image description here

Related Question