Stand alone Calculate Field script utilizzando Code Block for IMG Raster in Python
Ho una certa familiarità con l'utilizzo del linguaggio Python nello strumento Calcola campo in ArcGIS Pro, ma mi chiedevo come farlo in uno script Python autonomo con un file raster .img (Combine20012004FltPths.img).
Sto cercando di riclassificare un nuovo campo che ho creato utilizzando valori interi da un altro campo. Tuttavia, quando provo a eseguire il codice in IDLE, ottengo un "SyntaxError: invalid syntax".
Ecco il mio script Python per Code Block e lo strumento Calculate Field di gestione arcpy:
# Code Block for Calculate Field
codeBlock = def reclass(FlightPat1):
if (FlightPat1 == 0):
return "Unclassified"
elif (FlightPat1 == 11):
return "Open Water"
elif (FlightPat1 == 12):
return "Perennial Snow/Ice"
elif (FlightPat1 == 21):
return "Developed, Open Space"
elif (FlightPat1 == 22):
return "Developed, Low Intensity"
elif (FlightPat1 == 23):
return "Developed, Medium Intensity"
elif (FlightPat1 == 24):
return "Developed, High Intensity"
elif (FlightPat1 == 31):
return "Barren Land"
elif (FlightPat1 == 41):
return "Deciduous Forest"
elif (FlightPat1 == 42):
return "Evergreen Forest"
elif (FlightPat1 == 43):
return "Mixed Forest"
elif (FlightPat1 == 52):
return "Shrub/Scrub"
elif (FlightPat1 == 71):
return "Herbaceuous"
elif (FlightPat1 == 81):
return "Hay/Pasture"
elif (FlightPat1 == 82):
return "Cultivated Crops"
elif (FlightPat1 == 90):
return "Woody Wetlands"
elif (FlightPat1 == 95):
return "Emergent Herbaceuous Wetlands"
#Reclassify field based off another field
arcpy.CalculateField_management("Combine20012004FltPths.img", "NLCD01Clss",
"Reclass(!FlightPat1!)", expression_type = "PYTHON3",
code_block = codeBlock, field_type = "TEXT")
Avrei bisogno di convertire la tabella degli attributi del mio file .img (Combine20012004FltPths.img) in una tabella dBASE o geodatabase o in un altro formato di tabella, quindi eseguire lo strumento Blocco codice e Calcola campo affinché funzioni?
Risposte
La chiave per fornire un blocco di codice CalculateFieldè capire che all'interno dell'utilità viene utilizzato un parser Python separato. Il code_blockdeve quindi essere una stringa contenente codice valido. Puoi farlo usando le virgolette triple intorno al testo, in modo che Python gestisca correttamente la continuazione della riga:
# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1): # Note lowercase variable name -- upcase is for Classes
{rest of function}
"""
Ma questa è solo la prima iterazione del miglioramento. Esistono due approcci di base per la codifica di una funzione switchyard. Il primo utilizza una cascata di test condizionali e ha due forme principali: con e senza uscita anticipata. Primo senza:
# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1):
result = None #! Initialize so that it always returns a default value
if (flightPat1 == 0):
result = "Unclassified"
elif (flightPat1 == 11):
result = "Open Water"
...
return result
"""
Il modulo di uscita anticipata dovrebbe essere familiare, ma dato che returnviene utilizzato, non elseè necessario:
# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1):
if (flightPat1 == 0):
return "Unclassified"
if (flightPat1 == 11):
return "Open Water"
...
return None #! Suffices for final else
"""
Il secondo approccio sfrutta il tipo di dati del dizionario. È più veloce e più pitonico:
# Code Block for Calculate Field
codeBlock = """
lookup = {
0 : "Unclassified",
11 : "Open Water",
...
}
def reclass(flightPat1):
return lookup[flightPat1] if flightPat1 in lookup else None
"""
La parte sexy dell'uso del dizionario è che puoi combinare questo con il fatto che stai solo compilando una stringa qui, in modo da poterla assemblare dinamicamente (in questo caso, da una tabella di file geodatabase):
# Code Block for Calculate Field
lookupSrc = r"C:\Temp\gis_se.gdb\answer_lookup"
codeBlock = """
def reclass(flightPat1):
lookup = {
@TERMS
}
return lookup[flightPat1] if flightPat1 in lookup else None
""".replace('@TERMS',',\n'.join(
["{:10d} : '{:s}'".format(rec[0],rec[1].replace("'","\\'"))
for rec in arcpy.da.SearchCursor(lookupSrc,['ival','sval'])]))
Ok, quindi sì, è un po 'scottante, ma quante volte riesci a fare una comprensione dell'elenco su un cursore, un formato e due sostituzioni in una riga estesa? E funziona:
>>> print(codeBlock)
def reclass(flightPat1):
lookup = {
0 : 'Unclassified',
11 : 'Open Water',
12 : 'Perennial Snow/Ice',
21 : 'Developed, Open Space',
22 : 'Developed, Low Intensity',
23 : 'Developed, Medium Intensity',
24 : 'Developed, High Intensity',
31 : 'Barren Land',
41 : 'Deciduous Forest',
42 : 'Evergreen Forest',
43 : 'Mixed Forest',
52 : 'Shrub/Scrub',
71 : 'Herbaceuous',
81 : 'Hay/Pasture',
82 : 'Cultivated Crops',
90 : 'Woody Wetlands',
94 : 'Apostrophe\'s Example',
95 : 'Emergent Herbaceuous Wetlands'
}
return lookup[flightPat1] if flightPat1 in lookup else None
La parte ottusa potrebbe essere de-inlinata per auto-documentarsi:
# Code Block for Calculate Field
lookupSrc = r"C:\Temp\gis_se.gdb\answer_lookup"
codeFormat = """
def reclass(flightPat1):
lookup = {
@TERMS
}
return lookup[flightPat1] if flightPat1 in lookup else None
"""
termList = []
with arcpy.da.SearchCursor(lookupSrc,['ival','sval']) as cursor:
for row in cursor:
ival = row[0]
sval = row[1].replace("'","\\'") #! Handle case where sval contains apostrophe
term = "{:10d} : '{:s}'".format(ival,sval)
termList.append(term)
termString = ',\n'.join(termList) #! str.join() adds text between list elements
codeBlock = codeFormat.replace('@TERMS',termString)