I am currently attempting to create a Python Add_in tool which will perform a set of geoprocessing operations resulting in new values for a specific !shape.area!acres calculation on a new layer and then update rows in an existing feature class with these new values. I am using da.UpdateCursor as my edit tool to update the rows in the feature class from a dictionary of the new values. Basically, my script runs through all of the geoprocessing operations just fine and then either stops or skips over the da.UpdateCursor w/out throwing any errors. The last MessageBox trap that fires is right before the edit.startEditing, after that the code stops in the mxd w/ no errors.
I am open to any suggestions as to modifying the process as I am still fairly new to python programming and many of you might find my methods a little clunky. Also, this a trial tool for intended for later use on a versioned Feature Class in SDE once I get the process and methods understood.
I am using ArcGIS 10.2.1 for Desktop, with Windows 7.
# Import arcpy module
import arcpy, traceback
import pythonaddins
import os, sys
from arcpy import env
arcpy.env.overwriteOutput = True
arcpy.env.addOutputsToMap = False
workspace = r'C:\TEST\GEODATABASE.mdb'
arcpy.env.workspace = workspace
edit = arcpy.da.Editor(arcpy.env.workspace)
class Net_Stands(object):
"""Implementation for Net_Stands_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
try:
self.mxd = arcpy.mapping.MapDocument('current')
df = self.mxd.activeDataFrame
layers = arcpy.mapping.ListLayers(self.mxd)
Stands = arcpy.mapping.ListLayers(self.mxd, "StandsMaintenance", df)[0]
CompanyRoads = arcpy.mapping.ListLayers(self.mxd, "roads", df)[0]
layers = arcpy.mapping.ListLayers(self.mxd)
#creates Stands layer as temp Stands_lyr
arcpy.MakeFeatureLayer_management(Stands,"Stands_lyr")
#defines layer as temp Stands_lyr
Stands_temp = arcpy.mapping.Layer("Stands_lyr")
#creates a NetStands_Temp to cut the roads_buffer out of
arcpy.MakeFeatureLayer_management(Stands_temp, "NetStands_Temp")
#defines layer as temp NetStands_Temp
NetStands_Temp = arcpy.mapping.Layer("NetStands_Temp")
#From the roads feature class in the mxd, it makes a temp TRACT_BOUNDARY in the mxd.
arcpy.MakeFeatureLayer_management(CompanyRoads,"CompanyRoads_lyr")
#defines layer as temp TRACTS_BOUNDARY
CompanyRoads_temp = arcpy.mapping.Layer("CompanyRoads_lyr")
#Calculates the buffer distance based on value in Row_Feet into meters
arcpy.CalculateField_management(CompanyRoads_temp,"ROW_METERS_HALF","!ROW_FEET! * 0.1524","PYTHON_9.3","#")
#creates the buffer layer based on CompanyRoads_temp
mybuffer = arcpy.Buffer_analysis(CompanyRoads_temp,"#","ROW_METERS_HALF","FULL","ROUND","ALL","#")
#Erases the area that from mybuffer that overlap with Stands_temp
Erase_FC = arcpy.Erase_analysis(Stands_temp, mybuffer.getOutput(0))
#Recalculates the polygon shape since the roads buffer has been erased
arcpy.CalculateField_management(Erase_FC.getOutput(0),"NET_ACRES","!shape.area@acres!","PYTHON_9.3","#")
#Marker, this is the last line of code that works.
saysomething = pythonaddins.MessageBox("Completed Net_Acres Processing", "Clicked", 0)
print saysomething
#Start Editing
edit.startEditing(True,False)
edit.startOperation()
try:
joinfields = ['STID', 'NET_ACRES']
joindict = {}
with arcpy.da.SearchCursor(out_fc, joinfields) as rows:
for row in rows:
#joindict.append(row[0]),joindict.append(row[1])
joinval = row[0]
val1 = row[1]
joindict[joinval]= [val1]
print pythonaddins.MessageBox((row[0]) + " " + str(row[1]) + " joindict", "Joindict", 1)
del row, rows
with arcpy.da.UpdateCursor(Stands, joinfields) as recs:
for rec in recs:
keyval = rec[0]
if joindict.has_key(keyval):
print pythonaddins.MessageBox((rec[1]) + " rec" + str(rec[1]) + " rec"+ str(joindict[keyval]) + " joindict", "In the Update", 1)
rec[1] = joindict[keyval]
#print rec
recs.updateRow(rec)
del rec, recs
except Exception, e:
# If an error occurred, print line number and error message
tb = sys.exc_info()[2]
print pythonaddins.MessageBox("Line %i" % tb.tb_lineno)
print pythonaddins.MessageBox(e.message)
#print e.message
exceptions = pythonaddins.MessageBox(arcpy.GetMessages())
print exceptions
edit.stopOperation()
edit.stopEditing(True)
print pythonaddins.MessageBox("complete")
arcpy.RefreshActiveView()
arcpy.RefreshTOC()
arcpy.Delete_management(out_fc)
arcpy.Delete_management(Erase_FC)
arcpy.Delete_management(mybuffer_temp)
code_errors = pythonaddins.MessageBox(arcpy.GetMessages(), "Code Errors", 1)
print code_errors
except arcpy.ExecuteError:
lasterrors = pythonaddins.MessageBox(arcpy.GetMessages(), "WTF_Except Errors",1)
print lasterrors
-
Can you verify that the cursor is actually reading data? i.e with arcpy.da.SearchCursor("out_fc", ["OBJECTID"]) as cursor: rows = {row[0] for row in cursor}, then print len(rows)Barbarossa– Barbarossa2015年07月21日 18:17:32 +00:00Commented Jul 21, 2015 at 18:17
-
When executing this code in pyscripter, not as a Python Add-in, and with hard coded mxd, the da.SearchCursor on out_fc will return the proper values. However, for me to print the da.UpdateCursor, I must change the it to a da.SearchCursor and then take out the start/stop editing lines. This is because it will throw an error on the "for rec in recs" line, saying that edits can't be made in the session. So I haven't yet been able to fully execute the da.UpdateCursor, but the da.SearchCursor returns.kdevine– kdevine2015年07月21日 19:43:33 +00:00Commented Jul 21, 2015 at 19:43
2 Answers 2
After many trials and tribulations, I have got a working code with many bells and whistles that I feel obliged to share since they are a culmination of many 'stolen' snippets obtained from this site. I have included comments in much of the script to describe what is going on, so hopefully that is helpful. Basically, I was able to get both a arcpy.UpdateCursor and a arcpy.da.UpdateCursor to work. One of the trickiest aspects was for the code to be able to edit a workspace that was dynamic, in that if they were direct editing a Versioned SDE or if they had 'Checked Out' their data from SDE and working locally. The code had to account for this, but not all of my wishes were granted. The code will kick back a message if the user is NOT in edit mode already ONLY when in SDE. If they are using a local GDB, then the code still runs, but the edits are essentially not written...The new numbers appear in the attribute table, however, if you 'Reload Cache', they revert back and the edits aren't saved regardless. If already the user has already initiated an edit session, the edits aren't saved until the user "Saves Edits". Furthermore, if the SDE connection is not the 'hard coded' SDE_versioned Connection I'm assuming, a DialogMessage will appear for them to choose the Database Connection which must match the intended lyr. If not, it will loop until they choose the correct one. Also, this is all done "in_memory" and is lightening fast when run on a local GDB. So here goes:
class NoFeatures(Exception):
pass
import arcpy, traceback
import pythonaddins
import os, sys
import arcpy.toolbox
from arcpy import env
arcpy.env.overwriteOutput = True
#If you want to see the "in_memory/Layers being created, change below to "True"
#...then you must "#" comment out the Delete_Management("in_memory") at the bottom of code for them to persist in the TOC.
arcpy.env.addOutputsToMap = False
class Net_Stands(object):
"""Implementation for Net_Stands_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = True
def onClick(self):
try:
self.mxd = arcpy.mapping.MapDocument('current')
df = self.mxd.activeDataFrame
Stands = arcpy.mapping.ListLayers(self.mxd, "ForestStands", df)[0]
layers = arcpy.mapping.ListLayers(self.mxd)
#This will determine what the data connection is, either to a versioned SDE or Checkout GBD.
fullPath = os.path.dirname(arcpy.Describe(Stands).catalogPath)
try:
# Create a Describe object for an SDE database
try:
#If newDir is a directory, then it is a GDB, if it is not a GDB, then the code will go to the 'else' where is connects with the SDE. This will not enable editing on .mdb
if os.path.isdir(str(fullPath.rsplit('\\',1)[0])):
workspace = os.path.dirname(fullPath)
arcpy.env.workspace = workspace
if arcpy.Exists(workspace):
arcpy.Compact_management(workspace)
else:
pass
#pythonaddins.MessageBox(arcpy.GetMessages(2) + "I see you have Checked-Out Data", "Messages",1)
elif (str(fullPath.rsplit('\\',1)[1])) == "Hard-Coded Versioned SDE.sde":
sdeconnect = "Database Connections\Whats in the ArcCatalog Connection.sde"
workspace = sdeconnect
arcpy.env.workspace = workspace
else:
loop = True
while loop:
paramWorkspace = pythonaddins.OpenDialog('Choose Database Connection',False, r'','Open')
paramdesc = arcpy.Describe(paramWorkspace)
if not (paramWorkspace == (str(fullPath.rsplit('\\',1)[0]))):
pythonaddins.MessageBox("Please Choose Database Connections for ForestStands", "Database Connections", 1)
loop = True
continue
else:
pythonaddins.MessageBox("You Have Chosen Wisely", "Database Connections", 1)
workspace = paramWorkspace
arcpy.env.workspace = workspace
loop = False
break
except:
pass
except:
pythonaddins.MessageBox(arcpy.GetMessages(2) + " Failed to connect to " + fullPath + " Please Create a new Database Connection", "Connection Failure",1)
sys.exit
#Now that the connection has been made:
try:
fields = ("OBJECTID")
#This da.UpdateCursor will cause an SDE Editor to fail if not already editing, thus initiating the "You are not in Editor" msg.
#...you must be in Editor to allow for multiple da.UpdateCursor on a layer.
newCur = arcpy.da.UpdateCursor(Stands, fields)
with newCur as cursor:
for row in cursor:
break
try:
if arcpy.Exists(os.path.join(fullPath, "CompanyRoads")):
#Retrieves CompanyRoads from Workspace of ForestStands if in a GDB
fc = (os.path.join(fullPath,"CompanyRoads"))
memoryRoads = "in_memory" + "\\" + "CompanyRoads"
arcpy.CopyFeatures_management(fc, memoryRoads)
memoryRoads_lyr = arcpy.mapping.Layer(memoryRoads)
arcpy.RefreshActiveView
arcpy.RefreshTOC
elif arcpy.Exists("SDE_FeatureDataset.GISLOADER.CompanyRoads_Maintenance"):
#Retrieves CompanyRoads_Maintenance from Workspace of ForestStands if in a SDE
fc = ("SDE_FeatureDataset.GISLOADER.CompanyRoads_Maintenance")
memoryRoads = "in_memory" + "\\" + "CompanyRoads"
arcpy.CopyFeatures_management(fc, memoryRoads)
memoryRoads_lyr = arcpy.mapping.Layer(memoryRoads)
arcpy.RefreshActiveView
arcpy.RefreshTOC
#pythonaddins.MessageBox("memoryRoads", "Memory Roads",1)
elif arcpy.Exists(paramdesc):
#Retrieves CompanyRoads_Maintenance from Workspace choosen by user
fc = (paramdesc + ".CompanyRoads")
memoryRoads = "in_memory" + "\\" + "CompanyRoads"
arcpy.CopyFeatures_management(fc, memoryRoads)
memoryRoads_lyr = arcpy.mapping.Layer(memoryRoads)
arcpy.RefreshActiveView
arcpy.RefreshTOC
else:
#No Features Found
raise NoFeatures()
except NoFeatures:
pythonaddins.MessageBox(arcpy.GetMessages(2) + " No CompanyRoads Found", "CompanyRoads lyr Missing",1)
#This try/except on Editor will initiate an edit session.
#...startEditing/startOperation must be present regardless if already in an edit session for da.UpdateCursor to work in SDE
try:
edit = arcpy.da.Editor(arcpy.env.workspace)
# Start an edit session. Must provide the worksapce.
# Edit session is started without an undo/redo stack for versioned data
# (for second argument, use False for unversioned data)
edit.startEditing(True, False)
# Start an edit operation
edit.startOperation()
except:
pythonaddins.MessageBox("Not in Edit Session" + arcpy.GetMessages(2), "Edit Errors", 1)
pythonaddins.MessageBox(traceback.format_exc(), "Why Edits Failed", 1)
else:
try:
#Rest of script...
#
#Creation of an identical lyr of ForestStands for geoprocessing
memoryStands = "in_memory" + "\\" + "Stands_lyr"
arcpy.CopyFeatures_management(Stands, memoryStands)
#
#This creates a buffer of roads of 50 meters and selects only roads that the buffer intersects with any stand.
#...this drastically increases processing time since only a select set of raods are used for the calculation.
arcpy.SelectLayerByLocation_management(memoryRoads_lyr, 'intersect', memoryStands, "50", "NEW_SELECTION")
memoryNetStands_Temp = "in_memory" + "\\" + "NetStands_Temp"
arcpy.CopyFeatures_management(Stands, memoryNetStands_Temp)
memoryRoads_temp = "in_memory" + "\\" + "roads_lyr"
arcpy.CopyFeatures_management(memoryRoads_lyr, memoryRoads_temp)
arcpy.AddField_management(memoryRoads_temp, "ROW_METERS_FLOAT", "FLOAT")
CursorFieldNames = ["ROW_FEET", "ROW_METERS_FLOAT"]
cursor = arcpy.da.UpdateCursor(memoryRoads_temp,CursorFieldNames)
for row in cursor:
row[1] = row[0] * 0.1524 #Write area value to field
cursor.updateRow(row)
del row, cursor #Clean up cursor objects
arcpy.Buffer_analysis(memoryRoads_temp,"in_memory/my_buffer","ROW_METERS_FLOAT","FULL","ROUND","ALL","#")
arcpy.Erase_analysis(memoryStands, "in_memory/my_buffer", "in_memory/Erase_FC")
#The spatial reference # can be found in Arcmap for reference Different Projection
srNAD = arcpy.SpatialReference()
#The spatial reference # can be found in Arcmap for reference NAD 1983 UTM Zone 17
srUTM = arcpy.SpatialReference()
#This try/except splits out the NorthEast data from the rest to calculate acres in UTM.
try:
row,rows,cursor= None,None,None
#Notice NOT an da.UpdateCursor, just a UpdateCursor, this is bc all fields are required including SHAPE.
cur = arcpy.UpdateCursor("in_memory/Erase_FC", ["FIELD_AREA"],srUTM)
for row in cur:
#Use the FIELD_AREA field to meet the conditional statement.
if row.FIELD_AREA == "NorthEast":
row.NET_ACRES = round((row.SHAPE.area * 0.000247104393),3)
cur.updateRow(row)
else:
pass
curNAD = arcpy.UpdateCursor("in_memory/Erase_FC", ["FIELD_AREA"], srNAD)
for row in curNAD:
#Use the FIELD_AREA field to meet the conditional statement.
if row.FIELD_AREA <> "NorthEast":
row.NET_ACRES = round((row.SHAPE.area * 0.000247104393),3)
curNAD.updateRow(row)
except:
pythonaddins.MessageBox(traceback.format_exc(), "Try Errors",1)
pythonaddins.MessageBox(arcpy.GetMessages(2), "MSG",1)
#If ever in doubt, the line below also works for the try/except above, without splitting for UTM/NAD
#arcpy.CalculateField_management("in_memory/Erase_FC","NET_ACRES","!shape.area@acres!","PYTHON_9.3","#")
#The CROWN JEWEL of the code...try/except on creating a 'data dictionary from the "in_memory/Erase_FC" NET_ACRES field to populate
#...the ForestStands NET_ACRES field. The da.SearchCursor grabs the newly derived values from the "in_memory/Erase_FC".
#...Using the da.SearchCursor is effecient since it only grabs the fields you require, without grabing all the other fields that tax performance
try:
row= None
# Create Dictionary
with arcpy.da.SearchCursor("in_memory/Erase_FC",["StID", "NET_ACRES"]) as srows:
path_dict = dict([srow[0], srow[1]] for srow in srows)
#pythonaddins.MessageBox(str(path_dict), "Path_dictionary",1)
area = 'NorthEast'
#queryfield = 'AREA'
whereclauseUTM = "FIELD_AREA = '%s'" % area
#query = "[" + queryfield + "] " + "= " + "\'" + area + "\'" # else create a new query
with arcpy.da.UpdateCursor(Stands,["StID", "NET_ACRES", "GROSS_ACRES","SHAPE@AREA", " FIELD_AREA"], whereclauseUTM,srUTM) as urows:
for row in urows:
row[2] = round((row[3] * 0.000247104393),3)
if row[0] in path_dict:
row[1] = path_dict[row[0]]
#urows.updateRow(row)
else:
row[1] = 0
urows.updateRow(row)
whereclauseNAD = " SD_AREA <> '%s'" % area
with arcpy.da.UpdateCursor(Stands,["StID", "NET_ACRES", "GROSS_ACRES","SHAPE@AREA", " SD_AREA"], whereclauseNAD,srNAD) as urows:
for row in urows:
row[2] = round((row[3] * 0.000247104393),3)
if row[0] in path_dict:
row[1] = path_dict[row[0]]
else:
row[1] = 0
urows.updateRow(row)
except:
edit.undoOperation()
pythonaddins.MessageBox(arcpy.GetMessages(2), "Undo Operation", 1)
pythonaddins.MessageBox(traceback.format_exc(), "Traceback on Excecute Error",1)
else:
# Stop the edit session and DONT save the changes in code. Requires USER to save changes.
edit.stopOperation()
#edit.stopEditing(True)
arcpy.RefreshActiveView()
arcpy.RefreshTOC()
except:
#If these are triggered, then edits aren't saved, and the faulty lines of code are identified. No HARM, No Faul.
pythonaddins.MessageBox(arcpy.ExecuteError(), "Execute Error",1)
pythonaddins.MessageBox(traceback.format_exc(), "Traceback on Excecute Error",1)
pythonaddins.MessageBox(arcpy.GetMessages(2), "Error MSG",1)
except:
pythonaddins.MessageBox(arcpy.GetMessages(2) + " You are not in an Edit Session", "Cancelled Operation", 0)
pythonaddins.MessageBox(traceback.format_exc(), "Traceback",1)
except:
pythonaddins.MessageBox(arcpy.GetMessages(2), "Except Errors",1)
pythonaddins.MessageBox(traceback.format_exc(), "TraceBack", 1)
finally:
arcpy.Delete_management("in_memory")
if os.path.isdir(str(fullPath.rsplit('\\',1)[0])) and arcpy.Exists(workspace):
arcpy.Compact_management(workspace)
del workspace, fullPath
arcpy.RefreshTOC
arcpy.RefreshActiveView
From my experience it seems that the syntax "with arcpy.da.UpdateCcursor" (ie the use of a Python "with" statement for a cursor) in the code of a Python Add-In causes problems. I didn't check if using a arcpy.da cursor without a "with" statement works. The following code example use a "normal" UpdateCursor (arcpy.UdpateCursor, not part of the arcpy.da family) and should work. It's part of a Python Add-In that works on ArcGis 10.2.2 and Windows 7.
cursorYes = arcpy.UpdateCursor(saisie_layer)
for aline in cursorYes :
IDY = aline.getValue(IDfield)
if IDY not in list_aVerif_LA4 :
aline.setValue("TFV", "FF1G01-01")
aline.setValue("THEME", "11")
aline.setValue("ORI", None)
correction = aline.getValue("Type_modification")
cursorYes.updateRow(aline)
if IDY in list_aVerif_FF :
aline.setValue("Type_modification", "correction_tfv")
aline.setValue("TFV", "FF1G01-01")
aline.setValue("THEME", "11")
aline.setValue("ORI", None)
cursorYes.updateRow(aline)
# delete cursor
del aline
del cursorYes
To summarize try to use old fashion cursors : arcpy.UpdateCursor and do not forget to delete the cursor object and the variable used to store each row of the cursor.
Finally I would like to precise that any attempt to use a cursor outside the button class of a Python Add-In failed. Our Python Add-In has many button with the same Python code, the only difference being the value they write in a particular field. We tried to make a function outside the button classes to avoid repeating the same lines for each button, but calling a function from the button class resulted in the arcpy.UpdateCursor not working.
Explore related questions
See similar questions with these tags.