Script Calculate Field autônomo usando Bloco de Código para IMG Raster em Python
Estou um pouco familiarizado com o uso da linguagem python na ferramenta Calculate Field no ArcGIS Pro, mas queria saber como isso é feito em um script Python autônomo com um arquivo raster .img (Combine20012004FltPths.img).
Estou tentando reclassificar um novo campo que criei usando valores inteiros de outro campo. No entanto, quando tento executar o código no IDLE, obtenho um "SyntaxError: sintaxe inválida".
Aqui está meu script Python para o Bloco de Código e a ferramenta Calculate Field de gerenciamento 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")
Eu precisaria converter a tabela de atributos do meu arquivo .img (Combine20012004FltPths.img) em uma tabela dBASE ou geodatabase ou algum outro formato de tabela e, em seguida, executar a ferramenta Bloco de Código e Calcular Campo para que funcione?
Respostas
A chave para fornecer um bloco de código CalculateFieldé entender que um analisador Python separado é usado dentro do utilitário. O code_blockdeve, portanto, ser uma string contendo um código viável. Você pode fazer isso usando aspas triplas ao redor do texto, para que o Python controle a continuação de linha corretamente:
# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1): # Note lowercase variable name -- upcase is for Classes
{rest of function}
"""
Mas essa é apenas a primeira iteração de melhoria. Existem duas abordagens básicas para codificar uma função de switchyard. O primeiro usa uma cascata de testes condicionais e tem duas formas principais: Com e sem saída antecipada. Primeiro sem:
# 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
"""
O formulário de saída antecipada deve ser familiar, mas dado que returné usado, não elseé necessário:
# 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
"""
A segunda abordagem aproveita o tipo de dados do dicionário. É mais rápido e mais pitônico:
# Code Block for Calculate Field
codeBlock = """
lookup = {
0 : "Unclassified",
11 : "Open Water",
...
}
def reclass(flightPat1):
return lookup[flightPat1] if flightPat1 in lookup else None
"""
A parte interessante sobre o uso de dicionário é que você pode combinar isso com o fato de que você está apenas compilando uma string aqui, para que possa montá-la dinamicamente (neste caso, a partir de uma tabela de banco de dados geográfico):
# 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, então sim, isso é um pouco quente e pesado, mas quantas vezes você consegue fazer uma compreensão de lista em um cursor, um formato e duas substituições em uma linha estendida? E funciona:
>>> 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
A parte obtusa pode ser desincronizada para autodocumentar:
# 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)