How to create line segments and add sequential ids

2378
3
11-20-2016 09:45 AM
WendyWright
New Contributor

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

0 Kudos
3 Replies
RebeccaStrauch__GISP
MVP Emeritus

A few questions first:

  1. version of software, and do you have an Advanced (ArcInfo) license?
  2. are your transects all one segment, or several?,
    1. and if several, does the distance need to be split across the segments.  For example, if first part is 450m and second is 550m, and your segments will be 1km (1000m) each, do you need the 5th segment to include 500m from the first, and 500m from the second

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.

RebeccaStrauch__GISP
MVP Emeritus

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.  ??

RebeccaStrauch__GISP
MVP Emeritus

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...