[GIS] Split line at a point with ArcGIS 10.1 Basic level license

arcgis-10.1arcgis-desktopgeoprocessing-framework

I have a point and polyline shapefile and want to split lines using points — want to emulate the precise functionality of Split Line at Point tool (Data Management). I have ArcGIS 10.1 Basic level license and don't want to use third party tools — want to achieve this by using Basic level license geoprocessing tools available within ArcGIS 10.1. Any suggestions would be very much appreciative.

Best Answer

I have taken a slightly different approach than @radouxju. Here is an arcpy based solution I've written really quickly that will split your lines at points. It requires having two feature classes and each of them should have a unique ID field (to uniquely identify each point and each line).

The script logic is as follows: create several dictionaries to store pointID and lineID and each feature geometry in arcpy.Geometry() instance >> evaluate whether a point is located on the line and if yes, split it. I have not built in yet the searching algorithm that will look around for the closest point yet. The output child lines will inherit all the attributes their parents have (no attribute splitting logic exists either). The output feature class will contain only those lines which had points located on them. You can use simple Select By Location to select and merge with the rest of lines if needed.

The script uses heavily arcpy.Geometry() methods which turned out to be really fast and powerful. For instance, symmetrical difference method that can be initiated on a geometry object in any license, is exposed as a GP tool only in ArcGIS Desktop Advanced.

The geometry of output lines will be practically identical to the source ones. The point located on a line will be buffered and then the result polygon is used to intersect the line and then resulted sublines are integrated to share the same vertex. The smaller buffer zone you create, the more precise the output splitted lines will be with the respect to the source line geometry. Running a buffer of just several cm zone will be more than enough for most applications (unless you are dealing with submillimeter precision data used in civil engineering for constructing bridges or something).

import arcpy
arcpy.env.overwriteOutput = True

line_fc = r"C:\GIS\Temp\test.gdb\Lines"
point_fc = r"C:\GIS\Temp\test.gdb\Points"
point_fc_desc = arcpy.Describe(point_fc)
in_spatial_reference = point_fc_desc.spatialReference

#can use CopyFeatures to write the geometries to disk when troubleshooting
#buffered_point_fc = r"C:\GIS\Temp\test.gdb\PointsBuffered"
#intersected_line_fc = r"C:\GIS\Temp\test.gdb\LineIntersected"
#symmetrical_difference_line_fc = r"C:\GIS\Temp\test.gdb\LineIntersectedSymmDiff"
single_part_splitted_lines = r"C:\GIS\Temp\test.gdb\SplittedLines"
total_splitted_lines = r"C:\GIS\Temp\test.gdb\TotalSplittedLines"
total_splitted_lines_attributed = r"C:\GIS\Temp\test.gdb\TotalSplittedLinesAttributed"

arcpy.TruncateTable_management(total_splitted_lines)

#--- reference dictionaries ----------------#
points_id_geometry_dict = {} #{pointID: pointGeometry}
lines_id_geometry_dict = {} #{lineID: lineGeometry}

search_cursor = arcpy.da.SearchCursor(point_fc,["PointID","SHAPE@"])
for point_feature in search_cursor:
    points_id_geometry_dict[point_feature[0]] = point_feature[1]
del search_cursor

search_cursor = arcpy.da.SearchCursor(line_fc,["LineID","SHAPE@"])
for line_feature in search_cursor:
    lines_id_geometry_dict[line_feature[0]] = line_feature[1]
del search_cursor
#-------------------------------------------#

points_list =[]
lines_list = []

dictionary_lines_points = {} #{lineID: pointID} or {lineID: (pointID, pointID,...)}

point_cursor = arcpy.da.SearchCursor(point_fc,["SHAPE@","PointID"])
line_cursor = arcpy.da.SearchCursor(line_fc,["SHAPE@","LineID"])

for point in point_cursor:
    point_geom_and_id = [point[0],point[1]]
    points_list.append(point_geom_and_id)

for line in line_cursor:
    line_geom_and_id = [line[0],line[1]]
    lines_list.append(line_geom_and_id)

del point_cursor
del line_cursor

for line in lines_list:
    for point in points_list:
        if line[0].contains(point[0]): #finding what points are on what lines
            print "LineID:", line[1], "PointID:", point[1]
            if not line[1] in dictionary_lines_points: #handling situations when multiple points are on the same line
                dictionary_lines_points[line[1]] = point[1] #lineid is key, point ids is value (can be a tuple)
            else:
                dictionary_lines_points[line[1]] = (dictionary_lines_points[line[1]],point[1]) #making tuple for "" line: (point ids) ""

for key_line in dictionary_lines_points.keys(): #iterating each line in the line_fc
    pointID = dictionary_lines_points.get(key_line) #getting what PointID have match to lineID

    if not isinstance(pointID,tuple):
        input_point_geom_object = points_id_geometry_dict.get(pointID) #obtain point geometry based on pointID
        multipoints = input_point_geom_object
    else:
        merged_point_geometries = arcpy.Array() #constructing a multipoint (if multiple points are on the same line)
        for pointID_element in pointID:
            input_point_geom_object = points_id_geometry_dict.get(pointID_element)
            merged_point_geometries.add(input_point_geom_object.centroid) #creating array of points
            multipoints = arcpy.Multipoint(merged_point_geometries,in_spatial_reference)

    line_geometry_object = lines_id_geometry_dict.get(key_line) #obtain line geometry based on LineID

    buffered_point = multipoints.buffer(0.1) #same units as the geometry
    intersected_line = buffered_point.intersect(line_geometry_object,2) #2 - polyline returned
    symmetrical_difference_line = intersected_line.symmetricDifference(line_geometry_object)
    arcpy.MultipartToSinglepart_management(symmetrical_difference_line,single_part_splitted_lines)
    arcpy.Integrate_management(single_part_splitted_lines,"0.1 Meters")
    arcpy.Append_management(single_part_splitted_lines,total_splitted_lines,"NO_TEST")
    arcpy.Delete_management(single_part_splitted_lines)

    arcpy.SpatialJoin_analysis(target_features=total_splitted_lines,
                               join_features=line_fc,
                               out_feature_class=total_splitted_lines_attributed,
                               join_operation="JOIN_ONE_TO_ONE",join_type="KEEP_ALL",
                               match_option="INTERSECT",search_radius="#",distance_field_name="#")

##TODO
#iterate through line features that did not have any points located on them (distanceTo geometry method / select by location)
#if there are any points located within the search distance >> move them on line and run the logic >> append to the output fc
#if no point features are located within the search distance >> append them directly to the output fc