0

I am writing a Python Toolbox for a parcel prioritization tool. My input data (a set of 11 rasters) is organized into a dictionary, raster_dict, where keys are raster names (i.e. 'Important Bird Areas') and values are raster file names (i.e. importantbirdareas.tif).

To create drop-down menus in the tool GUI, in getParameterInfo function I have used the parameter.filter.list() function on a list of the keys from my raster dictionary. The user selects multiple raster inputs from the list of raster names.

In the execute function, I convert the multiple user input into a list using parameters[0].valueAsText.split(";"), which creates a list of raster names. I create an empty list for paths to my raster files, then iterate through the names in the raster name list, passing each raster name into the raster_dict.get() function to retrieve the corresponding raster file name in the dictionary. I concatenate the raster file name to the end of the path to the folder where the raster file is stored, and then append each path to the raster paths list. The list of paths is the input for the rest of my script (a raster calculation, zonal stats, and spatial join).

The tool fails at the concatenate step:

File "", line 107, in execute TypeError: can only concatenate str (not "NoneType") to str

After debugging, I have found that the problem is with passing my raster names into the raster_dict.get() function. raster_dict.get() is returning nothing, meaning there is no “match” between the dictionary keys and the raster names passed to the get() function. I have tested the code in IDLE (replacing the parameters[0].valueAsText with hard-coded values, and it runs as expected. I have also used Messages in the Python Toolbox to check that the variable I am passing to the get() function is a string that matches one of the keys in raster_dict.

I have also tried rewriting the code without dictionaries, using lists instead. When I try to match the input parameter(s) to a value in the list (for example: if raster_name in raster_name_list), the code still fails, not recognizing the matching values.

The error occurs in the second line of the following block:

for key in raster_names:
   raster_path = "F:\\GIS\\SEALT_prioritization\\Rasters\\" + raster_dict.get(key)
   rasterList.append(raster_path)

Here is my complete .pyt:


import arcpy
from arcpy.sa import *
from arcpy.ia import *
import os

class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        self.label = "SEALT Prioritization Python Toolbox"
        self.alias = "prioritize"

        # List of tool classes associated with this toolbox
        self.tools = [PrioritizeParcels]

class PrioritizeParcels(object):
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "Prioritize Parcels"
        self.description = "This tool accepts multiple raster layer inputs and sums the overlaid pixels Then it averages the pixel values within the polygons of a designated feature class."
        self.canRunInBackground = False

    def getParameterInfo(self):
        """Define parameter definitions"""
        input_rasters = arcpy.Parameter(
            name="input_rasters",
            displayName="Select input datasets",
            datatype="GPString",
            parameterType="Required",
            direction="Input",
            multiValue=True)

        raster_dict={"Freshwater forested shrub wetland":'Freshwater_Forested_Shrub_Wetland.tif', "Anadromous waters":
                    'MergedBuffer_300ft_AWC.tif', "Eelgrass":'MergedBuffer_300ft_Eelgrass.tif', "Estuarine and marine wetlands":
                     'MergedBuffer_300ft_Estuarine_Marine_Wetlands.tif', "Public Lands": 'MergedBuffer_300ft_PublicLands.tif',
                     "Rivers and streams":'MergedBuffer_300ft_Rivers_Streams.tif', "Lakes and ponds":'MergedBuffer_300ft_Waterbodies.tif',
                     "Freshwater emergent wetlands":'MergedFreshwater_Emergent_Wetland.tif', "Important Bird Areas":
                     'mergedibas', "Productive old growth forest":'mergedpog', "Protected areas":'protected'}
        input_rasters.filter.type="ValueList"
        input_rasters.filter.list= list(raster_dict.keys())
            
        in_parcels = arcpy.Parameter(
            displayName="Select parcels to be scored",
            name="in_parcels",
            datatype="GPString",
            parameterType="Required",
            direction="Input")
            #,multiValue=True)

        service_area_dict={"Yakutat": 'YakutatParcels', "Wrangell": 'WrangellParcels', "Sitka": 'SitkaParcels',
                            "Petersburg":'Petersburg_Lots', "Ketchikan": 'KetchikanParcels', "Juneau":'JuneauParcels',
                            "Haines":'HainesParcels', "Skagway": 'SkagwayParcels'}
        in_parcels.filter.type="ValueList"
        in_parcels.filter.list= list(service_area_dict.keys())

        out_path = arcpy.Parameter(
            name="out_path",
            displayName="Save results as",
            datatype= "GPString",
            parameterType="Required",
            direction="Output")
            

        parameters = [input_rasters, in_parcels, out_path]
        return parameters
  

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""
        return


    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        return

    def execute(self, parameters, messages):
        """The source code of the tool."""

        #arcpy.env.workspace = r"F:\GIS\SEALT_prioritization\Rasters"
        arcpy.env.overwriteOutput = True

        raster_dict={"Freshwater forested shrub wetland":'Freshwater_Forested_Shrub_Wetland.tif', "Anadromous waters":
                    'MergedBuffer_300ft_AWC.tif', "Eelgrass":'MergedBuffer_300ft_Eelgrass.tif', "Estuarine and marine wetlands":
                     'MergedBuffer_300ft_Estuarine_Marine_Wetlands.tif', "Public Lands": 'MergedBuffer_300ft_PublicLands.tif',
                     "Rivers and streams":'MergedBuffer_300ft_Rivers_Streams.tif', "Lakes and ponds":'MergedBuffer_300ft_Waterbodies.tif',
                     "Freshwater emergent wetlands":'MergedFreshwater_Emergent_Wetland.tif', "Important Bird Areas":
                     'mergedibas', "Productive old growth forest":'mergedpog', "Protected areas":'protected'}

        service_area_dict={"Yakutat": 'YakutatParcels', "Wrangell": 'WrangellParcels', "Sitka": 'SitkaParcels',
                            "Petersburg":'Petersburg_Lots', "Ketchikan": 'KetchikanParcels', "Juneau":'JuneauParcels',
                            "Haines":'HainesParcels', "Skagway": 'SkagwayParcels'}

        raster_names=parameters[0].valueAsText.split(";")
        rasterList = []

        for key in raster_names:
            raster_path = "F:\\GIS\\SEALT_prioritization\\Rasters\\" + raster_dict.get(key)
            rasterList.append(raster_path)

        rasterCount = len(rasterList)

        if rasterCount == 11:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            ras6 = arcpy.Raster(rasterList[5])
            ras7 = arcpy.Raster(rasterList[6])
            ras8 = arcpy.Raster(rasterList[7])
            ras9 = arcpy.Raster(rasterList[8])
            ras10 = arcpy.Raster(rasterList[9])
            ras11 = arcpy.Raster(rasterList[10])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5 + ras6 + ras7 + ras8 + ras9 + ras10 + ras11

        elif rasterCount == 10:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            ras6 = arcpy.Raster(rasterList[5])
            ras7 = arcpy.Raster(rasterList[6])
            ras8 = arcpy.Raster(rasterList[7])
            ras9 = arcpy.Raster(rasterList[8])
            ras10 = arcpy.Raster(rasterList[9])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5 + ras6 + ras7 + ras8 + ras9 + ras10
  
        elif rasterCount == 9:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            ras6 = arcpy.Raster(rasterList[5])
            ras7 = arcpy.Raster(rasterList[6])
            ras8 = arcpy.Raster(rasterList[7])
            ras9 = arcpy.Raster(rasterList[8])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5 + ras6 + ras7 + ras8 + ras9
    
        elif rasterCount == 8:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            ras6 = arcpy.Raster(rasterList[5])
            ras7 = arcpy.Raster(rasterList[6])
            ras8 = arcpy.Raster(rasterList[7])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5 + ras6 + ras7 + ras8
            
        elif rasterCount == 7:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            ras6 = arcpy.Raster(rasterList[5])
            ras7 = arcpy.Raster(rasterList[6])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5 + ras6 + ras7
           
        elif rasterCount == 6:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            ras6 = arcpy.Raster(rasterList[5])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5 + ras6
            
        elif rasterCount == 5:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            ras5 = arcpy.Raster(rasterList[4])
            outRaster = ras1 + ras2 + ras3 + ras4 + ras5
            
        elif rasterCount == 4:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            ras4 = arcpy.Raster(rasterList[3])
            outRaster = ras1 + ras2 + ras3 + ras4
            
        elif rasterCount == 3:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            ras3 = arcpy.Raster(rasterList[2])
            outRaster = ras1 + ras2 + ras3
            
        elif rasterCount == 2:
            ras1 = arcpy.Raster(rasterList[0])
            ras2 = arcpy.Raster(rasterList[1])
            outRaster = ras1 + ras2
            
        elif rasterCount == 1:
            ras1 = arcpy.Raster(rasterList[0])
            outRaster = ras1
            
        serviceArea = parameters[1].valueAsText
        parcels = "F:\\GIS\\Projects\\Tongass\\SEALTrust_Prioritization\\SEALT.gdb\\Parcels\\" + service_area_dict.get(serviceArea)

        zonalStats = ZonalStatistics(in_zone_data=parcels, zone_field="primary_key", in_value_raster=outRaster, statistics_type="MEAN", ignore_nodata="DATA")
        #zonalStats.save(r"F:\GIS\SEALT_prioritization\Output\zonal_out")

        centroids = r"memory\centroids"
        out_points = r"memory\outPoints"

        arcpy.management.FeatureToPoint(in_features=parcels, out_feature_class=centroids, point_location="INSIDE")

        ExtractValuesToPoints(in_point_features=centroids, in_raster=zonalStats,out_point_features=out_points,
                              interpolate_values="NONE", add_attributes="VALUE_ONLY")

        scoredParcels = parameters[2].valueAsText

        arcpy.analysis.SpatialJoin(target_features=parcels, join_features=out_points, out_feature_class=scoredParcels,
                                   join_operation="JOIN_ONE_TO_ONE", join_type="KEEP_ALL", match_option="CONTAINS")

        return

    def postExecute(self, parameters):
        """This method takes place after outputs are outputs are processed and
        added to the display."""
        return 

                                                                                                                                                                                                                                                                                                                                                                                                                                                         
1
  • 1
    You're right that it's not matching the key (which is why .get() returns None). Try adding a print statement to see exactly what is in "raster_names" before you loop through it. Also, although I don't think this is related to the error, I prefer to use os.path.join to build paths instead of string+string.
    – Brennan
    Commented Oct 28, 2022 at 17:06

2 Answers 2

0

I resolved the issue.

When printing the rasterList, I noticed an extra set of quotations in the print statement:

["'Anadromous waters'", "'Public Lands'", "'POG'"]

Not sure where the extra came from, but I removed it by placing rasterList.strip("'") into my for loop before concatenating path.

0

This isn't what you asked, but I was really curious if there was a way to vectorize your raster calculation, instead of using 11 if statements. I believe this approach works:

# get a list of variables ['a','b','c'] for each entry in your rasterList
varList = [chr(97+i) for i,r in enumerate(rasterList)]

# build an expression of 'a+b+c' for each variable
exp = "{:s}".format('+'.join(f"{x}" for x in varList))

# pass the list of rasters, names, and expression to RasterCalculator
outRaster = RasterCalculator(rasterList,varList, exp)


4
  • I am indeed trying to streamline that section of the code, though all of my attempts came down to dynamically naming variable, which is apparently highly discouraged in Python. After consulting the documentation for the enumerate() function, which I was not familiar with, I see the logic of your code, but I don’t understand how it gets around using the Raster() function, which, to my knowledge, is required to execute raster math in arcpy. As an aside, my understanding is that you can’t call RasterCalculator() in arcpy: you just build expressions on objects created using the Raster() function Commented Nov 8, 2022 at 4:37
  • Ultimately, I’d like the tool to run on any number of raster inputs (currently it is part of a Python toolbox and limited to 12 raster inputs, so the 12 if statements serves its purpose, even if it’s not elegant), which requires a different solution. Appreciate the input. Commented Nov 8, 2022 at 4:50
  • I also thought that RasterCalculator wouldn't work in arcpy, and I can't find anything in the help for ArcGIS Pro 3.0 that explicitly said it changed, but when I test my code in 3.0 it works as intended. The help does say that RasterCalculator within arcpy uses Map Alegbra syntax but does not require you to cast input data using Raster(). One other thing - I am defining rasterList using layer names in my Table of Contents, or using an input parameter type of ["GPRasterLayer","DERasterDataset"] instead of GPString. I don't know if this will make any difference or not.
    – Brennan
    Commented Nov 9, 2022 at 13:08
  • Interesting- I’ll definitely give it a try! Commented Nov 10, 2022 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.