Unsplit/Dissolve multiple touching lines in Stream Network using ArcGIS Desktop?
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:
Created a very small buffer polygon around the lines (0.1 metres) to make sure there was crossover between the lines that were connected.
Use the dissolve tool with the Unsplit option checked. This created individual polygons for each of the connected line groups.
Create a unique ID for each polygon.
Use the spatial join tool to add the unique polygon ids to each line that intersects or is within the polygons.
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.
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.
If I understand question correctly and this is what you'd like to see
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