The goal of this project is to rotate address point labels (house number specifically) to be perpendicular to the street to which they belong (purely by street name comparison thus far). Inputs are an address point (point) feature class and a road centerline (polyline) feature class. The output is a point feature class with a field called "angle" populated with a calculated angle for labeling (the input address point feature class has this "angle" field as well but isn't calculated). My problem is with the logic within the “while” loop as I tried to account for cul-de-sacs and dead ends but found that it also included the ends of all feature parts. Is there a way to test if a feature part is connected to another feature part? Any help/guidance is appreciated (an illustration and sample data are provided). **NOTE** I had to make the street names match exactly at this point as I have not added tests to check for differences yet.
Code:
#Import modules
import arcpy
import math
from arcpy import env
#Overwrite output to the output feature class
env.overwriteOutput = True
#Assign variables
env.workspace = r"Local_FGDB_Path"
addptFC = "TEST_ADD_PTS"
clFC = "Street_Test"
OutFC = "Output_Add_Pts"
#See if in_memory layers exist - delete them if they do
if arcpy.Exists("addpts"):
arcpy.Delete_management("addpts")
if arcpy.Exists("CLs"):
arcpy.Delete_management("CLs")
#Create in memory feature layers of input feature classes
arcpy.MakeFeatureLayer_management(addptFC, "addpts")
arcpy.MakeFeatureLayer_management(clFC, "CLs")
#Create UpdateCursor
addptrows = arcpy.UpdateCursor("addpts", "", "", "STREET_B", "STREET_B A")
#Set variable to "shape" field
addptDesc = arcpy.Describe("addpts")
addptSFN = addptDesc.ShapeFieldName #Field that holds the shape info
clDesc = arcpy.Describe("CLs")
clSFN = clDesc.ShapeFieldName #Field that holds the shape info
#Begin loop for address points
for addptrow in addptrows:
addptValue = addptrow.getValue("STREET_B")
#Create searchCursor for temporary centerline feature layer
clrows = arcpy.SearchCursor("CLs", "", "", "ST_BOTH", "ST_BOTH A")
#Set variable for feature
addptFEAT = addptrow.getValue(addptSFN)
#Set variable for feature part
addptPART = addptFEAT.getPart()
#Set variables for "X" and "Y" coordinates for feature part
addptX = addptPART.X
addptY = addptPART.Y
#Create empty list to store midpoint distances and angles of parts - compare all values to find which feature
#with similar street name is closest to address point
closestMid = []
#Begin loop for centerlines
for clrow in clrows:
clValue = clrow.getValue("ST_BOTH")
vertexList = []
#Find all like street names between address points and centerlines
if addptValue == clValue:
#Set variable for feature
clFEAT = clrow.getValue(clSFN)
#Set variable for feature part
clPART = clFEAT.getPart(0)
#Set variables for "X" and "Y" coordinates for feature part
for clpartObj in clPART:
vertexList.append((clpartObj.X, clpartObj.Y))
vertexCount = len(vertexList)
if vertexCount < 2:
break #If only one vertex then exit loop (no midpoint to calculate)
z = 0
#Loop through feature parts and get coordinates for each vertex
while z + 2 <= vertexCount: #That is until you run out of pairs to count
#Get vertices for segment
x1 = vertexList[z][0]
y1 = vertexList[z][1]
x2 = vertexList[z + 1][0]
y2 = vertexList[z + 1][1]
#Calculate the midpoint of the line segment
midX = (x1 + x2)/2
midY = (y1 + y2)/2
#Calculate the distance from the address point to the midpoint of the part
distMid = math.sqrt(((addptX - midX)**2) + ((addptY - midY)**2))
if x1 == vertexList[0][0] or x2 == vertexList[len(vertexList)-1][0]:
#These delta values calculated between midpoint of feature part and address point
deltaX = midX - addptX
deltaY = midY - addptY
#Calculate label angle for address point
addptAngle = (57.2957795 * math.atan2(deltaY, deltaX))
else:
#These delta values calculated on the feature parts
deltaX = x2 - x1
deltaY = y2 - y1
#Calculate label angle for address point
#("90" added so that point angle is perpendicular to line angle)
addptAngle = (90 + (57.2957795 * math.atan2(deltaY, deltaX)))
#"closestMid" is the nested list storing:
# 1) Distance from address point to midpoint of feature part,
# 2) Address point label angle
closestMid.append((distMid, addptAngle))
z += 1
####Set up condition so that when "addptValue" does not have a match that
####addptValue is copied to a text file
#####NOTE: Make sure that an address point is retreived here if it isn't spelled correctly
#####or if it does not legitimately have a centerline in the county
elif addptValue != clValue:
clrows.next()
#Delete the list holding the coordinates of all vertices for all feature parts
del vertexList[:]
#This is where the closest value for "closestMid" is finalized
finalAngle = 0 #The angle the address point will take
closestValue = 0 #The closest midpoint-to-address-point distance
#Sort the list "closestMid" and assign the first value of midpoint distance and
#address point angle to variables
closestMid.sort()
closestValue = closestMid[0][0]
finalAngle = closestMid[0][1]
#Write the address point angle to the "angle" field and update the row within
#the temporary feature layer "addpts"
addptrow.angle = finalAngle
addptrows.updateRow(addptrow)
#Delete the list holding the midpoint values and address point label angle values
#and delete the row and cursor for the centerline feature layer
del closestMid[:]
del clrow, clrows
#Copy the temporary feature layer "addpts" to the feature class "OutFC"
arcpy.CopyFeatures_management("addpts", OutFC)
#Delete the row and cursor for the address point feature layer
del addptrow, addptrows