7

I am trying to dissolve multiple lines for stream networks into individual features. The dataset contains many stream networks so a complete dissolve of the whole dataset wont do as I would like to only dissolve streams that are in the same network (illustrated in the pictures below).

I've tried using the Unsplit Line tool and the Dissolve tool in ArcGIS 10. The tool will only join two lines together, i.e. lines that share a common end point. This is illustrated in the screenshots below.

enter image description here

Here is the orignal dataset with the lines split, a single line feature is highlighted

enter image description here

This is the same feature highlighted after using the unsplit/dissolve tool. As you can see it has only joined two lines together rather than all of the lines that are touching.

The features in the same network do not have a common attribute either.

  • The lines that are in the same network, do they contain a common attribute? – Joseph Feb 2 '15 at 14:47
  • No that's something else I should have mentioned they do not. I was hoping there was something that dissolved based on lines that were touching within the network. But as far as I can tell dissolve/unsplit only works on lines with shared end points. – Catchment_Jack Feb 2 '15 at 14:49
  • One potential solution could be to create a unique ID for each set of lines that are connected to one another, but I'm not sure how this could be done either? – Catchment_Jack Feb 2 '15 at 16:13
  • That is a possible solution but I don't have enough experience in using the Field Calculator to achieve such a result. There are a number of functions which could be used to give each connected set a unique ID. Hopefully others can advise! – Joseph Feb 2 '15 at 16:18
  • 1
    @GeoJohn Well, you can do that if you select them first. Both Dissolve and Merge respect selections, but if your dataset is particularly large you'd want a way to automate it. The buffer method is more efficient, but the alternative is to iterate through with a loop that selects, then selects those that intersect the current selection until nothing new is added to the set (essentially growing the selection), then merge/dissolve, and move on to the next. I do have to wonder what it would take to add another option to Dissolve so it would only do intersecting/connected features as multi-parts. – Chris W Feb 4 '15 at 20:24
8

I've devised a way of doing it with little bits from all of the other answers.

The scripts that @GeoJohn and @FelixIP have created may well work (if you have access to ArcGIS 10.1 and the database access functions), so if someone else had this problem please check those options out as well.

I did the following in the end:

  1. Created a very small buffer polygon around the lines (0.1 metres) to make sure there was crossover between the lines that were connected.

  2. Use the dissolve tool with the Unsplit option checked. This created individual polygons for each of the connected line groups.

  3. Create a unique ID for each polygon.

  4. Use the spatial join tool to add the unique polygon ids to each line that intersects or is within the polygons.

  5. Use the dissolve tool again to dissolve the lines based on the unique ID.

That has worked for me but like I said the scripting methods may be preferable for other people.

enter image description here

3

I think the best way to do this is in arcpy. The following is a complete script that will do what you need. It works by putting a small buffer around all of the stream features and indexing them(adds a field that is used later in the script to dissolve). The buffer allows for every part of a network, whether it is connected or not, to be considered part of it by using the buffer distance as a "tolerance". So... if the networks are really close together you can set the buffer distance lower.

import arcpy, os
from arcpy import env

arcpy.env.overwriteOutput = True

inFC = arcpy.GetParameterAsText(0)
outLoc = arcpy.GetParameterAsText(1)
outName = arcpy.GetParameterAsText(2)

newFC = os.path.join(outLoc, outName)

def buildWhereClause(table, field, value):
    fieldDelimited = arcpy.AddFieldDelimiters(table, field)
    fieldType = arcpy.ListFields(table, field)[0].type
    if str(fieldType) == 'String':
        value = "'%s'" % value
    whereClause = "%s = %s" % (fieldDelimited, value)
    return whereClause

bufferClass = os.path.join(outLoc, outName + "_Buffer")
singleClass = os.path.join(outLoc, outName + "_singlePart")

arcpy.Buffer_analysis(inFC, bufferClass, 500, "", "", "ALL")
arcpy.MultipartToSinglepart_management(bufferClass, singleClass)

arcpy.AddField_management(singleClass, "INDEX_ID", "LONG")

fields = arcpy.ListFields(singleClass)
fieldList = []

for nm in fields:
    fieldList.append(nm.name)

for field in fieldList:
    if field == "INDEX_ID":
        fieldCounter = 1
        with arcpy.da.UpdateCursor(singleClass, field) as cursor:
            for row in cursor:
                row[0] = fieldCounter
                cursor.updateRow(row)
                fieldCounter += 1
        del cursor
        del row

arcpy.CreateFeatureclass_management(outLoc, outName + "_Final", "POLYLINE")
finalFC = os.path.join(outLoc, outName + "_Final")

try:
    arcpy.AddField_management(finalFC, "ID_Match", "LONG")
except:
    pass

tempFC = os.path.join(outLoc, "tempFC_SE")
tempFC_2 = os.path.join(outLoc, "tempFC_SE_2")

arcpy.AddField_management(inFC, "ID_Match", "LONG")

fieldMatch = ["INDEX_ID", "OBJECTID"]
inFCMatch = ["ID_Match"]

arcpy.MakeFeatureLayer_management(singleClass, "singleClass_lyr")
arcpy.MakeFeatureLayer_management(inFC, "inFC_lyr")

with arcpy.da.SearchCursor("singleClass_lyr", fieldMatch) as cursor:
    for row in cursor:
        arcpy.SelectLayerByAttribute_management("singleClass_lyr", "CLEAR_SELECTION")
        arcpy.SelectLayerByAttribute_management("inFC_lyr", "CLEAR_SELECTION")
        where = buildWhereClause(singleClass, "OBJECTID", str(row[1]))
        arcpy.SelectLayerByAttribute_management("singleClass_lyr", "NEW_SELECTION", where)
        arcpy.CopyFeatures_management("singleClass_lyr", tempFC_2)
        arcpy.SelectLayerByLocation_management("inFC_lyr", "INTERSECT", tempFC_2)
        arcpy.CopyFeatures_management("inFC_lyr", tempFC)
        with arcpy.da.UpdateCursor(tempFC, inFCMatch) as cursor2:
            for row2 in cursor2:
                row2[0] = row[0]
                cursor2.updateRow(row2)
        del cursor2
        del row2
        arcpy.Append_management(tempFC, finalFC, "NO_TEST")
        arcpy.Delete_management(tempFC)
        arcpy.Delete_management(tempFC_2)
del cursor
del row

arcpy.Dissolve_management(finalFC, newFC, "ID_Match")

arcpy.Delete_management(finalFC)
arcpy.Delete_management(bufferClass)
arcpy.Delete_management(singleClass)

The results will not preserve fields from the original FC but with a little tweaking that is also possible.

3

If I understand question correctly and this is what you'd like to see enter image description here the script below will do

# Import arcpy module
import arcpy, traceback, os, sys
from arcpy import env
infc = r'D:\Scratch\river_cl.shp'
joined = r'D:\Scratch\s_joined.shp'
groupField="GROUP"
arcpy.overwriteoutput=1
try:
    def showPyMessage():
        arcpy.AddMessage(str(time.ctime()) + " - " + message)
    arcpy.SpatialJoin_analysis(infc, infc, joined, "JOIN_ONE_TO_MANY","KEEP_ALL", "", "BOUNDARY_TOUCHES")
    vFT=list(arcpy.da.TableToNumPyArray(joined,("TARGET_FID","JOIN_FID")))
    bigList=[]
    while (True):
        if len(vFT)==0:
            break
        first=list(vFT.pop(0))
        while (True):
            if len(vFT)==0:
                break
            if -1 not in first:
                m=0;toRemove=[]
                for ent in vFT:
                    f,t = ent
                    if f in first or t in first:
                        small = filter(lambda x: x not in first, ent)
                        first+=small
                        toRemove.append(m)
                    m+=1
                if len(toRemove)==0:
                    break
                toRemove.reverse()
                for m in toRemove:
                    vFT.pop(m)
            else:
                break
        bigList.append(first)

    result=arcpy.GetCount_management(infc)
    nStreams=int(result.getOutput(0))
    groups=[-1]*nStreams
    m=0
    for first in bigList:
        small = tuple(filter(lambda x: x not in [-1], first))
        arcpy.AddMessage('Group %i Contains FID(s) in %s' %(m,small))
        for n in small:
            groups[n]=m
        m+=1
    with arcpy.da.UpdateCursor(infc,groupField) as cursor:
        m=0
        for row in cursor:
            row[0]=groups[m]
            cursor.updateRow(row)
            m+=1
except:
    message = "\n*** PYTHON ERRORS *** "; showPyMessage()
    message = "Python Traceback Info: " + traceback.format_tb(sys.exc_info()[2])[0]; showPyMessage()
    message = "Python Error Info: " +  str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"; showPyMessage()            

Just replace 3 lines at the beginning, starting with infc=... by your shapefile, intermediate output and field to store Group No. Add this field to infc manually. Script will populate this field with unique group numbers. It can be used for dissolve

  • Thanks for the answer. This looks promising but unfortunately I only have ArcGIS 10 and thus don't have access to the arcpy.da module. I'll have a crack at modifying the code to work in ArcGIS 10. – Catchment_Jack Feb 3 '15 at 9:51
  • Ok I am not sure how to modify the code as there doesn't appear to be an equivalent function for the arcpy.da.TabletoNumPyArray function. Please forgive my limited coding knowledge! – Catchment_Jack Feb 3 '15 at 10:45
0

I found another method described in an ArcGIS Discussion Forum posting that I think would work and may be simpler:

creating a route from the line shapefile (unticking ignore spatial gaps) and then exploding the resulting route.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.