Split Line at Vertices (Data Management) on a Basic License with ArcPy

Table of Contents

Introduction

The Split Line at Vertices (Data Management) geoprocessing tool is only available in ArcGIS Pro with an Advanced license. Here’s how you can use ArcPy to achieve a similar output. While the Advanced tool will take a linear or polygon feature class as input, here, we will only look at splitting linear features and will revisit for a polygon at a later date. Check out the Esri documentation for more information on the tool here. This workflow was created using ArcGIS Pro 3.2.2. 

ArcPy for Data Management and Geoprocessing with ArcGIS Pro

Leverage ArcPy for geospatial data management workflows within ArcGIS Pro. Learn the fundamentals of utilising ArcGIS Pro geoprocessing tools with ArcPy for data management, conversions, and analysis. This course is packed with amazing content that will help you discover the power of automating tasks within the ArcGIS Pro environment. Take your ArcPy skills from beginner to snake charmer. A little code goes a long way, a little ArcPy provides you with an in-demand skill. Sign up now for our highly rated course.

Syntax

The Split Lines at Vertices tool takes two required parameters; in_features that represents the features to split at vertices, and out_feature_class, the feature class to create to store the split lines. 

				
					arcpy.management.SplitLine(
    in_features,
    out_feature_class
)
				
			

Import the arcpy module

ArcPy is a Python site package that enables automation, analysis, and management of geographic data within the ArcGIS software environment. 

				
					import arcpy
				
			

Define the parameters from the syntax

We require two parameters; in_features and out_feature_class.

				
					## input feature class (linear)
in_features = arcpy.GetParameterAsText(0)
 
## output feature class filepath
out_feature_class = arcpy.GetParameterAsText(1)
				
			

Required Python objects for the tool

Each requirement is commented for more information.

				
					## srs id of the in_features so we can assign the output the same
srs_id = arcpy.Describe(in_features).spatialReference.factoryCode

## this list will hold the names of the input fields in order
in_fld_names = [fld.name for fld in arcpy.ListFields(in_features) if fld.type not in ("Blob","Geometry","GlobalID","Guid","OID","Raster")]

## add field for accessing geometry
in_fld_names.append("SHAPE@")

## we need the OID field for the ORIG_ID output_field
oid_fld = [fld.name for fld in arcpy.ListFields(in_features) if fld.type=="OID"][0]
in_fld_names.insert(0, oid_fld)

## this will hold information for all line segments to create
## it will be a list of lists containing attributes and geometry
segments_lst = []

## dictionary to hold the key: OID, value: vertice points for each line.
vertices_dict = {}
				
			

Get all vertices per feature

We populate our vertices_dict variable with information where the key is the original feature OID and the value is a list of points that represent all vertices associated with that individual feature.

				
					## iterate through each record in the in_features feature class.
## we are only interested in the OID field and the SHAPE@ for accessing geometry.
with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@"]) as cursor:
    ## for each feature/record
    for row in cursor:
        ## this list will hold the point geometry for each vertex per OID
        points_list = []
        ## dig into each part of the geometry
        ## row[-1] is the SHAPE@ toke for accessing geometry
        for part in row[-1]:
                ## Step through each vertex in the feature
                for pnt in part:
                    if pnt:
                        ## append the point geometry into the point_list for the OID
                        points_list.append(arcpy.PointGeometry(arcpy.Point(pnt.X, pnt.Y, pnt.Z, pnt.M)))
        ## add the OID as the key in the vertices_dict, and the list of points (vertices) as the dictionary value.
        vertices_dict[row[0]] = points_list
				
			

The Main Event: Split Lines at Vertices

Now that we have all our vertices nicely packaged in a dictionary, lets split those lines. Each split line will maintain the attributes from the original line it was split from.

				
					## iterate through each linear record from the in_features feature class.
with arcpy.da.SearchCursor(in_features, in_fld_names) as ln_cursor:
    for ln in ln_cursor:
        ## get the start point of the line
        first_ln_xy = (ln[-1].firstPoint.X, ln[-1].firstPoint.Y)

        ## select points that belong to the line from the vertices dictionary
        pt_sel = vertices_dict[ln[0]]

        ## this list will hold the distances points are along a line to help
        ## chop up a line in order
        distances = []

        ## for each PointGeometry in the list
        for pt in pt_sel:
            ## get the X,Y of the point
            pt_xy = (pt.firstPoint.X, pt.firstPoint.Y)

            ## if the point is a start; do nothing
            if  pt_xy == first_ln_xy:
                pass

            ## otherwise, lets get the distance along the line for each point (vertex)
            ## along the line and append the distance to the list.
            else:
                distance = ln[-1].queryPointAndDistance(pt)[1]
                distances.append(distance)

        ## 0.0 is the start of the line
        start_dist = 0.0

        ## sequence number per split segment of each line
        ## the sequence number is added to the output as per Esri documentation
        seq_num = 1

        ## iterate through a sorted list of distances
        ## and cut the line at each distance from the start distance
        ## the start distance becomes the current distance for each iteration
        for distance in sorted(distances):
            segment = ln[-1].segmentAlongLine(start_dist, distance)
            ## if segment has no length move on to the next line, we have hit the end
            if segment.getLength('PLANAR', 'METERS') == 0.0:
                continue
            ## otherwise, lets get the attributes for the line segment.
            else:
                start_dist = distance
                ## the attributes from the original line
                segment_attributes = list(ln[0:-1])
                ## the segment geometry
                segment_attributes.append(segment)
                ## the sequence number of the segment for this line
                segment_attributes.append(seq_num)
                ## append the info above into our segments list
                segments_lst.append(segment_attributes)
                ## increase the sequence number for the next segment
                seq_num += 1
				
			

Create a temporary feature class and define schema

We will create a temporary linear feature class in the memory workspace.

				
					## create a linear feature class in memory using the in_features as a template.
temp_fc = arcpy.management.CreateFeatureclass("memory", "temp_lines", "POLYLINE",
    in_features, "SAME_AS_TEMPLATE", "SAME_AS_TEMPLATE", srs_id)
				
			

Next, we alter the schema to add in the ORIG_FID and ORIG_SEQ fields as per the Esri documentation for the Split Line at Vertices tool.

				
					## add the ORIG_FID field
arcpy.management.AddField(temp_fc, "ORIG_FID", field_type="LONG", field_is_nullable="NULLABLE")
## add the ORIG_SEQ field
arcpy.management.AddField(temp_fc, "ORIG_SEQ", field_type="LONG", field_is_nullable="NULLABLE")

## adjust our in_fld_names list to account for the added fields.
## and remove the OID field.
in_fld_names.remove(oid_fld)
in_fld_names.insert(0, "ORIG_FID")
in_fld_names.append("ORIG_SEQ")
				
			

Add the split line information to the temporary feature class

Insert the data into our linear feature class that is held in the memory workspace.

				
					## use the Insert Cursor to insert one segment at a time as a record
with arcpy.da.InsertCursor(temp_fc, in_fld_names) as i_cursor:
    for attributes in segments_lst:
        i_cursor.insertRow(attributes)
				
			

Write the output to disk and clean-up

Almost there! Let’s write the data to disk and clean-up the memory workspace.

				
					arcpy.conversion.ExportFeatures(temp_fc, out_feature_class)

arcpy.management.Delete(temp_fc)
				
			

Mastering ArcGIS Pro ArcPy Search, Insert, & Update Cursors

A comprehensive course that covers ArcPy cursors in exquisite detail. Unlock the full potential of ArcPy Cursors with an intensive and in-depth course. Designed for GIS professionals, analysts, and enthusiasts, this course delves into the intricacies of Search, Insert, and Update Cursors in the ArcPy library, empowering you to manipulate and manage spatial data with precision and efficiency. This course is accredited by the Association for Geographic Information (AGI)

Create the tool in ArcGIS Pro

Save your script, and now that we’ve done the heavy lifting, let’s head over to ArcGIS Pro and add our tool to a Toolbox. I’ve created a Toolbox (.atbx) in the aptly named folder called Toolbox, and named the Toolbox Advanced_Tools_with_Basic_License.atbx by simply right-clicking on the Toolbox folder and selecting New > Toolbox (.atbx)

Right-click on the Advanced_Tools_with_Basic_License.atbx and select New > Script. The New Script window will appear. In the General tab set Name to splitLineAtVertices, Label to Split Line at Vertices (Basic), and the Description to Split Line at Vertices using a Basic License.

In the Parameters tab set as per below. Set the Filter for the Input Features parameter to be Polyline Feature Type. Set the Output Feature Class with a Direction of Output

In the Execution tab, click the folder icon in the top-right corner and ad your saved Python script. Click OK. Run the tool with a linear feature class of your choice.

You can download the tool and other Advanced tools with a Basic license over on this page.

All the code in one place

You can find the entire code workflow below with links to important components in the documentation that were used.

				
					import arcpy

################################################################################
## ESRI Documentation:
##  https://pro.arcgis.com/en/pro-app/3.2/arcpy/functions/getparameterastext.htm
##  https://pro.arcgis.com/en/pro-app/3.2/tool-reference/data-management/split-line-at-vertices.htm
##  https://pro.arcgis.com/en/pro-app/3.2/arcpy/functions/describe.htm
##  https://pro.arcgis.com/en/pro-app/3.2/arcpy/functions/listfields.htm
##  https://pro.arcgis.com/en/pro-app/3.2/arcpy/data-access/searchcursor-class.htm
##  https://pro.arcgis.com/en/pro-app/3.2/arcpy/classes/pointgeometry.htm
##  https://pro.arcgis.com/en/pro-app/3.2/tool-reference/data-management/create-feature-class.htm
##  https://pro.arcgis.com/en/pro-app/3.2/tool-reference/data-management/add-field.htm
##  https://pro.arcgis.com/en/pro-app/3.2/arcpy/data-access/insertcursor-class.htm
##  https://pro.arcgis.com/en/pro-app/3.2/tool-reference/conversion/export-features.htm
##  https://pro.arcgis.com/en/pro-app/3.2/tool-reference/data-management/delete.htm
##
## Syntax:
##     arcpy.management.SplitLine(in_features, out_feature_class)
##
## ArcGIS Pro Version: 3.2.2
##
################################################################################

################################################################################
## USER INPUTS #################################################################

## input feature class (linear)
in_features = arcpy.GetParameterAsText(0)

## output feature class filepath
out_feature_class = arcpy.GetParameterAsText(1)

################################################################################
## TOOL OBJECT REQUIREMENTS ####################################################

## srs id of the in_features so we can assign the output the same
srs_id = arcpy.Describe(in_features).spatialReference.factoryCode

## this list will hold the names of the input fields in order
in_fld_names = [fld.name for fld in arcpy.ListFields(in_features) if fld.type not in ("Blob","Geometry","GlobalID","Guid","OID","Raster")]

## add field for accessing geometry
in_fld_names.append("SHAPE@")

## we need the OID field for the ORIG_ID output_field
oid_fld = [fld.name for fld in arcpy.ListFields(in_features) if fld.type=="OID"][0]
in_fld_names.insert(0, oid_fld)

## this will hold information for all line segments to create
## it will be a list of lists containing attributes and geometry
segments_lst = []

## dictionary to hold the key: OID, value: vertice points for each line.
vertices_dict = {}

################################################################################
## GET VERTICES ################################################################

## iterate through each record in the in_features feature class.
## we are only interested in the OID field and the SHAPE@ for accessing geometry.
with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@"]) as cursor:
    ## for each feature/record
    for row in cursor:
        ## this list will hold the point geometry for each vertex per OID
        points_list = []
        ## dig into each part of the geometry
        ## row[-1] is the SHAPE@ toke for accessing geometry
        for part in row[-1]:
                ## Step through each vertex in the feature
                for pnt in part:
                    if pnt:
                        ## append the point geometry into the point_list for the OID
                        points_list.append(arcpy.PointGeometry(arcpy.Point(pnt.X, pnt.Y, pnt.Z, pnt.M)))
        ## add the OID as the key in the vertices_dict, and the list of points (vertices) as the dictionary value.
        vertices_dict[row[0]] = points_list

################################################################################
## SPLIT LINES #################################################################

## iterate through each linear record from the in_features feature class.
with arcpy.da.SearchCursor(in_features, in_fld_names) as ln_cursor:
    for ln in ln_cursor:
        ## get the start point of the line
        first_ln_xy = (ln[-1].firstPoint.X, ln[-1].firstPoint.Y)

        ## select points that belong to the line from the vertices dictionary
        pt_sel = vertices_dict[ln[0]]

        ## this list will hold the distances points are along a line to help
        ## chop up a line in order
        distances = []

        ## for each PointGeometry in the list
        for pt in pt_sel:
            ## get the X,Y of the point
            pt_xy = (pt.firstPoint.X, pt.firstPoint.Y)

            ## if the point is a start; do nothing
            if  pt_xy == first_ln_xy:
                pass

            ## otherwise, lets get the distance along the line for each point (vertex)
            ## along the line and append the distance to the list.
            else:
                distance = ln[-1].queryPointAndDistance(pt)[1]
                distances.append(distance)

        ## 0.0 is the start of the line
        start_dist = 0.0

        ## sequence number per split segment of each line
        ## the sequence number is added to the output as per Esri documentation
        seq_num = 1

        ## iterate through a sorted list of distances
        ## and cut the line at each distance from the start distance
        ## the start distance becomes the current distance for each iteration
        for distance in sorted(distances):
            segment = ln[-1].segmentAlongLine(start_dist, distance)
            ## if segment has no length move on to the next line, we have hit the end
            if segment.getLength('PLANAR', 'METERS') == 0.0:
                continue
            ## otherwise, lets get the attributes for the line segment.
            else:
                start_dist = distance
                ## the attributes from the original line
                segment_attributes = list(ln[0:-1])
                ## the segment geometry
                segment_attributes.append(segment)
                ## the sequence number of the segment for this line
                segment_attributes.append(seq_num)
                ## append the info above into our segments list
                segments_lst.append(segment_attributes)
                ## increase the sequence number for the next segment
                seq_num += 1

################################################################################
## CREATE OUPUT FEATURE CLASS ##################################################

## create a linear feature class in memory using the in_features as a template.
temp_fc = arcpy.management.CreateFeatureclass("memory", "temp_lines", "POLYLINE",
    in_features, "SAME_AS_TEMPLATE", "SAME_AS_TEMPLATE", srs_id)

################################################################################
## CREATE OUPUT FEATURE CLASS SCHEMA ###########################################

## add the ORIG_FID field
arcpy.management.AddField(temp_fc, "ORIG_FID", field_type="LONG", field_is_nullable="NULLABLE")
## add the ORIG_SEQ field
arcpy.management.AddField(temp_fc, "ORIG_SEQ", field_type="LONG", field_is_nullable="NULLABLE")

## adjust our in_fld_names list to account for the added fields.
## and remove the OID field.
in_fld_names.remove(oid_fld)
in_fld_names.insert(0, "ORIG_FID")
in_fld_names.append("ORIG_SEQ")

################################################################################
## INSERT THE DATA #############################################################

## use the Insert Cursor to insert one segment at a time as a record
with arcpy.da.InsertCursor(temp_fc, in_fld_names) as i_cursor:
    for attributes in segments_lst:
        i_cursor.insertRow(attributes)

################################################################################
## WRITE TO DISK ###############################################################

arcpy.conversion.ExportFeatures(temp_fc, out_feature_class)

################################################################################
## CLEAN UP ####################################################################

arcpy.management.Delete(temp_fc)

################################################################################
				
			

1 thought on “Split Line at Vertices (Data Management) on a Basic License with ArcPy”

  1. Pingback: Split Line at Point (Data Management) on a Basic License with ArcPy – FDM Geospatial Academy

Leave a Comment

Your email address will not be published. Required fields are marked *