コード例 #1
0
    def set_crop_params(self):
        """ List of <CropParameter> instances """
        logging.info('  Reading crop parameters')
        self.crop_params = crop_parameters.read_crop_parameters(
            self.crop_params_path)

        # Filter crop parameters based on skip and test lists
        # Filtering could happen in read_crop_parameters()
        if self.crop_skip_list or self.crop_test_list:
            # Leave bare soil "crop" parameters
            # Used in initialize_crop_cycle()
            non_crop_list = [44]
            # non_crop_list = [44,45,46,55,56,57]
            self.crop_params = {
                k: v
                for k, v in self.crop_params.iteritems()
                if ((self.crop_skip_list and k not in self.crop_skip_list) or (
                    self.crop_test_list and k in self.crop_test_list) or (
                        k in non_crop_list))
            }
コード例 #2
0
def main(ini_path):
    """Interpolate Preliminary Calibration Zones to All Zones

    Args:
        ini_path (str): file path of the project INI file
    Returns:
        None

    """
    logging.info('\nInterpolating Calibration Data from Subset Point Data')

    #  INI path
    crop_et_sec = 'CROP_ET'
    config = util.read_ini(ini_path, section=crop_et_sec)

    try:
        project_ws = config.get(crop_et_sec, 'project_folder')
    except:
        logging.error('project_folder parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        gis_ws = config.get(crop_et_sec, 'gis_folder')
    except:
        logging.error('gis_folder parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        et_cells_path = config.get(crop_et_sec, 'cells_path')
    except:
        logging.error('et_cells_path parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        calibration_ws = config.get(crop_et_sec, 'spatial_cal_folder')
    except:
        calibration_ws = os.path.join(project_ws, 'calibration')

    try:
        crop_params_name = config.get(crop_et_sec, 'crop_params_name')
    except:
        logging.error('crop_params_name parameter must be set in the INI file, '
                      'exiting')
        return False

    # Sub folder names
    static_ws = os.path.join(project_ws, 'static')
    crop_params_path = os.path.join(static_ws, crop_params_name)
    crop_et_ws = config.get(crop_et_sec, 'crop_et_folder')
    bin_ws = os.path.join(crop_et_ws, 'bin')

    # Check input folders
    if not os.path.exists(calibration_ws):
        logging.critical('\nERROR: The calibration folder does not exist. '
                         '\n  Run build_spatial_crop_params.py')
        sys.exit()

    # Check input folders
    if not os.path.isdir(project_ws):
        logging.critical('\nERROR: The project folder does not exist'
                         '\n  {}'.format(project_ws))
        sys.exit()
    elif not os.path.isdir(gis_ws):
        logging.critical('\nERROR: The GIS folder does not exist'
                         '\n  {}'.format(gis_ws))
        sys.exit()

    logging.info('\nGIS Workspace:      {}'.format(gis_ws))

    # ET cells field names
    cell_id_field = 'CELL_ID'
    cell_station_id_field = 'STATION_ID'
    # cell_name_field = 'CELL_NAME'
    # crop_acres_field = 'CROP_ACRES'

    # Do distance calculations in decimal degrees to match Arc script
    gcs_osr = osr.SpatialReference()
    gcs_osr.ImportFromEPSG(4326)

    # Read in the cell locations and values
    et_cells_data = defaultdict(dict)
    input_driver = _arcpy.get_ogr_driver(et_cells_path)
    input_ds = input_driver.Open(et_cells_path, 0)
    input_lyr = input_ds.GetLayer()
    input_osr = input_lyr.GetSpatialRef()
    # gcs_osr = input_osr.CloneGeogCS()
    for input_ftr in input_lyr:
        input_fid = input_ftr.GetFID()
        logging.debug('  FID: {}'.format(input_fid))
        input_id = input_ftr.GetField(input_ftr.GetFieldIndex(cell_id_field))
        input_geom = input_ftr.GetGeometryRef()
        centroid_geom = input_geom.Clone()
        # Do distance calculations in decimal degrees to match Arc script
        centroid_geom.Transform(
            osr.CoordinateTransformation(input_osr, gcs_osr))
        centroid_geom = centroid_geom.Centroid()
        et_cells_data[input_id]['X'] = centroid_geom.GetX()
        et_cells_data[input_id]['Y'] = centroid_geom.GetY()
    input_ds = None

    # Read crop parameters using ET Demands functions/methods
    logging.info('\nReading default crop parameters')
    sys.path.append(bin_ws)
    import crop_parameters
    crop_param_dict = crop_parameters.read_crop_parameters(crop_params_path)

    # Get list of crops specified in ET cells
    crop_field_list = [
        field for field in _arcpy.list_fields(et_cells_path)
        if re.match('CROP_\d{2}', field)]
    crop_number_list = [int(f.split('_')[1]) for f in crop_field_list]
    logging.info('Cell crop numbers: {}'.format(
        ', '.join(list(util.ranges(crop_number_list)))))
    logging.debug('Cell crop fields: {}'.format(', '.join(crop_field_list)))

    # Get Crop Names for each Crop in crop_number_list
    crop_name_list = []
    logging.debug('\nBuilding crop name list')
    for crop_num in crop_number_list:
        try:
            crop_param = crop_param_dict[crop_num]
        except:
            continue
        # logging.info('{:>2d} {}'.format(crop_num, crop_param.name))
        logging.debug('{}'.format(crop_param))
        # Replace other characters with spaces, then remove multiple spaces
        crop_name = re.sub('[-"().,/~]', ' ', str(crop_param.name).lower())
        crop_name = ' '.join(crop_name.strip().split()).replace(' ', '_')
        crop_name_list.append(crop_name)

    # Location of preliminary calibration .shp files (ADD AS INPUT ARG?)
    prelim_calibration_ws = os.path.join(calibration_ws,
                                         'preliminary_calibration')

    logging.info('\nInterpolating calibration parameters')
    for crop_num, crop_name in zip(crop_number_list, crop_name_list):
        # Preliminary calibration .shp
        subset_cal_file = os.path.join(
            prelim_calibration_ws,
            'crop_{0:02d}_{1}{2}').format(crop_num, crop_name, '.shp')
        final_cal_file = os.path.join(
            calibration_ws,
            'crop_{0:02d}_{1}{2}').format(crop_num, crop_name, '.shp')

        if not _arcpy.exists(subset_cal_file):
            logging.info(
                '\nCrop No: {} Preliminary calibration file not found. '
                'skipping.'.format(crop_num))
            continue
        logging.info('\nInterpolating Crop: {:02d}'.format(crop_num))

        # Params to Interpolate
        # param_list = ['T30_CGDD', 'CGDD_EFC', 'CGDD_TERM', 'KillFrostC']
        param_list = ['MAD_Init', 'MAD_Mid', 'T30_CGDD',
            'PL_GU_Date', 'CGDD_Tbase', 'CGDD_EFC',
            'CGDD_Term', 'Time_EFC', 'Time_Harv', 'KillFrostC']

        # Read in the calibration locations and values
        subset_cal_data = defaultdict(dict)
        input_driver = _arcpy.get_ogr_driver(subset_cal_file)
        input_ds = input_driver.Open(subset_cal_file, 0)
        input_lyr = input_ds.GetLayer()
        input_osr = input_lyr.GetSpatialRef()
        # gcs_osr = input_osr.CloneGeogCS()
        for input_ftr in input_lyr:
            input_fid = input_ftr.GetFID()
            logging.debug('  FID: {}'.format(input_fid))
            input_id = input_ftr.GetField(input_ftr.GetFieldIndex(
                cell_id_field))
            input_geom = input_ftr.GetGeometryRef()
            centroid_geom = input_geom.Clone()
            # Do distance calculations in decimal degrees to match Arc script
            centroid_geom.Transform(
                osr.CoordinateTransformation(input_osr, gcs_osr))
            centroid_geom = centroid_geom.Centroid()
            subset_cal_data[input_id]['X'] = centroid_geom.GetX()
            subset_cal_data[input_id]['Y'] = centroid_geom.GetY()
            for f in param_list:
                subset_cal_data[input_id][f] = input_ftr.GetField(
                    input_ftr.GetFieldIndex(f))
        input_ds = None

        # Compute interpolated calibration parameters
        final_cal_data = defaultdict(dict)
        for cell_id, cell_dict in et_cells_data.items():
            final_cal_data[cell_id] = {}
            logging.debug('  {}'.format(cell_id))

            # Precompute distances to all subset cells
            weight = {}
            for subset_id, subset_dict in subset_cal_data.items():
                distance = math.sqrt(
                    (subset_dict['X'] - cell_dict['X']) ** 2 +
                    (subset_dict['Y'] - cell_dict['Y']) ** 2)
                try:
                    weight[subset_id] = distance ** -2.0
                except:
                    weight[subset_id] = 0
                weight_total = sum(weight.values())

            # Brute force IDW using all subset cell
            for param in param_list:
                # If any weight is zero, use the values directly
                # There is probably a better way of flagging these
                d0 = [id for id, w in weight.items() if w == 0]
                if d0:
                    final_cal_data[cell_id][param] = subset_cal_data[
                        d0[0]][param]
                else:
                    final_cal_data[cell_id][param] = sum([
                        data[param] * weight[id]
                        for id, data in subset_cal_data.items()])
                    final_cal_data[cell_id][param] /= weight_total

        # Overwrite values in calibration .shp with interpolated values
        output_ds = input_driver.Open(final_cal_file, 1)
        output_lyr = output_ds.GetLayer()
        for output_ftr in output_lyr:
            output_id = output_ftr.GetField(
                output_ftr.GetFieldIndex(cell_id_field))
            for param in param_list:
                output_ftr.SetField(
                    input_ftr.GetFieldIndex(param),
                    round(final_cal_data[output_id][param], 1))
            output_lyr.SetFeature(output_ftr)
        output_ds = None
コード例 #3
0
def main(ini_path, zone_type='gridmet', overwrite_flag=False):
    """Interpolate Preliminary Calibration Zones to All Zones

    Args:
        ini_path (str): file path of the project INI file
        zone_type (str): Zone type (huc8, huc10, county, gridmet)
        overwrite_flag (bool): If True (default), overwrite existing files

    Returns:
        None
    """
    logging.info('\nInterpolating Calibration Data from Subset Point Data')

    #  INI path
    crop_et_sec = 'CROP_ET'
    config = util.read_ini(ini_path, section=crop_et_sec)

    try:
        project_ws = config.get(crop_et_sec, 'project_folder')
    except:
        logging.error('project_folder parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        gis_ws = config.get(crop_et_sec, 'gis_folder')
    except:
        logging.error('gis_folder parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        et_cells_path = config.get(crop_et_sec, 'cells_path')
    except:
        logging.error('et_cells_path parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        calibration_ws = config.get(crop_et_sec, 'spatial_cal_folder')
    except:
        calibration_ws = os.path.join(project_ws, 'calibration')

    # Sub folder names
    static_ws = os.path.join(project_ws, 'static')
    crop_params_path = os.path.join(static_ws, 'CropParams.txt')
    crop_et_ws = config.get(crop_et_sec, 'crop_et_folder')
    bin_ws = os.path.join(crop_et_ws, 'bin')

    # Check input folders
    if not os.path.exists(calibration_ws):
        logging.critical('ERROR: The calibration folder does not exist. '
                         'Run build_spatial_crop_params_arcpy.py, exiting')
        sys.exit()

    # Check input folders
    if not os.path.isdir(project_ws):
        logging.critical('ERROR: The project folder does not exist'
                         '\n  {}'.format(project_ws))
        sys.exit()
    elif not os.path.isdir(gis_ws):
        logging.critical('ERROR: The GIS folder does not exist'
                         '\n  {}'.format(gis_ws))
        sys.exit()
    logging.info('\nGIS Workspace:      {}'.format(gis_ws))

    # Check input zone type (GRIDMET ONLY FOR NOW!!!!)
    if zone_type == 'gridmet':
        station_zone_field = 'GRIDMET_ID'
        station_id_field = 'GRIDMET_ID'
    # DEADBEEF - Added for testing
    elif zone_type == 'huc8':
        station_zone_field = 'HUC8'
        station_id_field = 'STATION_ID'
    else:
        logging.error(
            '\nFUNCTION ONLY SUPPORTS GRIDMET ZONE TYPE AT THIS TIME')
        sys.exit()

    arcpy.env.overwriteOutput = overwrite_flag
    arcpy.CheckOutExtension('Spatial')

    cells_dd_path = os.path.join(gis_ws, 'ETCells_dd.shp')
    cells_ras_path = os.path.join(gis_ws, 'ETCells_ras.img')
    arcpy.Project_management(et_cells_path, cells_dd_path,
                             arcpy.SpatialReference('WGS 1984'))

    temp_path = os.path.join(calibration_ws, 'temp')
    if not os.path.exists(temp_path):
        os.makedirs(temp_path)
    temp_pt_file = os.path.join(temp_path, 'temp_pt_file.shp')

    # Read crop parameters using ET Demands functions/methods
    logging.info('\nReading Default Crop Parameters')
    sys.path.append(bin_ws)
    import crop_parameters
    crop_param_dict = crop_parameters.read_crop_parameters(crop_params_path)

    # Get list of crops specified in ET cells
    crop_field_list = [
        field.name for field in arcpy.ListFields(et_cells_path)
        if re.match('CROP_\d{2}', field.name)
    ]
    logging.debug('Cell crop fields: {}'.format(', '.join(crop_field_list)))
    crop_number_list = [
        int(f_name.split('_')[1]) for f_name in crop_field_list
    ]

    crop_number_list = [crop_num for crop_num in crop_number_list]
    logging.info('Cell crop numbers: {}'.format(', '.join(
        list(util.ranges(crop_number_list)))))

    # Get Crop Names for each Crop in crop_number_list
    crop_name_list = []
    logging.debug('\nBuilding crop name list')
    for crop_num in crop_number_list:
        try:
            crop_param = crop_param_dict[crop_num]
        except:
            continue
        # logging.info('{:>2d} {}'.format(crop_num, crop_param.name))
        logging.debug('{}'.format(crop_param))
        # Replace other characters with spaces, then remove multiple spaces
        crop_name = re.sub('[-"().,/~]', ' ', str(crop_param.name).lower())
        crop_name = ' '.join(crop_name.strip().split()).replace(' ', '_')
        crop_name_list.append(crop_name)

    # Set arcpy environmental parameters
    arcpy.env.extent = cells_dd_path
    arcpy.env.outputCoordinateSystem = cells_dd_path

    # Convert cells_dd to cells_ras
    # (0.041666667 taken from GEE GRIDMET tiff) HARDCODED FOR NOW
    arcpy.FeatureToRaster_conversion(cells_dd_path, station_id_field,
                                     cells_ras_path, 0.041666667)

    # Location of preliminary calibration .shp files (ADD AS INPUT ARG?)
    prelim_calibration_ws = os.path.join(calibration_ws,
                                         'preliminary_calibration')

    logging.info('\nInterpolating calibration parameters')
    for crop_num, crop_name in zip(crop_number_list, crop_name_list):
        # Preliminary calibration .shp
        subset_cal_file = os.path.join(
            prelim_calibration_ws,
            'crop_{0:02d}_{1}{2}'.format(crop_num, crop_name, '.shp'))
        final_cal_file = os.path.join(
            calibration_ws,
            'crop_{0:02d}_{1}{2}'.format(crop_num, crop_name, '.shp'))

        if not arcpy.Exists(subset_cal_file):
            logging.info(
                '\nCrop No: {} preliminary calibration file not found. '
                'Skipping.'.format(crop_num))
            continue
        logging.info('\nInterpolating Crop: {:02d}'.format(crop_num))

        # Polygon to Point
        arcpy.FeatureToPoint_management(subset_cal_file, temp_pt_file,
                                        "CENTROID")

        # Change Processing Extent to match final calibration file
        # arcpy.env.extent = cells_dd_path
        # arcpy.env.outputCoordinateSystem = cells_dd_path
        arcpy.env.snapRaster = cells_ras_path
        cell_size = arcpy.Raster(cells_ras_path).meanCellHeight

        # Params to Interpolate
        # Full list
        # param_list = ['MAD_Init', 'MAD_Mid', 'T30_CGDD',
        #     'PL_GU_Date', 'CGDD_Tbase', 'CGDD_EFC',
        #     'CGDD_Term', 'Time_EFC', 'Time_Harv', 'KillFrostC']

        # Short list
        param_list = ['T30_CGDD', 'CGDD_EFC', 'CGDD_TERM', 'KillFrostC']

        # Create final pt file based on cells raster for ExtractMultiValuesToPoints
        final_pt_path = os.path.join(temp_path, 'final_pt.shp')
        arcpy.RasterToPoint_conversion(cells_ras_path, final_pt_path, 'VALUE')

        # Empty list to fill with idw raster paths
        ras_list = []
        for param in param_list:
            outIDW_ras = arcpy.sa.Idw(temp_pt_file, param, cell_size)
            outIDW_ras_path = os.path.join(temp_path,
                                           '{}{}'.format(param, '.img'))
            outIDW_ras.save(outIDW_ras_path)
            ras_list.append(outIDW_ras_path)

        # Extract all idw raster values to point .shp
        arcpy.sa.ExtractMultiValuesToPoints(final_pt_path, ras_list, 'NONE')

        # Read Interpolated Point Attribute table into dictionary ('GRID_CODE' is key)
        # https://gist.github.com/tonjadwyer/0e4162b1423c404dc2a50188c3b3c2f5
        def make_attribute_dict(fc, key_field, attr_list=['*']):
            attdict = {}
            fc_field_objects = arcpy.ListFields(fc)
            fc_fields = [
                field.name for field in fc_field_objects
                if field.type != 'Geometry'
            ]
            if attr_list == ['*']:
                valid_fields = fc_fields
            else:
                valid_fields = [
                    field for field in attr_list if field in fc_fields
                ]
            # Ensure that key_field is always the first field in the field list
            cursor_fields = [key_field
                             ] + list(set(valid_fields) - set([key_field]))
            with arcpy.da.SearchCursor(fc, cursor_fields) as cursor:
                for row in cursor:
                    attdict[row[0]] = dict(zip(cursor.fields, row))
            return attdict

        cal_dict = make_attribute_dict(final_pt_path, 'GRID_CODE', param_list)

        # Overwrite values in calibration .shp with values from interpolated dictionary
        fields = ['CELL_ID'] + param_list
        with arcpy.da.UpdateCursor(final_cal_file, fields) as cursor:
            for row in cursor:
                for param_i, param in enumerate(param_list):
                    row[param_i + 1] = round(
                        cal_dict[int(row[0])][fields[param_i + 1]], 1)
                cursor.updateRow(row)
コード例 #4
0
def main(ini_path,
         zone_type='huc8',
         area_threshold=10,
         dairy_cuttings=5,
         beef_cuttings=4,
         crop_str='',
         remove_empty_flag=True,
         overwrite_flag=False,
         cleanup_flag=False):
    """Build a feature class for each crop and set default crop parameters

    Apply the values in the CropParams.txt as defaults to every cell

    Args:
        ini_path (str): file path of the project INI file
        zone_type (str): Zone type (huc8, huc10, county)
        area_threshold (float): CDL area threshold [acres]
        dairy_cuttings (int): Initial number of dairy hay cuttings
        beef_cuttings (int): Initial number of beef hay cuttings
        crop_str (str): comma separate list or range of crops to compare
        overwrite_flag (bool): If True, overwrite existing output rasters
        cleanup_flag (bool): If True, remove temporary files

    Returns:
        None
    """
    logging.info('\nCalculating ET-Demands Spatial Crop Parameters')

    remove_empty_flag = True

    # Input paths
    # DEADBEEF - For now, get cropET folder from INI file
    # This function may eventually be moved into the main cropET code
    config = util.read_ini(ini_path, section='CROP_ET')
    crop_et_sec = 'CROP_ET'
    project_ws = config.get(crop_et_sec, 'project_folder')
    gis_ws = config.get(crop_et_sec, 'gis_folder')
    cells_path = config.get(crop_et_sec, 'cells_path')
    # try: cells_path = config.get(crop_et_sec, 'cells_path')
    # except: cells_path = os.path.join(gis_ws, 'ETCells.shp')
    stations_path = config.get(crop_et_sec, 'stations_path')
    crop_et_ws = config.get(crop_et_sec, 'crop_et_folder')
    bin_ws = os.path.join(crop_et_ws, 'bin')

    try:
        template_ws = config.get(crop_et_sec, 'template_folder')
    except:
        template_ws = os.path.join(os.path.dirname(crop_et_ws), 'static')
    try:
        calibration_ws = config.get(crop_et_sec, 'spatial_cal_folder')
    except:
        calibration_ws = os.path.join(project_ws, 'calibration')

    # Sub folder names
    static_ws = os.path.join(project_ws, 'static')
    pmdata_ws = os.path.join(project_ws, 'pmdata')
    crop_params_path = os.path.join(static_ws, 'CropParams.txt')

    # Input units
    cell_elev_units = 'FEET'
    station_elev_units = 'FEET'

    # Field names
    cell_id_field = 'CELL_ID'
    cell_name_field = 'CELL_NAME'
    crop_acres_field = 'CROP_ACRES'
    dairy_cutting_field = 'Dairy_Cut'
    beef_cutting_field = 'Beef_Cut'

    # Only keep the following ET Cell fields
    keep_field_list = [cell_id_field, cell_name_field, 'AG_ACRES']
    # keep_field_list = ['NLDAS_ID', 'CELL_ID', 'HUC8', 'COUNTY', 'AG_ACRES']
    # keep_field_list = ['FIPS', 'COUNTY']

    # The maximum crop name was ~50 characters
    string_field_len = 50

    # Check input folders
    if not os.path.isdir(crop_et_ws):
        logging.error(('ERROR: The INI cropET folder ' +
                       'does not exist\n  {}').format(crop_et_ws))
        sys.exit()
    elif not os.path.isdir(bin_ws):
        logging.error('\nERROR: The Bin workspace {0} ' +
                      'does not exist\n'.format(bin_ws))
        sys.exit()
    elif not os.path.isdir(project_ws):
        logging.error(('ERROR: The project folder ' +
                       'does not exist\n  {}').format(project_ws))
        sys.exit()
    elif not os.path.isdir(gis_ws):
        logging.error(
            ('ERROR: The GIS folder ' + 'does not exist\n  {}').format(gis_ws))
        sys.exit()
    if '.gdb' not in calibration_ws and not os.path.isdir(calibration_ws):
        os.makedirs(calibration_ws)
    logging.info('\nGIS Workspace:      {0}'.format(gis_ws))
    logging.info('Project Workspace:  {0}'.format(project_ws))
    logging.info('CropET Workspace:   {0}'.format(crop_et_ws))
    logging.info('Bin Workspace:      {0}'.format(bin_ws))
    logging.info('Calib. Workspace:   {0}'.format(calibration_ws))

    # Check input files
    if not os.path.isfile(crop_params_path):
        logging.error('\nERROR: The crop parameters file {} ' +
                      'does not exist\n'.format(crop_params_path))
        sys.exit()
    elif not arcpy.Exists(cells_path):
        logging.error(('\nERROR: The ET Cell shapefile {} ' +
                       'does not exist\n').format(cells_path))
        sys.exit()
    elif not os.path.isfile(stations_path) or not arcpy.Exists(stations_path):
        logging.error(('ERROR: The NLDAS station shapefile ' +
                       'does not exist\n  %s').format(stations_path))
        sys.exit()
    logging.debug('Crop Params Path:   {0}'.format(crop_params_path))
    logging.debug('ET Cells Path:      {0}'.format(cells_path))
    logging.debug('Stations Path:      {0}'.format(stations_path))

    # For now, only allow calibration parameters in separate shapefiles
    ext = '.shp'
    # # Build output geodatabase if necessary
    # if calibration_ws.endswith('.gdb'):
    #     .debug('GDB Path:           {0}'.format(calibration_ws))
    #      = ''
    #      arcpy.Exists(calibration_ws) and overwrite_flag:
    #        try: arcpy.Delete_management(calibration_ws)
    #        except: pass
    #      calibration_ws is not None and not arcpy.Exists(calibration_ws):
    #        arcpy.CreateFileGDB_management(
    #            os.path.dirname(calibration_ws),
    #            os.path.basename(calibration_ws))
    # else:
    #      = '.shp'

    # Field Name, Property, Field Type
    # Property is the string of the CropParameter class property value
    # It will be used to access the property using getattr
    dairy_cutting_field = 'Dairy_Cut'
    beef_cutting_field = 'Beef_Cut'
    param_list = [
        # ['Name', 'name', 'STRING'],
        # ['ClassNum', 'class_number', 'LONG'],
        # ['IsAnnual', 'is_annual', 'SHORT'],
        # ['IrrigFlag', 'irrigation_flag', 'SHORT'],
        # ['IrrigDays', 'days_after_planting_irrigation', 'LONG'],
        # ['Crop_FW', 'crop_fw', 'LONG'],
        # ['WinterCov', 'winter_surface_cover_class', 'SHORT'],
        # ['CropKcMax', 'kc_max', 'FLOAT'],
        ['MAD_Init', 'mad_initial', 'LONG'],
        ['MAD_Mid', 'mad_midseason', 'LONG'],
        # ['RootDepIni', 'rooting_depth_initial', 'FLOAT'],
        # ['RootDepMax', 'rooting_depth_max', 'FLOAT'],
        # ['EndRootGrw', 'end_of_root_growth_fraction_time', 'FLOAT'],
        # ['HeightInit', 'height_initial', 'FLOAT'],
        # ['HeightMax', 'height_max', 'FLOAT'],
        # ['CurveNum', 'curve_number', 'LONG'],
        # ['CurveName', 'curve_name', 'STRING'],
        # ['CurveType', 'curve_type', 'SHORT'],
        # ['PL_GU_Flag', 'flag_for_means_to_estimate_pl_or_gu', 'SHORT'],
        ['T30_CGDD', 't30_for_pl_or_gu_or_cgdd', 'FLOAT'],
        ['PL_GU_Date', 'date_of_pl_or_gu', 'FLOAT'],
        ['CGDD_Tbase', 'tbase', 'FLOAT'],
        ['CGDD_EFC', 'cgdd_for_efc', 'LONG'],
        ['CGDD_Term', 'cgdd_for_termination', 'LONG'],
        ['Time_EFC', 'time_for_efc', 'LONG'],
        ['Time_Harv', 'time_for_harvest', 'LONG'],
        ['KillFrostC', 'killing_frost_temperature', 'Float'],
        # ['InvokeStrs', 'invoke_stress', 'SHORT'],
        # ['CN_Coarse', 'cn_coarse_soil', 'LONG'],
        # ['CN_Medium', 'cn_medium_soil', 'LONG'],
        # ['CN_Fine', 'cn_fine_soil', 'LONG']
    ]
    # if calibration_ws.endswith('.gdb'):
    #     _cutting_field = 'Dairy_Cuttings'
    #     _cutting_field = 'Beef_Cuttings'
    #     _list = [
    #        # ['Name', 'name', 'STRING'],
    #        # ['Class_Number', 'class_number', 'LONG'],
    #        # ['Is_Annual', 'is_annual', 'SHORT'],
    #        # ['Irrigation_Flag', 'irrigation_flag', 'SHORT'],
    #        # ['Irrigation_Days', 'days_after_planting_irrigation', 'LONG'],
    #        # ['Crop_FW', 'crop_fw', 'LONG'],
    #        # ['Winter_Cover_Class', 'winter_surface_cover_class', 'SHORT'],
    #        # ['Crop_Kc_Max', 'kc_max', 'FLOAT'],
    #        # ['MAD_Initial', 'mad_initial', 'LONG'],
    #        # ['MAD_Midseason', 'mad_midseason', 'LONG'],
    #        # ['Root_Depth_Ini', 'rooting_depth_initial', 'FLOAT'],
    #        # ['Root_Depth_Max', 'rooting_depth_max', 'FLOAT'],
    #        # ['End_Root_Growth', 'end_of_root_growth_fraction_time', 'FLOAT'],
    #        # ['Height_Initial', 'height_initial', 'FLOAT'],
    #        # ['Height_Maximum', 'height_max', 'FLOAT'],
    #        # ['Curve_Number', 'curve_number', 'LONG'],
    #        # ['Curve_Name', 'curve_name', 'STRING'],
    #        # ['Curve_Type', 'curve_type', 'SHORT'],
    #        # ['PL_GU_Flag', 'flag_for_means_to_estimate_pl_or_gu', 'SHORT'],
    #        ['T30_CGDD', 't30_for_pl_or_gu_or_cgdd', 'FLOAT'],
    #        ['PL_GU_Date', 'date_of_pl_or_gu', 'FLOAT'],
    #        ['CGDD_Tbase', 'tbase', 'FLOAT'],
    #        ['CGDD_EFC', 'cgdd_for_efc', 'LONG'],
    #        ['CGDD_Termination', 'cgdd_for_termination', 'LONG'],
    #        ['Time_EFC', 'time_for_efc', 'LONG'],
    #        ['Time_Harvest', 'time_for_harvest', 'LONG'],
    #        ['Killing_Crost_C', 'killing_frost_temperature', 'Float'],
    #        # ['Invoke_Stress', 'invoke_stress', 'SHORT'],
    #        # ['CN_Coarse_Soil', 'cn_coarse_soil', 'LONG'],
    #        # ['CN_Medium_Soil', 'cn_medium_soil', 'LONG'],
    #        # ['CN_Fine_Soil', 'cn_fine_soil', 'LONG']
    #    ]

    # Allow user to subset crops and cells from INI
    try:
        crop_skip_list = sorted(
            list(util.parse_int_set(config.get(crop_et_sec,
                                               'crop_skip_list'))))
    except:
        crop_skip_list = []
    try:
        crop_test_list = sorted(
            list(util.parse_int_set(config.get(crop_et_sec,
                                               'crop_test_list'))))
    except:
        crop_test_list = []
    try:
        cell_skip_list = config.get(crop_et_sec, 'cell_skip_list').split(',')
        cell_skip_list = sorted([c.strip() for c in cell_skip_list])
    except:
        cell_skip_list = []
    try:
        cell_test_list = config.get(crop_et_sec, 'cell_test_list').split(',')
        cell_test_list = sorted([c.strip() for c in cell_test_list])
    except:
        cell_test_list = []

    # Overwrite INI crop list with user defined values
    # Could also append to the INI crop list
    if crop_str:
        try:
            crop_test_list = sorted(list(util.parse_int_set(crop_str)))
        # try:
        #     crop_test_list = sorted(list(set(
        #         crop_test_list + list(util.parse_int_set(crop_str)))
        except:
            pass
    # Don't build crop parameter files for non-crops
    crop_skip_list = sorted(
        list(set(crop_skip_list + [44, 45, 46, 55, 56, 57])))

    # crop_test_list = sorted(list(set(crop_test_list + [46])))
    logging.debug('\ncrop_test_list = {0}'.format(crop_test_list))
    logging.debug('crop_skip_list = {0}'.format(crop_skip_list))
    logging.debug('cell_test_list = {0}'.format(cell_test_list))
    logging.debug('cell_test_list = {0}'.format(cell_test_list))

    # Read crop parameters using ET Demands functions/methods
    logging.info('\nReading Default Crop Parameters')
    sys.path.append(bin_ws)
    import crop_parameters
    crop_param_dict = crop_parameters.read_crop_parameters(crop_params_path)

    # arcpy.CheckOutExtension('Spatial')
    # arcpy.env.pyramid = 'NONE 0'
    arcpy.env.overwriteOutput = overwrite_flag
    arcpy.env.parallelProcessingFactor = 8

    # Get list of crops specified in ET cells
    # Currently this may only be crops with CDL acreage
    crop_field_list = [
        field.name for field in arcpy.ListFields(cells_path)
        if re.match('CROP_\d{2}', field.name)
    ]
    logging.debug('Cell crop fields: {}'.format(', '.join(crop_field_list)))
    crop_number_list = [
        int(f_name.split('_')[1]) for f_name in crop_field_list
    ]
    crop_number_list = [
        crop_num for crop_num in crop_number_list
        if not ((crop_test_list and crop_num not in crop_test_list) or
                (crop_skip_list and crop_num in crop_skip_list))
    ]
    logging.info('Cell crop numbers: {}'.format(', '.join(
        list(util.ranges(crop_number_list)))))

    # Get crop acreages for each cell
    crop_acreage_dict = defaultdict(dict)
    field_list = [cell_id_field] + crop_field_list
    with arcpy.da.SearchCursor(cells_path, field_list) as cursor:
        for row in cursor:
            for i, crop_num in enumerate(crop_number_list):
                crop_acreage_dict[crop_num][row[0]] = row[i + 1]

    # Make an empty template crop feature class
    logging.info('')
    crop_template_path = os.path.join(calibration_ws, 'crop_00_template' + ext)
    if overwrite_flag and arcpy.Exists(crop_template_path):
        logging.debug('Overwriting template crop feature class')
        arcpy.Delete_management(crop_template_path)
    if arcpy.Exists(crop_template_path):
        logging.info('Template crop feature class already exists, skipping')
    else:
        logging.info('Building template crop feature class')
        arcpy.CopyFeatures_management(cells_path, crop_template_path)

        # Remove unneeded et cell fields
        for field in arcpy.ListFields(crop_template_path):
            if (field.name not in keep_field_list and field.editable
                    and not field.required):
                logging.debug('  Delete field: {0}'.format(field.name))
                arcpy.DeleteField_management(crop_template_path, field.name)
        field_list = [f.name for f in arcpy.ListFields(crop_template_path)]

        # Add crop acreage field
        if crop_acres_field not in field_list:
            logging.debug('  Add field: {0}'.format(crop_acres_field))
            arcpy.AddField_management(crop_template_path, crop_acres_field,
                                      'Float')
            arcpy.CalculateField_management(crop_template_path,
                                            crop_acres_field, '0',
                                            'PYTHON_9.3')

        # Add crop parameter fields if necessary
        for param_field, param_method, param_type in param_list:
            logging.debug('  Add field: {0}'.format(param_field))
            if param_field not in field_list:
                arcpy.AddField_management(crop_template_path, param_field,
                                          param_type)
        # if dairy_cutting_field not in field_list:
        #     .debug('  Add field: {0}'.format(dairy_cutting_field))
        #     .AddField_management(crop_template_path, dairy_cutting_field, 'Short')
        #     .CalculateField_management(
        #        crop_template_path, dairy_cutting_field, dairy_cuttings, 'PYTHON')
        # if beef_cutting_field not in field_list:
        #     .debug('  Add field: {0}'.format(beef_cutting_field))
        #     .AddField_management(crop_template_path, beef_cutting_field, 'Short')
        #     .CalculateField_management(
        #        crop_template_path, beef_cutting_field, beef_cuttings, 'PYTHON')

    # Add an empty/zero crop field for the field mappings below
    # if len(arcpy.ListFields(cells_path, 'CROP_EMPTY')) == 0:
    #     .AddField_management(cells_path, 'CROP_EMPTY', 'Float')
    #     .CalculateField_management(
    #        cells_path, 'CROP_EMPTY', '0', 'PYTHON_9.3')

    # Process each crop
    logging.info('\nBuild crop feature classes')
    for crop_num in crop_number_list:
        try:
            crop_param = crop_param_dict[crop_num]
        except:
            continue
        logging.info('{0:>2d} {1}'.format(crop_num, crop_param))
        # Replace other characters with spaces, then remove multiple spaces
        crop_name = re.sub('[-"().,/~]', ' ', str(crop_param.name).lower())
        crop_name = ' '.join(crop_name.strip().split()).replace(' ', '_')
        crop_path = os.path.join(
            calibration_ws,
            'crop_{0:02d}_{1}{2}'.format(crop_num, crop_name, ext))
        crop_field = 'CROP_{0:02d}'.format(crop_num)

        # Skip if all zone crop areas are below threshold
        if all(
            [v < area_threshold
             for v in crop_acreage_dict[crop_num].values()]):
            logging.info('  All crop acreaeges below threshold, skipping crop')
            continue

        # Remove existing shapefiles if necessary
        if overwrite_flag and arcpy.Exists(crop_path):
            logging.debug('  Overwriting: {}'.format(
                os.path.basename(crop_path)))
            arcpy.Delete_management(crop_path)

        # Don't check skip list until after existing files are removed
        # if ((crop_test_list and crop_num not in crop_test_list) or
        #     _skip_list and crop_num in crop_skip_list)):
        #     .debug('  Skipping')
        #

        # Copy ET cells for each crop if needed
        if arcpy.Exists(crop_path):
            logging.debug('  Shapefile already exists, skipping')
            continue
        else:
            # logging.debug('    {0}'.format(crop_path))
            arcpy.Copy_management(crop_template_path, crop_path)
            # Remove extra fields
            # for field in arcpy.ListFields(crop_path):
            #      field.name not in keep_field_list:
            #        # logging.debug('    {0}'.format(field.name))
            #        arcpy.DeleteField_management(crop_path, field.name)

        # Add alfalfa cutting field
        if crop_num in [1, 2, 3, 4]:
            if len(arcpy.ListFields(crop_path, dairy_cutting_field)) == 0:
                logging.debug('  Add field: {0}'.format(dairy_cutting_field))
                arcpy.AddField_management(crop_path, dairy_cutting_field,
                                          'Short')
                arcpy.CalculateField_management(crop_path, dairy_cutting_field,
                                                dairy_cuttings, 'PYTHON')
            if len(arcpy.ListFields(crop_path, beef_cutting_field)) == 0:
                logging.debug('  Add field: {0}'.format(beef_cutting_field))
                arcpy.AddField_management(crop_path, beef_cutting_field,
                                          'Short')
                arcpy.CalculateField_management(crop_path, beef_cutting_field,
                                                beef_cuttings, 'PYTHON')

        # Write default crop parameters to file
        field_list = [p[0]
                      for p in param_list] + [cell_id_field, crop_acres_field]
        with arcpy.da.UpdateCursor(crop_path, field_list) as cursor:
            for row in cursor:
                # Skip and/or remove zones without crop acreage
                if crop_acreage_dict[crop_num][row[-2]] < area_threshold:
                    if remove_empty_flag:
                        cursor.deleteRow()
                    continue
                # Write parameter values
                for i, (param_field, param_method,
                        param_type) in enumerate(param_list):
                    row[i] = getattr(crop_param, param_method)
                # Write crop acreage
                row[-1] = crop_acreage_dict[crop_num][row[-2]]
                cursor.updateRow(row)
コード例 #5
0
def main(ini_path,
         area_threshold=10,
         dairy_cuttings=5,
         beef_cuttings=4,
         crop_str='',
         overwrite_flag=False):
    """Build a feature class for each crop and set default crop parameters

    Apply the values in the CropParams.txt as defaults to every cell

    Parameters
    ----------
    ini_path : str
        File path of the parameter INI file.
    area_threshold : float
        CDL area threshold [acres].
    dairy_cuttings : int
        Initial number of dairy hay cuttings.
    beef_cuttings : int
        Initial number of beef hay cuttings.
    crop_str : str
        Comma separated list or range of crops to compare (no spaces, ex: 1,2,4-6)
    overwrite_flag : bool
        If True, overwrite existing output rasters.

    Returns
    -------
    None

    """
    logging.info('\nCalculating ET-Demands Spatial Crop Parameters')

    remove_empty_flag = True

    # Input paths
    # DEADBEEF - For now, get cropET folder from INI file
    # This function may eventually be moved into the main cropET code
    crop_et_sec = 'CROP_ET'
    config = util.read_ini(ini_path, section=crop_et_sec)

    try:
        project_ws = config.get(crop_et_sec, 'project_folder')
    except:
        logging.error('project_folder parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        gis_ws = config.get(crop_et_sec, 'gis_folder')
    except:
        logging.error('gis_folder parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        cells_path = config.get(crop_et_sec, 'cells_path')
    except:
        # cells_path = os.path.join(gis_ws, 'ETCells.shp')
        logging.error('et_cells_path parameter must be set in the INI file, '
                      'exiting')
        return False
    try:
        stations_path = config.get(crop_et_sec, 'stations_path')
    except:
        logging.error('stations_path parameter must be set in the INI file, '
                      'exiting')
        return False

    try:
        crop_params_name = config.get(crop_et_sec, 'crop_params_name')
    except:
        logging.error(
            'crop_params_name parameter must be set in the INI file, '
            'exiting')
        return False

    crop_et_ws = config.get(crop_et_sec, 'crop_et_folder')
    bin_ws = os.path.join(crop_et_ws, 'bin')

    try:
        calibration_ws = config.get(crop_et_sec, 'spatial_cal_folder')
    except:
        calibration_ws = os.path.join(project_ws, 'calibration')

    # Sub folder names
    static_ws = os.path.join(project_ws, 'static')
    crop_params_path = os.path.join(static_ws, crop_params_name)

    # ET cells field names
    cell_id_field = 'CELL_ID'
    cell_name_field = 'CELL_NAME'
    crop_acres_field = 'CROP_ACRES'

    # Only keep the following ET Cell fields
    keep_field_list = [cell_id_field, cell_name_field, 'AG_ACRES']
    # keep_field_list = ['CELL_ID', 'STATION_ID', 'HUC8', 'HUC10', 'GRIDMET_ID',
    #                    'COUNTYNAME', 'AG_ACRES']
    # keep_field_list = ['FIPS', 'COUNTYNAME']

    # Check input folders
    if not os.path.isdir(crop_et_ws):
        logging.error('\nERROR: The INI cropET folder does not exist'
                      '\n  {}'.format(crop_et_ws))
        sys.exit()
    elif not os.path.isdir(bin_ws):
        logging.error('\nERROR: The bin workspace does not exist'
                      '\n  {}'.format(bin_ws))
        sys.exit()
    elif not os.path.isdir(project_ws):
        logging.error('\nERROR: The project folder does not exist'
                      '\n  {}'.format(project_ws))
        sys.exit()
    elif not os.path.isdir(gis_ws):
        logging.error('\nERROR: The GIS folder does not exist'
                      '\n  {}'.format(gis_ws))
        sys.exit()
    if '.gdb' not in calibration_ws and not os.path.isdir(calibration_ws):
        os.makedirs(calibration_ws)
    logging.info('\nGIS Workspace:      {}'.format(gis_ws))
    logging.info('Project Workspace:  {}'.format(project_ws))
    logging.info('CropET Workspace:   {}'.format(crop_et_ws))
    logging.info('Bin Workspace:      {}'.format(bin_ws))
    logging.info('Calib. Workspace:   {}'.format(calibration_ws))

    # Check input files
    if not os.path.isfile(crop_params_path):
        logging.error('\nERROR: The crop parameters file does not exist'
                      '\n  {}'.format(crop_params_path))
        sys.exit()
    elif not os.path.isfile(cells_path):
        logging.error('\nERROR: The ET Cell shapefile does not exist'
                      '\n  {}'.format(cells_path))
        sys.exit()
    elif not os.path.isfile(stations_path):
        logging.error('\nERROR: The weather station shapefile does not exist'
                      '\n  {}'.format(stations_path))
        sys.exit()
    logging.debug('Crop Params Path:   {}'.format(crop_params_path))
    logging.debug('ET Cells Path:      {}'.format(cells_path))
    logging.debug('Stations Path:      {}'.format(stations_path))

    # For now, only allow calibration parameters in separate shapefiles
    ext = '.shp'
    # # Build output geodatabase if necessary
    # if calibration_ws.endswith('.gdb'):
    #     logging.debug('GDB Path:           {}'.format(calibration_ws))
    #     ext = ''
    #     _arcpy.exists(calibration_ws) and overwrite_flag:
    #         try: _arcpy.delete(calibration_ws)
    #         except: pass
    #     if calibration_ws is not None and not _arcpy.exists(calibration_ws):
    #         arcpy.CreateFileGDB_management(
    #             os.path.dirname(calibration_ws),
    #             os.path.basename(calibration_ws))
    # else:
    #     ext = '.shp'

    # Field Name, Property, Field Type
    # Property is the string of the CropParameter class property value
    # It will be used to access the property using getattr
    dairy_cutting_field = 'Dairy_Cut'
    beef_cutting_field = 'Beef_Cut'
    param_list = [
        # ['Name', 'name', ogr.OFTString],
        # ['ClassNum', 'class_number', ogr.OFTInteger],
        # ['IsAnnual', 'is_annual', 'SHORT'],
        # ['IrrigFlag', 'irrigation_flag', 'SHORT'],
        # ['IrrigDays', 'days_after_planting_irrigation', ogr.OFTInteger],
        # ['Crop_FW', 'crop_fw', ogr.OFTInteger],
        # ['WinterCov', 'winter_surface_cover_class', 'SHORT'],
        # ['CropKcMax', 'kc_max', ogr.OFTReal],
        ['MAD_Init', 'mad_initial', ogr.OFTInteger],
        ['MAD_Mid', 'mad_midseason', ogr.OFTInteger],
        # ['RootDepIni', 'rooting_depth_initial', ogr.OFTReal],
        # ['RootDepMax', 'rooting_depth_max', ogr.OFTReal],
        # ['EndRootGrw', 'end_of_root_growth_fraction_time', ogr.OFTReal],
        # ['HeightInit', 'height_initial', ogr.OFTReal],
        # ['HeightMax', 'height_max', ogr.OFTReal],
        # ['CurveNum', 'curve_number', ogr.OFTInteger],
        # ['CurveName', 'curve_name', ogr.OFTString],
        # ['CurveType', 'curve_type', 'SHORT'],
        # ['PL_GU_Flag', 'flag_for_means_to_estimate_pl_or_gu', 'SHORT'],
        ['T30_CGDD', 't30_for_pl_or_gu_or_cgdd', ogr.OFTReal],
        ['PL_GU_Date', 'date_of_pl_or_gu', ogr.OFTReal],
        ['CGDD_Tbase', 'tbase', ogr.OFTReal],
        ['CGDD_EFC', 'cgdd_for_efc', ogr.OFTInteger],
        ['CGDD_Term', 'cgdd_for_termination', ogr.OFTInteger],
        ['Time_EFC', 'time_for_efc', ogr.OFTInteger],
        ['Time_Harv', 'time_for_harvest', ogr.OFTInteger],
        ['KillFrostC', 'killing_frost_temperature', ogr.OFTReal],
        # ['InvokeStrs', 'invoke_stress', 'SHORT'],
        # ['CN_Coarse', 'cn_coarse_soil', ogr.OFTInteger],
        # ['CN_Medium', 'cn_medium_soil', ogr.OFTInteger],
        # ['CN_Fine', 'cn_fine_soil', ogr.OFTInteger]
    ]
    # if calibration_ws.endswith('.gdb'):
    #     dairy_cutting_field = 'Dairy_Cuttings'
    #     beef_cutting_field = 'Beef_Cuttings'
    #     param_list  = [
    #        # ['Name', 'name', 'STRING'],
    #        # ['Class_Number', 'class_number', ogr.OFTInteger],
    #        # ['Is_Annual', 'is_annual', 'SHORT'],
    #        # ['Irrigation_Flag', 'irrigation_flag', 'SHORT'],
    #        # ['Irrigation_Days', 'days_after_planting_irrigation', ogr.OFTInteger],
    #        # ['Crop_FW', 'crop_fw', ogr.OFTInteger],
    #        # ['Winter_Cover_Class', 'winter_surface_cover_class', 'SHORT'],
    #        # ['Crop_Kc_Max', 'kc_max', ogr.OFTReal],
    #        # ['MAD_Initial', 'mad_initial', ogr.OFTInteger],
    #        # ['MAD_Midseason', 'mad_midseason', ogr.OFTInteger],
    #        # ['Root_Depth_Ini', 'rooting_depth_initial', ogr.OFTReal],
    #        # ['Root_Depth_Max', 'rooting_depth_max', ogr.OFTReal],
    #        # ['End_Root_Growth', 'end_of_root_growth_fraction_time', ogr.OFTReal],
    #        # ['Height_Initial', 'height_initial', ogr.OFTReal],
    #        # ['Height_Maximum', 'height_max', ogr.OFTReal],
    #        # ['Curve_Number', 'curve_number', ogr.OFTInteger],
    #        # ['Curve_Name', 'curve_name', ogr.OFTString],
    #        # ['Curve_Type', 'curve_type', 'SHORT'],
    #        # ['PL_GU_Flag', 'flag_for_means_to_estimate_pl_or_gu', 'SHORT'],
    #        ['T30_CGDD', 't30_for_pl_or_gu_or_cgdd', ogr.OFTReal],
    #        ['PL_GU_Date', 'date_of_pl_or_gu', ogr.OFTReal],
    #        ['CGDD_Tbase', 'tbase', ogr.OFTReal],
    #        ['CGDD_EFC', 'cgdd_for_efc', ogr.OFTInteger],
    #        ['CGDD_Termination', 'cgdd_for_termination', ogr.OFTInteger],
    #        ['Time_EFC', 'time_for_efc', ogr.OFTInteger],
    #        ['Time_Harvest', 'time_for_harvest', ogr.OFTInteger],
    #        ['Killing_Crost_C', 'killing_frost_temperature', ogr.OFTReal],
    #        # ['Invoke_Stress', 'invoke_stress', 'SHORT'],
    #        # ['CN_Coarse_Soil', 'cn_coarse_soil', ogr.OFTInteger],
    #        # ['CN_Medium_Soil', 'cn_medium_soil', ogr.OFTInteger],
    #        # ['CN_Fine_Soil', 'cn_fine_soil', ogr.OFTInteger]
    #    ]

    crop_add_list = []
    if crop_str:
        try:
            crop_add_list = sorted(list(util.parse_int_set(crop_str)))
        # try:
        #     crop_test_list = sorted(list(set(
        #         crop_test_list + list(util.parse_int_set(crop_str)))
        except:
            pass
    # Don't build crop parameter files for non-crops
    crop_skip_list = sorted(list(set([44, 45, 46, 55, 56, 57])))

    # crop_test_list = sorted(list(set(crop_test_list + [46])))
    logging.info('\ncrop_add_list = {}'.format(crop_add_list))

    # Read crop parameters using ET Demands functions/methods
    logging.info('\nReading default crop parameters')
    sys.path.append(bin_ws)
    import crop_parameters
    crop_param_dict = crop_parameters.read_crop_parameters(crop_params_path)

    # Get list of crops specified in ET cells
    # Currently this may only be crops with CDL acreage
    crop_field_list = sorted([
        field for field in _arcpy.list_fields(cells_path)
        if re.match('CROP_\d{2}', field)
    ])
    crop_number_list = [int(f.split('_')[-1]) for f in crop_field_list]
    logging.info('Cell crop numbers: {}'.format(', '.join(
        list(util.ranges(crop_number_list)))))
    logging.debug('Cell crop fields: {}'.format(', '.join(crop_field_list)))

    # Get crop acreages for each cell
    # DEADBEEF - Does this dict need to be keyed by crop then cell_id?
    #   Could it be changed to cell_id, crop or fid, crop to make it easier to
    #   write to the shapefile using update_cursor()?
    crop_acreage_dict = defaultdict(dict)
    field_list = [cell_id_field] + crop_field_list
    for fid, row in _arcpy.search_cursor(cells_path, field_list).items():
        for crop_field, crop_num in zip(crop_field_list, crop_number_list):
            if crop_skip_list and crop_num in crop_skip_list:
                continue
            elif crop_num in crop_add_list:
                crop_acreage_dict[crop_num][row[cell_id_field]] = 0
            elif row[crop_field]:
                crop_acreage_dict[crop_num][
                    row[cell_id_field]] = row[crop_field]
            else:
                crop_acreage_dict[crop_num][row[cell_id_field]] = 0

    crop_number_list = sorted(list(set(crop_number_list) | set(crop_add_list)))

    # Make an empty template crop feature class
    logging.info('')
    crop_template_path = os.path.join(calibration_ws, 'crop_00_template' + ext)
    if overwrite_flag and _arcpy.exists(crop_template_path):
        logging.debug('Overwriting template crop feature class')
        _arcpy.delete(crop_template_path)
    if _arcpy.exists(crop_template_path):
        logging.info('Template crop feature class already exists, skipping')
    else:
        logging.info('Building template crop feature class')
        _arcpy.copy(cells_path, crop_template_path)

        # Remove unneeded et cell fields
        for field in _arcpy.list_fields(crop_template_path):
            # if (field not in keep_field_list and
            #         field.editable and not field.required):
            if field not in keep_field_list:
                logging.debug('  Delete field: {}'.format(field))
                _arcpy.delete_field(crop_template_path, field)
        field_list = _arcpy.list_fields(crop_template_path)

        # Add crop acreage field
        if crop_acres_field not in field_list:
            logging.debug('  Add field: {}'.format(crop_acres_field))
            _arcpy.add_field(crop_template_path, crop_acres_field, ogr.OFTReal)
            _arcpy.calculate_field(crop_template_path, crop_acres_field, '0')

        # Add crop parameter fields if necessary
        for param_field, param_method, param_type in param_list:
            logging.debug('  Add field: {}'.format(param_field))
            if param_field not in field_list:
                _arcpy.add_field(crop_template_path, param_field, param_type)
        # if dairy_cutting_field not in field_list:
        #     logging.debug('  Add field: {}'.format(dairy_cutting_field))
        #     _arcpy.add_field(crop_template_path, dairy_cutting_field,
        #                      ogr.OFTInteger)
        #     _arcpy.calculate_field(crop_template_path, dairy_cutting_field,
        #                            dairy_cuttings)
        # if beef_cutting_field not in field_list:
        #     logging.debug('  Add field: {}'.format(beef_cutting_field))
        #     _arcpy.add_field(crop_template_path, beef_cutting_field,
        #                      ogr.OFTInteger)
        #     _arcpy.calculate_field(crop_template_path, beef_cutting_field,
        #                            beef_cuttings)

    # Add an empty/zero crop field for the field mappings below
    # if 'CROP_EMPTY' not in _arcpy.list_fields(cells_path):
    #     _arcpy.add_field(cells_path, 'CROP_EMPTY', ogr.OFTReal)
    #     _arcpy.calculate_field(cells_path, 'CROP_EMPTY', '0')

    # Process each crop
    logging.info('\nBuilding crop feature classes')
    for crop_num in crop_number_list:
        try:
            crop_param = crop_param_dict[crop_num]
        except:
            continue
        logging.info('{:>2d} {}'.format(crop_num, crop_param.name))
        logging.debug('{}'.format(crop_param))
        # Replace other characters with spaces, then remove multiple spaces
        crop_name = re.sub('[-"().,/~]', ' ', str(crop_param.name).lower())
        crop_name = ' '.join(crop_name.strip().split()).replace(' ', '_')
        crop_path = os.path.join(
            calibration_ws,
            'crop_{0:02d}_{1}{2}'.format(crop_num, crop_name, ext))
        # crop_field = 'CROP_{:02d}'.format(crop_num)

        # Don't check crops in add list
        if crop_num in crop_add_list:
            pass
        # Skip if all zone crop areas are below threshold
        elif all(
            [v < area_threshold
             for v in crop_acreage_dict[crop_num].values()]):
            logging.info('** Skipping Crop {}, All crop acreages below'
                         ' threshold'.format(crop_num))
            continue

        # Remove existing shapefiles if necessary
        if overwrite_flag and _arcpy.exists(crop_path):
            logging.debug('  Overwriting: {}'.format(
                os.path.basename(crop_path)))
            _arcpy.delete(crop_path)

        # Don't check skip list until after existing files are removed
        # if ((crop_test_list and crop_num not in crop_test_list) or
        #     _skip_list and crop_num in crop_skip_list)):
        #     .debug('  Skipping')

        # Copy ET cells for each crop if needed
        if _arcpy.exists(crop_path):
            logging.debug('  Shapefile already exists, skipping')
            continue
        else:
            # logging.debug('    {}'.format(crop_path))
            _arcpy.copy(crop_template_path, crop_path)
            # Remove extra fields
            # for field in _arcpy.list_fields(crop_path):
            #     if field not in keep_field_list:
            #         # logging.debug('    {}'.format(field))
            #         _arcpy.delete_field(crop_path, field)

        # Add alfalfa cutting field
        if crop_num in [1, 2, 3, 4]:
            if dairy_cutting_field not in _arcpy.list_fields(crop_path):
                logging.debug('  Add field: {}'.format(dairy_cutting_field))
                _arcpy.add_field(crop_path, dairy_cutting_field,
                                 ogr.OFTInteger)
                _arcpy.calculate_field(crop_path, dairy_cutting_field,
                                       str(dairy_cuttings))
            if beef_cutting_field not in _arcpy.list_fields(crop_path):
                logging.debug('  Add field: {}'.format(beef_cutting_field))
                _arcpy.add_field(crop_path, beef_cutting_field, ogr.OFTInteger)
                _arcpy.calculate_field(crop_path, beef_cutting_field,
                                       str(beef_cuttings))

        # Write default crop parameters to file
        # Note: Couldn't use _arcpy.udpate_cursor directly since the
        # crop_acreage_dict is keyed by crop_num then by cell_id (not FID first)
        input_driver = _arcpy.get_ogr_driver(crop_path)
        input_ds = input_driver.Open(crop_path, 1)
        input_lyr = input_ds.GetLayer()
        for input_ftr in input_lyr:
            cell_id = input_ftr.GetField(
                input_ftr.GetFieldIndex(cell_id_field))

            # Don't remove zero acreage crops if in add list
            if crop_num in crop_add_list:
                pass
            # Skip and/or remove zones without crop acreage
            elif crop_acreage_dict[crop_num][cell_id] < area_threshold:
                if remove_empty_flag:
                    input_lyr.DeleteFeature(input_ftr.GetFID())
                continue

            # Write parameter values
            for param_field, param_method, param_type in param_list:
                input_ftr.SetField(input_ftr.GetFieldIndex(param_field),
                                   getattr(crop_param, param_method))

            # Write crop acreage
            if crop_num not in crop_add_list:
                input_ftr.SetField(input_ftr.GetFieldIndex(crop_acres_field),
                                   crop_acreage_dict[crop_num][cell_id])

            input_lyr.SetFeature(input_ftr)
        input_ds = None