Connecting Discontinuous Line Segments

4920
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

View solution in original post

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
Regular Contributor

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