TransWikia.com

Stand alone Calculate Field script using Code Block for IMG Raster in Python

Geographic Information Systems Asked by NamuDude on December 3, 2020

I am somewhat familiar with using the python language in the Calculate Field tool in ArcGIS Pro, but was wondering how this is done in a stand alone Python script with a raster .img (Combine20012004FltPths.img) file.

I am trying to reclassify a new field I have created using integer values from another field. However, when I try to run the code in IDLE, I get a "SyntaxError: invalid syntax".

Here is my python script for the Code Block and the arcpy management Calculate Field tool:

# 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")

Would I need to convert the attribute table of my .img (Combine20012004FltPths.img) file into a dBASE or geodatabase table or some other table format, then run the Code Block and Calculate Field tool for it to work?

One Answer

The key to providing a code block to CalculateField is to understand that a separate Python parser is used inside the utility. The code_block must therefore be a string containing viable code. You can accomplish this by using triple-quotes around the text, so that Python handles line continuation correctly:

# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1):   # Note lowercase variable name -- upcase is for Classes
   {rest of function}
"""

But that's only the first iteration of improvement. There are two basic approaches to coding a switchyard function. The first uses a cascade of conditional tests, and has two major forms: With and without early exit. First without:

# 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
"""

The early exit form should be familiar, but given that return is used, no else is needed:

# 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
"""

The second approach leverages the dictionary datatype. It is faster, and more pythonic:

# Code Block for Calculate Field
codeBlock = """
lookup = {
     0 : "Unclassified",
    11 : "Open Water",
    ...
}
def reclass(flightPat1):
    return lookup[flightPat1] if flightPat1 in lookup else None
"""

The sexy part about dictionary use is that you can combine this with the fact that you're just compiling a string here, so you can assemble it dynamically (in this case, from a file geodatabase table):

# Code Block for Calculate Field
lookupSrc = r"C:Tempgis_se.gdbanswer_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'])]))

Okay, so yeah, that's a bit hot-n-heavy, but how many times do you get to do a list comprehension on a cursor, a format, and two replacements in one extended line? And it does work:

>>> 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

The obtuse part could be de-inlined to self-document:

# Code Block for Calculate Field
lookupSrc = r"C:Tempgis_se.gdbanswer_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) 
                                       

Correct answer by Vince on December 3, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP