I’m fairly inexperienced with python, but have been attempting to use it to automate a geoprocessing workflow. I’ve pieced together most of it, but I’m stuck with one section…
I have a shapefile with transect lines, and attribute fields TransNa (transect name) and surveydate (date of survey). I want to split each transect line into segments based on a user specified distance. The output file would have a number of segments representing each transect line. I also want to add a few fields to the output file.
Interval – sequential record number for each segment of a transect line. First segment of each transect line would be 1.
Segment – Sort records by surveydate, TransNa and Interval fields, then add a sequential record number for all records.
Mid_x – x coordinate for segment midpoint
Mid_y – y coordinate for segment midpoint
Any guidance on how to achieve this would be much appreciated!
Thanks
A few questions first:
How I name my transect segments if, lets say the transect number i 101, the segments are 101.1, 101.2, 101.3. The next transect would be 102.1, 102.2, etc. We had a project that has transects that were 25-25k that I had to re-segment into 1k lengths to I could buffer. Because the flight line transect are over several segments (after removing "off-trans" segments), I had to create the transects as "routes" and use the geometry to product the exact length needed).
If they are all one segment, the process is much easier, including some ArcGIS tools or see this Feature Line Split
This probably will not run, as is (for one, you would have to supply the input attributes) and I would already have my segments sorted and converted to routes (I have 2 prior scripts that do that), but depending on how far along you are will determine if any of this will help.
EDIT: cleaned up the code a little...
'''
Purpose:
to re-segment the flown-and-cleaned transects into new segments, e.g. 1k
and then rerun the buffers.
Requirements for inputs:
- Sorted transect FC, with all attributes
(including TRANSNO, SEGMENT, VIEWSIDE, TransSeg (?), sortSegs, distFrom, distTo)
- Route FC created from the above based on TRANSNO, and two fields: distFrom, distTo
Input parameters for this script:
- workspace, i.e. the FGDB to read/write from
- sorted transects FC
- routes
- new segment distance
Outputs:
- temp files (overwritten for each segment):
- freq (frequency table)
- Points (the points used for splitting)
- zTransExp (the individual exported sorted trans)
- zTransSplit (the individual split transects)
- zTranFinal (last individual FC before append)
- reSegment<newLength> (final, all transects appended)
###################################################################################
'''
import os, sys
import time
import arcpy
#from ADFGUtils import *
#from gpdecorators import *
def myMsgs(message):
arcpy.AddMessage(message)
print(message)
myMsgs("Running against: {}".format(sys.version))
# catch_errors decorator must preceed a function using the @ notation.
#@catch_errors
def main():
'''
Main function to resegment (retrofit) the flown transect to new segment length
'''
#setup variables
arcpy.env.overwriteOutput = True
# set parameter for script source, for template location
theScriptPath = arcpy.GetParameterAsText(0)
if not theScriptPath:
theScriptPath = r"\_data\_scripts"
theTemplatePath = os.path.join(theScriptPath,"Reports\RetroFit\ReSegment.gdb")
# set parameter for project directory
theProjectDir = arcpy.GetParameterAsText(1)
if not theProjectDir:
theProjectDir = r"\_data\_ProjectName"
else:
myMsgs(theProjectDir)
theProjectDir = os.path.realpath(theProjectDir)
# project folder, create other
fdrReports = os.path.join(theProjectDir, "Reports")
fdrRetroFit = arcpy.GetParameterAsText(6)
if not fdrRetroFit:
fdrRetroFit = "RetroFit"
else:
myMsgs(fdrRetroFit)
fdrRetroFit = os.path.join(fdrReports, fdrRetroFit)
outFGDB = os.path.join(fdrRetroFit, "ReSegment.gdb")
arcpy.env.workspace = outFGDB
inputSort = arcpy.GetParameterAsText(2)
if not inputSort:
inputSort = "transSorted" # Sorted transects
inputRoutes = arcpy.GetParameterAsText(3)
if not inputRoutes:
inputRoutes = "transRoutes" # ROUTES
newLength = arcpy.GetParameter(4)
if not newLength:
newLength = 1000
newLength= int(newLength)
thePts = "Point"
#createNewFinal = True
#if createNewFinal == True:
finalFC = "reSegA" + str(newLength)
if arcpy.Exists(finalFC):
myMsgs("deleting " + finalFC)
arcpy.Delete_management(finalFC)
if arcpy.Exists(finalFC):
myMsgs("still exists")
else:
myMsgs("doesnt exist")
spatialRef = arcpy.SpatialReference(3338) #arcpy.Describe(theTranFinal).spatialReference
myMsgs(" Creating/replacing the final FC: " + finalFC)
myMsgs(theTemplatePath)
# Use Search Cursor to process one TRANSNO and geometry at a time
sCur = arcpy.da.SearchCursor(inputRoutes, ["TRANSNO", "SHAPE@"])
for aRoute in sCur:
pointList = [] # initialize point array
theTransID = aRoute[0] # get TRANSNO string value
geo = aRoute[1] # get all transect geometry
lenTrans = geo.length # get the full length of the route
myMsgs("-->> ReSegmenting {0}".format(theTransID) +
"; total initial length: " + str(int(lenTrans)) + " ###")
# Calculate the number of transect at full newLength
calcSegs = int(lenTrans/newLength)
# Determine the length of the remainder segment
lastSeg = lenTrans - (newLength * calcSegs) # remainder length
#myMsgs ("No. New Segments: " + str(calcSegs))
# initialize the starting distance to point to beginning of line
startPtDist = 0
# initialze counter for creating new segments
newSeg = 0
# Determine whether last segment should be added to previous or standalone
if lastSeg < (newLength/2):
# if < newLength/2, added to previous segment --- else stands alone
calcSegs = calcSegs - 1 # number of new point pairs needed
# Collect points along route to be used for spliting
while newSeg < calcSegs:
startPtDist = startPtDist + newLength # 1st split point
newPT = geo.positionAlongLine(startPtDist)
pointList.append(newPT)
#myMsgs ("Distance along line: " + str(startPtDist))
newSeg = newSeg + 1
# Creating Split Point feature class, create and calc ptID field
# #myMsgs(" Creating point FC for transect: " + theTransID)
arcpy.CopyFeatures_management(pointList, thePts)
#myMsgs(" ..adding ptID field")
arcpy.AddField_management(thePts, "ptID", "SHORT")
#myMsgs(" ..calcing ptID = Objectid") # to help with the re-sorting process
#arcpy.CalculateField_management(thePts,"ptID","[OBJECTID]","VB","#")
arcpy.CalculateField_management(thePts,"ptID","!OBJECTID!","PYTHON_9.3","#")
'''
#################################################################################
Done creating the list of points to be used for splitting
#################################################################################
'''
# define an Expression for reselecting individual transect for processing
exprTran = arcpy.AddFieldDelimiters(inputSort, "TRANSNO") + " = " + "'%s'" %theTransID
# create a sorted Feature Layer of each individual transect
# #myMsgs(" Making new temp trans layer for the transect...")
# "trans_fl" the second arg below if want to be tmp, first arg in SJ
# -->>arcpy.MakeFeatureLayer_management(inputSort, "trans_fl", exprTran)
zTransExp = "in_memory/zTransExp"
arcpy.Select_analysis(inputSort, zTransExp, exprTran) # makes FC not layer
# Clean up the output be deleting fields with bogus data for the new segments
lstFieldsToDelete = "LENGTH;LATITUDE;LONGITUDE;XCOORD;YCOORD;PLANEDIST;TRANDIST;STARTTIME;ENDTIME;LENGTH_meters"
arcpy.DeleteField_management(zTransExp,lstFieldsToDelete)
# create new Feature Layer split by the points
#myMsgs(" ....splitting temp trans layer by Point to zTransSplit")
zTransSplit = "in_memory/zTransSplit"
arcpy.SplitLineAtPoint_management(zTransExp,thePts, zTransSplit,"5 Meters")
# add field for a new
arcpy.AddField_management(zTransSplit, "tmpSortID", "SHORT")
# creating a Frequency table of segments for use in cursors
freqFC = "freq"
arcpy.Frequency_analysis(zTransSplit, freqFC, "tmpSortID")
# Search cursor of segment numbers
sCur = arcpy.da.SearchCursor(freqFC, ["tmpSortID"])
for aRec in sCur:
freqSort = aRec[0]
#myMsgs(" Segment block: " + str(freqSort))
cntSegs = 0
# Update cursor of the segments
uCur = arcpy.da.UpdateCursor(zTransSplit, ["tmpSortID", "SHAPE@"])
for row in uCur:
geo = row[1]
firstPt = geo.firstPoint
firstPtX = int(firstPt.X)
firstPtY = int(firstPt.Y)
#myMsgs("X: " + str(firstPtX) + " Y: " + str(firstPtY))
# Search cursor of the split points
sCur = arcpy.da.SearchCursor(thePts, ["ptID", "SHAPE@"])
for rowPt in sCur:
geoPt = rowPt[1]
aPt = geoPt.firstPoint
aPtX = int(aPt.X)
aPtY = int(aPt.Y)
theID = rowPt[0]
#myMsgs(" x: " + str(aPtX) + " y: " + str(aPtY))
if cntSegs == 0:
theNewPtID = 0
elif ((firstPtX == aPtX) and (firstPtY == aPtY)): #lastPT == ptXY:
#myMsgs("hello")
theNewPtID = theID
cntSegs = cntSegs + 1
#myMsgs(str(theNewPtID))
row[0] = theNewPtID
uCur.updateRow(row)
# overwrites the same file but not before appending to a very final FC
theTranFinal = "zTranFinal"
#myMsgs(" ...resorting")
arcpy.Sort_management(zTransSplit,theTranFinal,"sortSegs ASCENDING;tmpSortID ASCENDING","UR")
'''
############################################################################
IF ALL SEGS IN PROPER ORDER, will assign the new numbers and related numbers
############################################################################
'''
# Adding a field for the new Segment numbers, with decimal for related segments
#myMsgs(" --> Adding a new field to relate short segments")
arcpy.AddField_management(theTranFinal,"finalSortID", "DOUBLE","3","1","#","#","NULLABLE","NON_REQUIRED","#")
# Creating new segment numbers for the new segments.
# If segment is final or "full" length, number is #.1, e.g. 1.1, 2.1, etc
# If multiple to make new length, decimal is incremented, e.g. 5.1, 5.2, etc
# #myMsgs(" Assigning the new (final) segment numbers")
newSegID = 1.1
prevLength = 0
currLength = 0
uCursor = arcpy.da.UpdateCursor(theTranFinal, ["finalSortID", "SHAPE@"])
for aRow in uCursor:
currLength = aRow[1].length
if currLength < (newLength - 1):
aRow[0] = newSegID
newSegID = (newSegID + .1)
prevLength = currLength + prevLength
if prevLength > (newLength - 1):
newSegID = (int(newSegID) + 1.1)
prevLength = 0
else:
aRow[0] = newSegID
newSegID = (int(newSegID) + 1.1)
prevLength = 0
uCursor.updateRow(aRow)
arcpy.AddField_management(theTranFinal, "finalSortInt", "SHORT")
#arcpy.CalculateField_management(theTranFinal,"finalSortInt","Int ( [finalSortID] )","VB","#")
arcpy.CalculateField_management(theTranFinal,"finalSortInt","int(!finalSortID!)","PYTHON_9.3","#")
# ---> Move the create to another location....check to see
# if it exists....othrwise, create from the zfinalSort
# created below
if not arcpy.Exists(finalFC):
arcpy.CreateFeatureclass_management(outFGDB, finalFC, "POLYLINE",theTranFinal, "SAME_AS_TEMPLATE", "SAME_AS_TEMPLATE", spatialRef)
#else:
#arcpy.CreateFeatureclass_management(outFGDB, finalFC, "POLYLINE", theTranFinal, "SAME_AS_TEMPLATE", "SAME_AS_TEMPLATE", spatialRef)
#createNewFinal = False
# append all the individual files to one. This will feed into FlipAndBuffer tool
myMsgs(" ...Appending to " + finalFC + " ###")
arcpy.Append_management(theTranFinal, finalFC, "TEST", "#", "#")
# add final uniqID field for final matchup of new segments to bear group locations
arcpy.AddField_management(finalFC, "newUniqID", "SHORT", 6, 0, 6)
arcpy.CalculateField_management(finalFC, "newUniqID", "!OBJECTID!","PYTHON_9.3","#")
# Clean up the output by deleting fields with bogus data for the new segments
lstFieldsToDelete = "sortSegs;distFrom;distTo;tmpSortID"
arcpy.DeleteField_management(finalFC,lstFieldsToDelete)
myMsgs(" Done deleting fields bogus fields")
# to add final layer...
myMsgs(" Creating a Feature Layer and adding it to the TOC")
fullFinalPath = os.path.join(outFGDB, finalFC)
arcpy.SetParameterAsText(5, fullFinalPath)
myMsgs('!!! Success !!!')
# End main function
if __name__ == '__main__':
main()
If you look at the UpdateCursor area, you can see that I am working with the first and last point of the segments. It is just as easy to grab mid point geometry.
UpdateCursor—Help | ArcGIS for Desktop
Geometry—Help | ArcGIS for Desktop
I you have code you want to post, make sure it is formatted correctly. To add to a comment here, at the menu at the top, .... More > Syntax Highlighter > Python then paste you code in the box.
Hope this helps.
To add-to/correct my comments above...
On collecting out flight lines, our transect already had a segment number, to I actually had a separate field for the segment number (I've done several of these script in the past). The graphic below shows part of the attribute table, and you can see how my segment 10.1 and 10.2 are over two flown segments but are related to get the 1km length (since they are the same transect). Hope this doesn't make it more complicated to understand. ??
I cleaned up the code a little in my first comment. The main lines you need to look at start at about line 106, whereI set up a cursor for the routes. If your transects are just on line, you should not need to create routes...I needed it since my are not contiguous line segments. Hopefully I have enough comments in the code to explain the rest.
tagging a few others for better exposure https://community.esri.com/groups/python-snippets?sr=search&searchId=627114b6-15b5-4912-8229-e0d252b... https://community.esri.com/community/gis/analysis/geoprocessing?sr=search&searchId=161c50b4-101a-4ad...