Skip to main content
Tweeted twitter.com/StackGIS/status/883064246199881732
deleted 16 characters in body
Source Link
mgri
  • 16.3k
  • 6
  • 48
  • 80

Thanks to all!

Thanks to all!

Source Link

How to iterate through vertices in line segments, and all possible branches in arcpy?

I have an interesting problem. I am using arcpy with arcGIS desktop 10.2.

I am given a starting point and distance to a fault on electrical lines. My task is to automate the process of plotting these points into a feature class and displaying them on a webmap for other non-GIS users to view. An example: I receive tabular info that has these two relevant pieces of information:

Starting point: Breaker #113 (Which I know the location of, and touches an electric line segment)
Fault Location: 18.3 Miles

It is important to note that the line features that I must be working on have a commonly shared attribute, called 'lineGroup', but there are many touching line features or segments that may be in that lineGroup. After I select the first line feature that touches my starting point, I iterate recursively through each vertice and measure the distance between them. I keep a running total of the distance, and the code plots a point when it reaches the fault location.

I can get my code to work fine when there is only a single line segment to iterate over, but my dataset contains many different line segments that I may have to iterate over to reach the fault distance. Also, there may be branches off of a line that head in a different direction. So I can potentially have multiple points plotted from one run of this script.

Here is the code I currently have, which works well for a single line segment. What I need help with is how to continue onward with the function when I hit the end of the first line segment, as well as how to branch off and find a possible fault location down a branch.

import arcpy, sys, os, math, time
from math import radians, sin, cos
print("Running against: {}".format(sys.version))
def main_funct(breaker_number, faultMiles):
    try:
        arcpy.env.overwriteOutput = True     
        faultDistance = faultMiles * 1609.34 # Mile to meter conversion rate       
        # Set feature class variables
        ohLine='Database Connections\T_OverheadTransmissionLine'
        breakers= r'C:\GIS\Data\temp_data\temp.gdb\Breakers' # Local copy for testing    
        fault_Locations= r'C:\GIS\Data\temp_data\temp.gdb\Fault_Locations'     
        # Make Feature layers for selections
        breaker_FL = 'in_memory\\breaker_FL'
        arcpy.MakeFeatureLayer_management(breakers, breaker_FL)   
        line_FL = 'in_memory\\line_FL'
        arcpy.MakeFeatureLayer_management(ohLine, line_FL)        
        # Select Starting Breaker and overhead line segments then get breaker XY 
        selectBreakerAndLine(breaker_FL, line_FL, breaker_number)        
        # Measure and total up distance between all vertices in the line segment
        totalDistance = 0
        startingGeometry = getPointXY(breaker_FL, arcpy.Describe(line_FL).spatialReference)
        (startPoint, endPoint, distance2EndPoint) = measureBetweenVertices(faultDistance, line_FL, startingGeometry)
        distance = faultDistance - distance2EndPoint
        angle = findAngleBetweenPoints(startPoint, endPoint)
        faultCoordinates = findPointCoordinates(startPoint, angle, distance)
        updateFaultLocations(fault_Locations, faultCoordinates, breaker_number, faultMiles, line_FL)                 
    except arcpy.ExecuteError:
        print arcpy.GetMessages(2)
    except Exception as e:
        print e.args[0]
    arcpy.AddMessage('\nCompleted')
def selectBreakerAndLine(breaker_FL, line_FL, breaker_number):
    expression = "EquipmentID = '{}'".format(breaker_number)
    arcpy.SelectLayerByAttribute_management(breaker_FL, 'NEW_SELECTION', expression)
    if int(arcpy.GetCount_management(breaker_FL).getOutput(0)) == 1: #Alter this later? For breakers on both ends of line with same ID
        arcpy.SelectLayerByLocation_management(line_FL, 'INTERSECT', breaker_FL)
        #print 'Count of selected Lines: {}'.format(int(arcpy.GetCount_management(line_FL).getOutput(0)))
    else:
        print('Error in selecting breakers')
def getPointXY(startPoint, spatial_ref):
    # Returns the X and Y of the starting point's XY
    shape = arcpy.da.SearchCursor(startPoint, ("SHAPE@",), where_clause=None, spatial_reference= spatial_ref ).next()[0]  
    point = arcpy.Point(shape.centroid.X, shape.centroid.Y)
    geometry = arcpy.PointGeometry(point)
    return geometry    
def measureBetweenVertices(faultDistance, line_FL, startingGeometry, skipList = [0] , totalDistance = 0, previousVertice = None):
    if totalDistance > faultDistance:
        print('Reached the fault location distance\nStart Point: ({}, {})\nEnd Point: ({}, {})\nTotal Distance: {}'.format(previousVertice.centroid.X, previousVertice.centroid.Y,\
            startingGeometry.centroid.X, startingGeometry.centroid.Y, totalDistance))    
        return (startingGeometry, previousVertice, totalDistance)                
    else:    
        skipList.append((startingGeometry.centroid.X, startingGeometry.centroid.Y))
        for row in arcpy.da.SearchCursor(line_FL, ['SHAPE@',], where_clause=None, spatial_reference= arcpy.Describe(line_FL).spatialReference):
            for part in row[0]:
                verticeDict = {}
                for pnt in part:
                    point = arcpy.Point(pnt.X, pnt.Y)
                    verticePoint = arcpy.PointGeometry(point)
                    measuredDistance = verticePoint.distanceTo(startingGeometry)
                    #print( 'Measured distance from start to end point = {}'.format(measuredDistance))
                    if measuredDistance == 0: 
                        pass
                    elif (verticePoint.centroid.X, verticePoint.centroid.Y) in skipList:
                        pass
                    else: 
                        verticeDict[verticePoint] = measuredDistance 
                totalDistance += (min(verticeDict.values()))
                closestVertice = (min(verticeDict.iterkeys(), key=(lambda key: verticeDict[key])))
                skipList.append((closestVertice.centroid.X, closestVertice.centroid.Y))   
        #print(skipList)
        print('Total Distance = {} meters, Required distance = {} meters | Original vertice location = ({}, {}) | Closest vertice location = ({}, {})\n{}'.format(totalDistance, faultDistance,\                                                                                                                                                       startingGeometry.centroid.X, startingGeometry.centroid.Y,\                                                                                                                                                       closestVertice.centroid.X, closestVertice.centroid.Y, '-'*200))
        # Recursive call
        return measureBetweenVertices(faultDistance, line_FL, closestVertice, skipList, totalDistance, startingGeometry)
def findIntersectingLines(startingGeometry, closestVertice, line_FL):
    #Make blank geometry points and an array to store them in for line vertices
    point = arcpy.Point()
    array = arcpy.Array()    
    #Add 1st point to array
    point.X = startingGeometry.centroid.X
    point.Y = startingGeometry.centroid.Y
    array.add(point)    
    #Add 2nd point to array
    point.X = closestVertice.centroid.X
    point.Y = closestVertice.centroid.Y
    array.add(point)    
    #Create a polyline object from array
    polyline = arcpy.Polyline(array)   
    arcpy.SelectLayerByLocation_management(line_FL, 'INTERSECT', polyline)
    print int(arcpy.GetCount_management(line_FL).getOutput(0))
def findAngleBetweenPoints(point1, point2):
    x1, x2 = point1.centroid.X, point2.centroid.X
    y1, y2 = point1.centroid.Y, point2.centroid.Y
    yDelta = y2 - y1
    xDelta = x2 - x1
    radians = math.atan2(yDelta, xDelta)
    degrees = math.degrees(radians)
    adjusted_degrees = (degrees + 360) % 360
    return adjusted_degrees - 90
def findPointCoordinates(startPoint, angle, distance):
    #Find point from an angle and a distance
    origin_x = startPoint.centroid.X
    origin_y = startPoint.centroid.Y    
    # calculate offsets with light trig
    (disp_x, disp_y) = (distance * sin(radians(angle)),\
                        distance * cos(radians(angle)))
    (end_x, end_y) = (origin_x + disp_x, origin_y + disp_y)
    return (end_x, end_y)
def updateFaultLocations(fault_Locations, faultCoordinates, breakerNum, distanceToFault, line_FL):
# Create an edit session and use an updateCursor to insert a row with point geometry for new breaker
    edit = arcpy.da.Editor(os.path.dirname(fault_Locations))
    edit.startEditing(False, True)
    edit.startOperation()                
    updateCursor = arcpy.da.InsertCursor(fault_Locations, ["SHAPE@XY", 'X', 'Y', 'BreakerNumber', 'DistanceFromBreaker',],)
    updateCursor.insertRow([faultCoordinates, faultCoordinates[0], faultCoordinates[1], breakerNum, distanceToFault])
    edit.stopOperation()
    edit.stopEditing(True)
    arcpy.Snap_edit(fault_Locations, [[line_FL, "EDGE", "5 Meters"]])
    return   
# This test allows the script to be used from the operating
# system command prompt (stand-alone), in a Python IDE, 
# as a geoprocessing script tool, or as a module imported in
# another script
if __name__ == '__main__':
    # Arguments are optional
    argv = tuple(arcpy.GetParameterAsText(i)
        for i in range(arcpy.GetArgumentCount()))
    #main_funct(*argv)
    main_funct(715, 18.752) #Test to show that it works

Thanks to all!