Connecting Discontinuous Line Segments

6323
3
Jump to solution
01-28-2015 01:52 PM
DanielWalters
New Contributor

I have a shape file with a series of line segments that are discontinuous, I want to join them together so they form a continuous line based on a common attribute from the attribute table. I already understand how to merge the lines using Merge in the editing tool bar, but I'm not clear on if there is a way to get those segments to link up as shown in the image below (I have the broken lines on top, I want the continuous line below) without manually snapping them together point by point, as it is a large file with numerous such segments. Is there a tool in ArcGIS that can do this? Thanks.  (Note: I've seen some other postings mention the Dissolve tool, but I don't see how it applies here).

 

Lines.jpg

0 Kudos
1 Solution

Accepted Solutions
XanderBakker
Esri Esteemed Contributor

Hi Daniel Walters‌,

I saw your question and thought, let's give it a try...

Based on this input:

input.png

Is creates this output:

output.png

Using this code:

import math

def main():
    import arcpy

    fc_in = r"D:\Xander\GeoNet\Nueva carpeta\test.gdb\lines"
    fc_out = r"D:\Xander\GeoNet\Nueva carpeta\test.gdb\lines_out"
    tolerance = 150 # max distance to snap to a node

    # create dicts
    dct_lines = getPolylineDict(fc_in)
    dct_from, dct_to = getFromAndToNodes(dct_lines)

    # loop through polylines
    for oid, polyline in dct_lines.items():
        # search candidates from point
        pnt_from = dct_from[oid]
        dist_min = tolerance + 0.01
        pnt_found = None
        for oid_from, pnt in dct_from.items():
            if oid_from != oid:
                dist = calcDistance(pnt_from, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        for oid_to, pnt in dct_to.items():
            if oid_to != oid:
                dist = calcDistance(pnt_from, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        # change line from point
        if pnt_found != None:
            polyline = insertPointAtStart(polyline, pnt_found)
            dct_lines[oid] = polyline
            dct_from, dct_to = getFromAndToNodes(dct_lines)

        # search candidates to points
        pnt_to = dct_to[oid]
        dist_min = tolerance + 0.01
        pnt_found = None
        for oid_from, pnt in dct_from.items():
            if oid_from != oid:
                dist = calcDistance(pnt_to, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        for oid_to, pnt in dct_to.items():
            if oid_to != oid:
                dist = calcDistance(pnt_to, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        # change line end point
        if pnt_found != None:
            polyline = addPointAtEnd(polyline, pnt_found)
            dct_lines[oid] = polyline
            dct_from, dct_to = getFromAndToNodes(dct_lines)

    # create list of polylines
    lst_polylines = dct_lines.values()
    arcpy.CopyFeatures_management(lst_polylines, fc_out)


def getPolylineDict(fc_in):
    dct = {}
    flds = ("OID@", "SHAPE@")
    with arcpy.da.SearchCursor(fc_in, flds) as curs:
        for row in curs:
            dct[row[0]] = row[1]
    del row, curs
    return dct

def getFromAndToNodes(dct):
    dct_from = {}
    dct_to = {}
    for oid, polyline in dct.items():
        dct_from[oid] = polyline.firstPoint
        dct_to[oid] = polyline.lastPoint
    return dct_from, dct_to

def insertPointAtStart(polyline, pnt_found):
    sr = polyline.spatialReference
    lst_pnts = [pnt_found]
    for part in polyline:
        for pnt in part:
            lst_pnts.append(pnt)
    return arcpy.Polyline(arcpy.Array(lst_pnts), sr)

def addPointAtEnd(polyline, pnt_found):
    sr = polyline.spatialReference
    lst_pnts = []
    for part in polyline:
        for pnt in part:
            lst_pnts.append(pnt)
    lst_pnts.append(pnt_found)
    return arcpy.Polyline(arcpy.Array(lst_pnts), sr)

def calcDistance(pnt_to, pnt):
    return math.hypot(pnt_to.X - pnt.X, pnt_to.Y - pnt.Y)

if __name__ == '__main__':
    main()

If you have any questions, just let me know...

Kind regards, Xander

View solution in original post

3 Replies
XanderBakker
Esri Esteemed Contributor

Hi Daniel Walters‌,

I saw your question and thought, let's give it a try...

Based on this input:

input.png

Is creates this output:

output.png

Using this code:

import math

def main():
    import arcpy

    fc_in = r"D:\Xander\GeoNet\Nueva carpeta\test.gdb\lines"
    fc_out = r"D:\Xander\GeoNet\Nueva carpeta\test.gdb\lines_out"
    tolerance = 150 # max distance to snap to a node

    # create dicts
    dct_lines = getPolylineDict(fc_in)
    dct_from, dct_to = getFromAndToNodes(dct_lines)

    # loop through polylines
    for oid, polyline in dct_lines.items():
        # search candidates from point
        pnt_from = dct_from[oid]
        dist_min = tolerance + 0.01
        pnt_found = None
        for oid_from, pnt in dct_from.items():
            if oid_from != oid:
                dist = calcDistance(pnt_from, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        for oid_to, pnt in dct_to.items():
            if oid_to != oid:
                dist = calcDistance(pnt_from, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        # change line from point
        if pnt_found != None:
            polyline = insertPointAtStart(polyline, pnt_found)
            dct_lines[oid] = polyline
            dct_from, dct_to = getFromAndToNodes(dct_lines)

        # search candidates to points
        pnt_to = dct_to[oid]
        dist_min = tolerance + 0.01
        pnt_found = None
        for oid_from, pnt in dct_from.items():
            if oid_from != oid:
                dist = calcDistance(pnt_to, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        for oid_to, pnt in dct_to.items():
            if oid_to != oid:
                dist = calcDistance(pnt_to, pnt)
                if dist < dist_min:
                    dist_min = dist
                    pnt_found = arcpy.Point(pnt.X, pnt.Y)

        # change line end point
        if pnt_found != None:
            polyline = addPointAtEnd(polyline, pnt_found)
            dct_lines[oid] = polyline
            dct_from, dct_to = getFromAndToNodes(dct_lines)

    # create list of polylines
    lst_polylines = dct_lines.values()
    arcpy.CopyFeatures_management(lst_polylines, fc_out)


def getPolylineDict(fc_in):
    dct = {}
    flds = ("OID@", "SHAPE@")
    with arcpy.da.SearchCursor(fc_in, flds) as curs:
        for row in curs:
            dct[row[0]] = row[1]
    del row, curs
    return dct

def getFromAndToNodes(dct):
    dct_from = {}
    dct_to = {}
    for oid, polyline in dct.items():
        dct_from[oid] = polyline.firstPoint
        dct_to[oid] = polyline.lastPoint
    return dct_from, dct_to

def insertPointAtStart(polyline, pnt_found):
    sr = polyline.spatialReference
    lst_pnts = [pnt_found]
    for part in polyline:
        for pnt in part:
            lst_pnts.append(pnt)
    return arcpy.Polyline(arcpy.Array(lst_pnts), sr)

def addPointAtEnd(polyline, pnt_found):
    sr = polyline.spatialReference
    lst_pnts = []
    for part in polyline:
        for pnt in part:
            lst_pnts.append(pnt)
    lst_pnts.append(pnt_found)
    return arcpy.Polyline(arcpy.Array(lst_pnts), sr)

def calcDistance(pnt_to, pnt):
    return math.hypot(pnt_to.X - pnt.X, pnt_to.Y - pnt.Y)

if __name__ == '__main__':
    main()

If you have any questions, just let me know...

Kind regards, Xander

filibertogh
New Contributor

Hi XanderBarkker,

Thanks a lot for your python script. It helped me a lot with a project am working on. I am running the script on a file and currently having some error. Please see below:

 

filibertogh_0-1623250683177.png

Any help or input is welcomes. Thanks!

0 Kudos
larryzhang
Occasional Contributor III

nice job with python coding by Xander,

Also, nice to use the simple generalization tool called 'Extend Line' at ArcGIS Help (10.2, 10.2.1, and 10.2.2)

The following simple standalone is copied from the Help

# Name: ExtendLine.py
# Description:  Clean up street centerlines that were digitized without
#                      having set proper snapping environments

# import system modules
import arcpy
from arcpy import env

# Set environment settings
env.workspace = "C:/data"

# Make backup copy of streets feature class, since modification with
#  the Editing tools below is permanent
streets = "majorrds.shp"
streetsBackup = "C:/output/Output.gdb/streetsBackup"
arcpy.CopyFeatures_management(streets, streetsBackup)

# Trim street lines to clean up dangles
arcpy.TrimLine_edit(streets, "10 Feet", "KEEP_SHORT")

# Extend street lines to clean up dangles
arcpy.ExtendLine_edit(streets, "15 Feet", "EXTENSION")

0 Kudos