QGIS – Dissolve Consecutive Touching Line Segments with Common Value

aggregatefield-calculatorgeometry-by-expressionqgis

Dissolve Problem

I am trying to dissolve the grey line layer where the field "dis" ( "anzahl" || '-' || "heizlast") has the same value and the segments are touching ( end_point touches the next segments start_point).

The dissolve function ( with keep disjoint features separate) is not working for this specific example becuse the orange segment and the blue one touch in the end_point. For this kind of solution I thought of geometry by expression:

collect_geometries(
 aggregate(
            layer:='Aggregated',
            aggregate:='array_agg',  --or 'collect' I am not quite sure
            expression:=$geometry,
            filter:=intersects(end_point(@geometry), start_point(geometry(@parent))) 
            AND "dis"  = attribute(  @feature , 'dis')
        ))

However the result is not what I am expecting. I think I read it somewhere that the aggregate function is not working well with the geometry by expression. I am not quite sure that my function is good enough , maybe I need some kind of iteration where more than two segments are compared ? like array_foreach ? I am stuck ….

I just found quite the same question here and tried the expression:
Merging line segments using length rules in QGIS

union (
        $geometry, 
        eval (
            'overlay_touches (''line'', $geometry, filter:=id= '  || id || ' and $id <> '  || $id  || ' )[0]'
        )
    )

The problem is that the expression is combining two segments at a time and also generates NULL geometries and duplicates. The expression needs a finishing touch for iterating and collecting all the touching lines where the end_point match the start_point and has the same value. Also the expression that I copied is referencing to "id" ( int) and my field for dissolving "dis" is of type string wich is not working in this context.Here is the part where I need some help.

Result overlay_touches

Retrospectively
Dissolving line segments in QGIS if they touch and fall within same class?

I also found the exact the same question ( 8 years old) but the solution by the author himself can not be applicable in this case. I need a more general function otherwise it complicate the situation with the so called "higher" order and splitting by lines.

I tried something similar by myself:

Dissolve ( field: "dis" and keep disjoint feature separate )

In this case the 3 segments ( blue marked ) are being merged because they are touching. I can then generate the vertices of the segments and categorize this by the next expression:

count('Vertices', group_by:=geom_to_wkt($geometry))

Dissolving_Disjoint

Afterwards I would split the undesired merged segments from before with the vertices that are different then 1 and get what I need. BUT choosing this path it has again an undesirable effect for the remaining splitted lines which will changed the direction.

DirectionProblem

Best Answer

I deleted my previous answer because it didn't work in all cases.

You have to create for this example an field "dissolveID":

enter image description here

layer = iface.activeLayer() # MultiLineString

anzahl_idx = layer.fields().indexOf('anzahl') # int 
heizlast_idx = layer.fields().indexOf('heizlast') # int
dissolveID_idx = layer.fields().indexOf('dissolveID') # int

f_endpoint = QgsFeature()
neighbour_startpoint = QgsFeature()

with edit(layer):
    for f in layer.getFeatures():
        f['dissolveID'] = NULL
        layer.updateFeature(f)

dissolveID = 0

with edit(layer):
    for f in layer.getFeatures():
        f_refValue = str(f.attributes()[anzahl_idx]) + str(f.attributes()[heizlast_idx])
        f_id = f.id()
        f_dissolveID = f.attributes()[dissolveID_idx]
        f_points = [point for point in f.geometry().asMultiPolyline()]
        endpoint = f_points[0][-1]
        f_endpoint.setGeometry(QgsGeometry.fromPointXY(endpoint))
        
        for neighbour in layer.getFeatures():
            f_dissolveID = f.attributes()[dissolveID_idx]
            neighbour_dissolveID = neighbour.attributes()[dissolveID_idx]
            neighbour_refValue = str(neighbour.attributes()[anzahl_idx]) + str(neighbour.attributes()[heizlast_idx])
            neighbour_id = neighbour.id()
            neighbour_points = [p for p in neighbour.geometry().asMultiPolyline()]
            startpoint = neighbour_points[0][0]
            neighbour_startpoint.setGeometry(QgsGeometry.fromPointXY(startpoint))
            if neighbour_startpoint.geometry().intersects(f_endpoint.geometry()) and  \
                neighbour_refValue == f_refValue and \
                neighbour_id != f_id :
                if f_dissolveID == NULL and \
                    neighbour_dissolveID == NULL:
                    f['dissolveID'] = dissolveID
                    neighbour['dissolveID'] = dissolveID
                    layer.updateFeature(f)
                    layer.updateFeature(neighbour)
                    f_dissolveID = f.attributes()[dissolveID_idx]
                    neighbour_dissolveID = neighbour.attributes()[dissolveID_idx]
                elif f_dissolveID == NULL and \
                    neighbour_dissolveID != NULL:
                    f['dissolveID'] = neighbour_dissolveID
                    layer.updateFeature(f)
                    f_dissolveID = f.attributes()[dissolveID_idx]
                elif f_dissolveID != NULL and \
                    neighbour_dissolveID == NULL:
                    neighbour['dissolveID'] = f_dissolveID
                    layer.updateFeature(neighbour)
                    neighbour_dissolveID = neighbour.attributes()[dissolveID_idx]
                    
        dissolveID += 1

enter image description here

The code above checks if you have the same attributes and it is connected endpoint - startpoint. In some cases you could want to check azimut - angle to check a significant change in direction. Because in my example from above a simple endpoint to startpoint connection will also dissolve Line ID 3 and Line ID 6 if the have the same attributes. Therefore you must calculate the difference between the azimuth of the feature and the neighbour AND define a tolerance. This could be achieved with:

startpoint.azimuth(endpoint)

enter image description here

You could verify your defined tolerance in the first if statement.

enter image description here

Another thing that could be necessary to verify is the amount of nodes at the endpoint of the feature. 1 would be an endpoint. 2 a line and more than 2 a kind of crossing.

count = 0
for i in layer.getFeatures():
    if f_endpoint.geometry().intersects(i.geometry()):
        count += 1

enter image description here

And again you could verify your accepted node count in the first if statement.

If you want you can implement the python script as function in the graphic modeler.

Related Question