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.
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
Unlock the full potential of ArcPy Cursors with our 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.
ArcPy Cursors enable you to streamline your GIS workflows, automate repetitive tasks, and witness the full potential of spatial data manipulation in ArcGIS Pro.
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)
################################################################################
3 thoughts on “Split Line at Vertices (Data Management) on a Basic License with ArcPy”
Pingback: Split Line at Point (Data Management) on a Basic License with ArcPy – FDM Geospatial Academy
Thank you so much for publishing this! It worked perfectly for my project.
Excellent, that’s great to hear.