def crt_fill_parameters(config_path): """Calculate GSFLOW CRT Fill Parameters Parameters ---------- config_path : str Project configuration file (.ini) path. Returns ------- None """ # Initialize hru_parameters class hru = support.HRUParameters(config_path) # Open input parameter config file inputs_cfg = ConfigParser.ConfigParser() try: inputs_cfg.readfp(open(config_path)) except Exception as e: logging.error('\nERROR: Config file could not be read, ' 'is not an input file, or does not exist\n' ' config_file = {}\n' ' Exception: {}\n'.format(config_path, e)) sys.exit() # Log DEBUG to file log_file_name = 'crt_fill_parameters_log.txt' log_console = logging.FileHandler(filename=os.path.join( hru.log_ws, log_file_name), mode='w') log_console.setLevel(logging.DEBUG) log_console.setFormatter(logging.Formatter('%(message)s')) logging.getLogger('').addHandler(log_console) logging.info('\nGSFLOW CRT Fill Parameters') # Parameters exit_seg = 0 # CRT Parameters try: use_crt_fill_flag = inputs_cfg.getboolean('INPUTS', 'use_crt_fill_flag') except ConfigParser.NoOptionError: use_crt_fill_flag = False logging.info(' Missing INI parameter, setting {} = {}'.format( 'use_crt_fill_flag', use_crt_fill_flag)) try: crt_hruflg = inputs_cfg.getint('INPUTS', 'crt_hruflg') except ConfigParser.NoOptionError: crt_hruflg = 0 logging.info(' Missing INI parameter, setting {} = {}'.format( 'crt_hruflg', crt_hruflg)) try: crt_flowflg = inputs_cfg.getint('INPUTS', 'crt_flowflg') except ConfigParser.NoOptionError: crt_flowflg = 1 logging.info(' Missing INI parameter, setting {} = {}'.format( 'crt_flowflg', crt_flowflg)) try: crt_dpit = inputs_cfg.getfloat('INPUTS', 'crt_dpit') except ConfigParser.NoOptionError: crt_dpit = 0.01 logging.info(' Missing INI parameter, setting {} = {}'.format( 'crt_dpit', crt_dpit)) try: crt_outitmax = inputs_cfg.getint('INPUTS', 'crt_outitmax') except ConfigParser.NoOptionError: crt_outitmax = 100000 logging.info(' Missing INI parameter, setting {} = {}'.format( 'crt_outitmax', crt_outitmax)) # Intentionally not allowing user to change this value crt_iprn = 1 # CRT Fill Parameters fill_ws_name = 'fill_work' fill_strmflg = 0 fill_visflg = 0 fill_ifill = 1 # CRT Executable crt_exe_path = inputs_cfg.get('INPUTS', 'crt_exe_path') output_name = 'outputstat.txt' # Check input paths if not arcpy.Exists(hru.polygon_path): logging.error('\nERROR: Fishnet ({}) does not exist\n'.format( hru.polygon_path)) sys.exit() # Check that input fields exist and have data # Fields generated by hru_parameters for f in [hru.type_field, hru.row_field, hru.col_field]: if not arcpy.ListFields(hru.polygon_path, f): logging.error( '\nERROR: Input field {} is not present in fishnet' '\nERROR: Try re-running hru_parameters.py\n'.format(f)) sys.exit() elif support.field_stat_func(hru.polygon_path, f, 'MAXIMUM') == 0: logging.error( '\nERROR: Input field {} contains only 0' '\nERROR: Try re-running hru_parameters.py\n'.format(f)) sys.exit() # Fields generated by dem_2_streams for f in [ hru.irunbound_field, hru.iseg_field, hru.flow_dir_field, hru.outflow_field, hru.subbasin_field ]: if not arcpy.ListFields(hru.polygon_path, f): logging.error( '\nERROR: Input field {} is not present in fishnet' '\nERROR: Try re-running dem_2_streams.py\n'.format(f)) sys.exit() elif support.field_stat_func(hru.polygon_path, f, 'MAXIMUM') == 0: logging.error( '\nERROR: Input field {} contains only 0' '\nERROR: Try re-running dem_2_streams.py\n'.format(f)) sys.exit() # Build output folder if necessary fill_ws = os.path.join(hru.param_ws, fill_ws_name) if not os.path.isdir(fill_ws): os.makedirs(fill_ws) # Copy CRT executable if necessary crt_exe_name = os.path.basename(crt_exe_path) if not os.path.isfile(os.path.join(fill_ws, crt_exe_name)): shutil.copy(crt_exe_path, fill_ws) if not os.path.isfile(os.path.join(fill_ws, crt_exe_name)): logging.error('\nERROR: CRT executable ({}) does not exist\n'.format( os.path.join(fill_ws, crt_exe_name))) sys.exit() # Fill files fill_hru_casc_path = os.path.join(fill_ws, 'HRU_CASC.DAT') fill_outflow_hru_path = os.path.join(fill_ws, 'OUTFLOW_HRU.DAT') fill_land_elev_path = os.path.join(fill_ws, 'LAND_ELEV.DAT') fill_xy_path = os.path.join(fill_ws, 'XY.DAT') # Output names # dem_adj_raster_name = 'dem_adj' # hru_type_raster_name = 'hru_type' # lakes_raster_name = 'lakes' # streams_raster_name = 'streams' # iseg_raster_name = 'iseg' # irunbound_raster_name = 'irunbound' # Output raster paths # dem_adj_raster = os.path.join(fill_ws, dem_adj_raster_name + '.img') # hru_type_raster = os.path.join(fill_ws, hru_type_raster_name + '.img') # Output ascii paths # a_fmt = '{}_ascii.txt' # dem_adj_ascii = os.path.join(fill_ws, a_fmt.format(dem_adj_raster_name)) # hru_type_ascii = os.path.join(fill_ws, a_fmt.format(hru_type_raster_name)) # Set ArcGIS environment variables arcpy.CheckOutExtension('Spatial') env.overwriteOutput = True # env.pyramid = 'PYRAMIDS -1' env.pyramid = 'PYRAMIDS 0' env.workspace = fill_ws env.scratchWorkspace = hru.scratch_ws # Add fields if necessary logging.info('\nAdding fields if necessary') support.add_field_func(hru.polygon_path, hru.krch_field, 'LONG') support.add_field_func(hru.polygon_path, hru.irch_field, 'LONG') support.add_field_func(hru.polygon_path, hru.jrch_field, 'LONG') support.add_field_func(hru.polygon_path, hru.iseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.reach_field, 'LONG') # add_field_func(hru.polygon_path, hru.rchlen_field, 'LONG') support.add_field_func(hru.polygon_path, hru.maxreach_field, 'LONG') support.add_field_func(hru.polygon_path, hru.outseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.irunbound_field, 'LONG') support.add_field_func(hru.polygon_path, hru.crt_elev_field, 'DOUBLE') support.add_field_func(hru.polygon_path, hru.crt_fill_field, 'DOUBLE') # Calculate KRCH, IRCH, JRCH for stream segments logging.info('\nKRCH, IRCH, & JRCH for streams') fields = [ hru.type_field, hru.iseg_field, hru.row_field, hru.col_field, hru.krch_field, hru.irch_field, hru.jrch_field ] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if (int(row[0]) in [1, 3] and int(row[1]) > 0): row[4], row[5], row[6] = 1, int(row[2]), int(row[3]) else: row[4], row[5], row[6] = 0, 0, 0 update_c.updateRow(row) # Get list of segments and downstream cell for each stream/lake cell # Downstream is calculated from flow direction # Use IRUNBOUND instead of ISEG, since ISEG will be zeroed for lakes # DEADBEEF - I don't think ISEG will be zero for lakes anymore logging.info('Cell out-flow dictionary') cell_dict = dict() fields = [ hru.type_field, hru.krch_field, hru.lake_id_field, hru.iseg_field, hru.irunbound_field, hru.dem_adj_field, hru.flow_dir_field, hru.col_field, hru.row_field, hru.id_field ] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # Skip inactive cells if int(row[0]) == 0: continue # Skip non-stream and non-lake cells elif (int(row[1]) == 0 and int(row[2]) == 0): continue # ROW / COL cell = (int(row[7]), int(row[8])) # Read in parameters # HRU_ID, ISEG, support.next_row_col(FLOW_DIR, CELL), DEM_ADJ, X, X, X cell_dict[cell] = [ int(row[9]), int(row[4]), support.next_row_col(int(row[6]), cell), float(row[5]), 0, 0, 0 ] # Build list of unique segments iseg_list = sorted(list(set([v[1] for v in cell_dict.values()]))) print(iseg_list) # Calculate IREACH and OUTSEG logging.info('Calculate {} and {}'.format(hru.reach_field, hru.outseg_field)) outseg_dict = dict() for iseg in sorted(iseg_list): logging.debug(' Segment: {}'.format(iseg)) # Subset of cell_dict for current iseg iseg_dict = dict([(k, v) for k, v in cell_dict.items() if v[1] == iseg]) # List of all cells in current iseg iseg_cells = iseg_dict.keys() # List of out_cells for all cells in current iseg out_cells = [value[2] for value in iseg_dict.values()] # Every iseg will (should?) have one out_cell out_cell = list(set(out_cells) - set(iseg_cells)) # Process streams and lakes separately # Streams if iseg > 0: # If there is more than one out_cell # there is a problem with the stream network if len(out_cell) != 1: logging.error( '\nERROR: ISEG {} has more than one out put cell' '\n Out cells: {}' '\n Check for streams exiting then re-entering a lake' '\n Lake cell elevations may not be constant\n'.format( iseg, out_cell)) sys.exit() # If not output cell, assume edge of domain try: outseg = cell_dict[out_cell[0]][1] except KeyError: outseg = exit_seg # Track sub-basin outseg outseg_dict[iseg] = outseg # Calculate reach number for each cell reach_dict = dict() start_cell = list(set(iseg_cells) - set(out_cells))[0] for i in range(len(out_cells)): # logging.debug(' Reach: {} Cell: {}'.format(i+1, start_cell)) reach_dict[start_cell] = i + 1 start_cell = iseg_dict[start_cell][2] # For each cell in iseg, save outseg, reach, & maxreach for iseg_cell in iseg_cells: cell_dict[iseg_cell][4:] = [ outseg, reach_dict[iseg_cell], len(iseg_cells) ] del reach_dict, start_cell # Lakes else: # For lake cells, there can be multiple outlets if all of them # are to inactive cells or out of the model # Otherwise, like streams, there should only be one outcell per iseg logging.debug(' Length: {}'.format(len(out_cells))) if len(out_cell) == 1: try: outseg = cell_dict[out_cell[0]][1] except KeyError: outseg = exit_seg elif (len(out_cell) != 1 and all(x[0] not in cell_dict.keys() for x in out_cell)): outseg = exit_seg logging.debug(' All out cells are inactive, setting outseg ' 'to exit_seg {}'.format(exit_seg)) else: logging.error( '\nERROR: ISEG {} has more than one out put cell' '\n Out cells: {}' '\n Check for streams exiting then re-entering a lake' '\n Lake cell elevations may not be constant\n'.format( iseg, out_cell)) raw_input('ENTER') # Track sub-basin outseg outseg_dict[iseg] = outseg # For each lake segment cell, only save outseg # All lake cells are routed directly to the outseg for iseg_cell in iseg_cells: cell_dict[iseg_cell][4:] = [outseg, 0, 0] del outseg del iseg_dict, iseg_cells, iseg del out_cells, out_cell # Saving ireach and outseg logging.info('Save {} and {}'.format(hru.reach_field, hru.outseg_field)) fields = [ hru.type_field, hru.iseg_field, hru.col_field, hru.row_field, hru.outseg_field, hru.reach_field, hru.maxreach_field ] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: # if (int(row[0]) > 0 and int(row[1]) > 0): # #DEADBEEF - I'm not sure why only iseg > 0 in above line # DEADBEEF - This should set outseg for streams and lakes if (int(row[0]) > 0 and int(row[1]) != 0): row[4:] = cell_dict[(int(row[2]), int(row[3]))][4:] else: row[4:] = [0, 0, 0] update_c.updateRow(row) # Set all lake iseg to 0 logging.info('Lake {}'.format(hru.iseg_field)) update_rows = arcpy.UpdateCursor(hru.polygon_path) for row in update_rows: if int(row.getValue(hru.type_field)) != 2: continue iseg = int(row.getValue(hru.iseg_field)) if iseg < 0: row.setValue(hru.iseg_field, 0) update_rows.updateRow(row) del row, iseg del update_rows # Set environment parameters env.extent = hru.extent env.cellsize = hru.cs env.outputCoordinateSystem = hru.sr # # Build rasters # logging.info('\nOutput model grid rasters') # arcpy.PolygonToRaster_conversion( # hru.polygon_path, hru.type_field, hru_type_raster, # 'CELL_CENTER', '', hru.cs) # arcpy.PolygonToRaster_conversion( # hru.polygon_path, hru.dem_adj_field, dem_adj_raster, # 'CELL_CENTER', '', hru.cs) # # # Build rasters # logging.info('Output model grid ascii') # arcpy.RasterToASCII_conversion(hru_type_raster, hru_type_ascii) # arcpy.RasterToASCII_conversion(dem_adj_raster, dem_adj_ascii) logging.debug('\nRemoving existing CRT fill files') if os.path.isfile(fill_outflow_hru_path): os.remove(fill_outflow_hru_path) if os.path.isfile(fill_hru_casc_path): os.remove(fill_hru_casc_path) if os.path.isfile(fill_land_elev_path): os.remove(fill_land_elev_path) if os.path.isfile(fill_xy_path): os.remove(fill_xy_path) # Input parameters files for Cascade Routing Tool (CRT) logging.info('\nBuilding output CRT fill files') # Generate OUTFLOW_HRU.DAT for CRT # Outflow cells exit the model to inactive cells or out of the domain # Outflow field is set in dem_2_streams logging.info(' {}'.format(os.path.basename(fill_outflow_hru_path))) outflow_hru_list = [] fields = [ hru.type_field, hru.outflow_field, hru.subbasin_field, hru.row_field, hru.col_field ] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): if int(row[0]) != 0 and int(row[1]) == 1: outflow_hru_list.append([int(row[3]), int(row[4])]) if outflow_hru_list: with open(fill_outflow_hru_path, 'w+') as f: f.write('{} NUMOUTFLOWHRU\n'.format(len(outflow_hru_list))) for i, outflow_hru in enumerate(outflow_hru_list): f.write('{} {} {} OUTFLOW_ID ROW COL\n'.format( i + 1, outflow_hru[0], outflow_hru[1])) f.close() else: logging.error('\nERROR: No OUTFLOWHRU points, exiting') sys.exit() del outflow_hru_list # # DEADBEEF - Old method for setting OUTFLOW_HRU.DAT # # Only streams that flow to real gauges are used # # Generate OUTFLOW_HRU.DAT for CRT # logging.info(' {}'.format( # os.path.basename(fill_outflow_hru_path))) # outflow_hru_list = [] # fields = [ # hru.type_field, hru.iseg_field, hru.outseg_field, hru.reach_field, # hru.maxreach_field, hru.col_field, hru.row_field] # for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # if int(row[0]) != 1 or int(row[1]) == 0: # continue # if int(row[2]) == 0 and int(row[3]) == int(row[4]): # outflow_hru_list.append([int(row[6]), int(row[5])]) # if outflow_hru_list: # with open(fill_outflow_hru_path, 'w+') as f: # f.write('{} NUMOUTFLOWHRU\n'.format( # len(outflow_hru_list))) # for i, outflow_hru in enumerate(outflow_hru_list): # f.write('{} {} {} OUTFLOW_ID ROW COL\n'.format( # i+1, outflow_hru[0], outflow_hru[1])) # f.close() # del outflow_hru_list # Generate HRU_CASC.DAT for CRT from hru_polygon logging.info(' {}'.format(os.path.basename(fill_hru_casc_path))) hru_type_dict = defaultdict(dict) for row in sorted( arcpy.da.SearchCursor(hru.polygon_path, [ hru.row_field, hru.col_field, hru.type_field, hru.dem_adj_field ])): # Calculate CRT fill for all non-lake and non-ocean (elev > 0) cells # if row[3] > 0 and row[2] == 0: # hru_type_dict[int(row[0])][int(row[1])] = 1 # else: hru_type_dict[int(row[0])][int(row[1])] = row[2] # Calculate CRT fill for all active cells hru_type_dict[int(row[0])][int(row[1])] = row[2] hru_casc_header = ( '{} {} {} {} {} {} {} {} ' 'HRUFLG STRMFLG FLOWFLG VISFLG IPRN IFILL DPIT OUTITMAX\n').format( crt_hruflg, fill_strmflg, crt_flowflg, fill_visflg, crt_iprn, fill_ifill, crt_dpit, crt_outitmax) with open(fill_hru_casc_path, 'w+') as f: f.write(hru_casc_header) for row, col_data in sorted(hru_type_dict.items()): f.write(' '.join([str(t) for c, t in sorted(col_data.items())]) + '\n') f.close() del hru_casc_header, hru_type_dict # # Generate HRU_CASC.DATA for CRT from raster/ascii # with open(hru_type_ascii, 'r') as f: ascii_data = f.readlines() # f.close() # hru_casc_header = ( # '{} {} {} {} {} {} {} {} ' # 'HRUFLG STRMFLG FLOWFLG VISFLG ' # 'IPRN IFILL DPIT OUTITMAX\n').format( # crt_hruflg, fill_strmflg, crt_flowflg, fill_visflg, # crt_iprn, fill_ifill, crt_dpit, crt_outitmax) # with open(fill_hru_casc_path, 'w+') as f: # f.write(hru_casc_header) # for ascii_line in ascii_data[6:]: f.write(ascii_line) # f.close() # del hru_casc_header, ascii_data # Generate LAND_ELEV.DAT for CRT from hru_polygon logging.info(' {}'.format(os.path.basename(fill_land_elev_path))) dem_adj_dict = defaultdict(dict) for row in sorted( arcpy.da.SearchCursor( hru.polygon_path, [hru.row_field, hru.col_field, hru.dem_adj_field])): dem_adj_dict[int(row[0])][int(row[1])] = row[2] with open(fill_land_elev_path, 'w+') as f: row_first = dem_adj_dict.keys()[0] f.write('{} {} NROW NCOL\n'.format(len(dem_adj_dict.keys()), len(dem_adj_dict[row_first]))) for row, col_data in sorted(dem_adj_dict.items()): f.write(' '.join( ['{:10.6f}'.format(t) for c, t in sorted(col_data.items())]) + '\n') f.close() del dem_adj_dict # # Generate LAND_ELEV.DAT for CRT from raster/ascii # logging.info(' {}'.format(os.path.basename(fill_land_elev_path))) # with open(dem_adj_ascii, 'r') as f: ascii_data = f.readlines() # f.close() # with open(fill_land_elev_path, 'w+') as f: # f.write('{} {} NROW NCOL\n'.format( # ascii_data[1].split()[1], ascii_data[0].split()[1])) # for ascii_line in ascii_data[6:]: f.write(ascii_line) # f.close() # del ascii_data # Generate XY.DAT for CRT logging.info(' {}'.format(os.path.basename(fill_xy_path))) xy_list = [ map(int, row) for row in sorted( arcpy.da.SearchCursor(hru.polygon_path, [hru.id_field, hru.x_field, hru.y_field])) ] with open(fill_xy_path, 'w+') as f: for line in sorted(xy_list): f.write(' '.join(map(str, line)) + '\n') f.close() del xy_list # Run CRT logging.info('\nRunning CRT') subprocess.check_output(crt_exe_name, cwd=fill_ws, shell=True) # Read in outputstat.txt and get filled DEM logging.info('\nReading CRT {}'.format(output_name)) output_path = os.path.join(fill_ws, output_name) with open(output_path, 'r') as f: output_data = [l.strip() for l in f.readlines()] f.close() # Determine where filled data is in the file try: crt_dem_i = output_data.index( 'CRT FILLED LAND SURFACE MODEL USED TO GENERATE CASCADES') crt_fill_i = output_data.index( 'DIFFERENCES BETWEEN FILLED AND UNFILLED LAND SURFACE MODELS') except ValueError: logging.error('\nERROR: CRT didn\'t completely run\n' ' Check the CRT outputstat.txt file\n') sys.exit() logging.info(' Break indices: {}, {}'.format(crt_dem_i, crt_fill_i)) crt_dem_data = [ r.split() for r in output_data[crt_dem_i + 1:crt_dem_i + hru.rows + 1] ] crt_fill_data = [ r.split() for r in output_data[crt_fill_i + 1:crt_fill_i + hru.rows + 1] ] logging.info(' ROWS/COLS: {}/{}'.format(len(crt_dem_data), len(crt_dem_data[0]))) logging.info(' ROWS/COLS: {}/{}'.format(len(crt_fill_data), len(crt_fill_data[0]))) # crt_type_i = crt_fill_i + (crt_fill_i - crt_dem_i) # crt_dem_data = [ # r.split() for r in output_data[crt_dem_i+1: crt_dem_i+hru.rows+1]] # crt_fill_data = [ # r.split() for r in output_data[crt_fill_i+1: crt_type_i-1]] # Build dictionaries of the CRT data crt_dem_dict = defaultdict(dict) crt_fill_dict = defaultdict(dict) for i, r in enumerate(crt_dem_data): crt_dem_dict[i + 1] = dict([(j + 1, c) for j, c in enumerate(crt_dem_data[i])]) for i, r in enumerate(crt_fill_data): crt_fill_dict[i + 1] = dict([(j + 1, c) for j, c in enumerate(crt_fill_data[i])]) # Write CRT values to hru_polygon logging.info('Writing CRT data to fishnet') logging.debug(' {:<4s} {:<4s} {:>7s}'.format('ROW', 'COL', 'FILL')) fields = [ hru.row_field, hru.col_field, hru.crt_elev_field, hru.crt_fill_field, hru.dem_adj_field ] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: # If DEM values are too large for CRT, they may be symbols that will be skipped if support.is_number(crt_dem_dict[int(row[0])][int(row[1])]): row[2] = float(crt_dem_dict[int(row[0])][int(row[1])]) row[3] = float(crt_fill_dict[int(row[0])][int(row[1])]) if float(row[3]) > 0: logging.debug(' {:>4d} {:>4d} {:>7.2f}'.format( row[0], row[1], float(row[3]))) if use_crt_fill_flag and float(row[3]) > 0: row[4] = row[2] update_c.updateRow(row)
def stream_parameters(config_path): """Calculate GSFLOW Stream Parameters Parameters ---------- config_path : str Project configuration file (.ini) path. Returns ------- None """ # Initialize hru_parameters class hru = support.HRUParameters(config_path) # Open input parameter config file inputs_cfg = ConfigParser.ConfigParser() try: inputs_cfg.readfp(open(config_path)) except Exception as e: logging.error( '\nERROR: Config file could not be read, ' 'is not an input file, or does not exist\n' ' config_file = {}\n' ' Exception: {}\n'.format(config_path, e)) sys.exit() # Log DEBUG to file log_file_name = 'stream_parameters_log.txt' log_console = logging.FileHandler( filename=os.path.join(hru.log_ws, log_file_name), mode='w') log_console.setLevel(logging.DEBUG) log_console.setFormatter(logging.Formatter('%(message)s')) logging.getLogger('').addHandler(log_console) logging.info('\nGSFLOW Stream Parameters') # CRT Parameters try: crt_hruflg = inputs_cfg.getint('INPUTS', 'crt_hruflg') except ConfigParser.NoOptionError: crt_hruflg = 0 logging.info( ' Missing INI parameter, setting {} = {}'.format( 'crt_hruflg', crt_hruflg)) try: crt_flowflg = inputs_cfg.getint('INPUTS', 'crt_flowflg') except ConfigParser.NoOptionError: crt_flowflg = 1 logging.info( ' Missing INI parameter, setting {} = {}'.format( 'crt_flowflg', crt_flowflg)) try: crt_dpit = inputs_cfg.getfloat('INPUTS', 'crt_dpit') except ConfigParser.NoOptionError: crt_dpit = 0.01 logging.info( ' Missing INI parameter, setting {} = {}'.format( 'crt_dpit', crt_dpit)) try: crt_outitmax = inputs_cfg.getint('INPUTS', 'crt_outitmax') except ConfigParser.NoOptionError: crt_outitmax = 100000 logging.info( ' Missing INI parameter, setting {} = {}'.format( 'crt_outitmax', crt_outitmax)) # Intentionally not allowing user to change this value crt_iprn = 1 # CRT streams/cascade parameters crt_ws = os.path.join(hru.param_ws, 'cascade_work') crt_strmflg = 1 crt_visflg = 1 crt_ifill = 1 # CRT groundwater cascades gw_ws = os.path.join(hru.param_ws, 'cascade_gw_work') gw_strmflg = 1 gw_visflg = 1 gw_ifill = 1 # CRT Executable crt_exe_path = inputs_cfg.get('INPUTS', 'crt_exe_path') output_name = 'outputstat.txt' # Override ascii and rasters flags to generate CRT inputs output_ascii_flag = True output_rasters_flag = True # Parameters exit_seg = 0 # Check input paths if not arcpy.Exists(hru.polygon_path): logging.error( '\nERROR: Fishnet ({}) does not exist\n'.format( hru.polygon_path)) sys.exit() # Streams shapefile from dem_2_streams is needed to get the length flow_temp_ws = os.path.join(hru.param_ws, 'flow_rasters') if not os.path.isdir(flow_temp_ws): logging.error( '\nERROR: Flow_rasters folder does not exist' '\nERROR: {}' '\nERROR: Try re-running dem_2_streams.py\n'.format( flow_temp_ws)) sys.exit() streams_path = os.path.join(flow_temp_ws, 'streams.shp') if not os.path.isfile(streams_path): logging.error( '\nERROR: Stream shapefiles does not exist' '\nERROR: {}' '\nERROR: Try re-running dem_2_streams.py\n'.format( streams_path)) sys.exit() # Check that input fields exist and have data # Fields generated by hru_parameters for f in [hru.type_field, hru.row_field, hru.col_field]: if not arcpy.ListFields(hru.polygon_path, f): logging.error( '\nERROR: Input field {} is not present in fishnet' '\nERROR: Try re-running hru_parameters.py\n'.format(f)) sys.exit() elif support.field_stat_func(hru.polygon_path, f, 'MAXIMUM') == 0: logging.error( '\nERROR: Input field {} contains only 0' '\nERROR: Try re-running hru_parameters.py\n'.format(f)) sys.exit() # Fields generated by dem_2_streams for f in [hru.irunbound_field, hru.iseg_field, hru.flow_dir_field, hru.outflow_field, hru.subbasin_field]: if not arcpy.ListFields(hru.polygon_path, f): logging.error( '\nERROR: Input field {} is not present in fishnet' '\nERROR: Try re-running dem_2_streams.py\n'.format(f)) sys.exit() elif support.field_stat_func(hru.polygon_path, f, 'MAXIMUM') == 0: logging.error( '\nERROR: Input field {} contains only 0' '\nERROR: Try re-running dem_2_streams.py\n'.format(f)) sys.exit() # Build output folder if necessary stream_temp_ws = os.path.join(hru.param_ws, 'stream_rasters') if not os.path.isdir(stream_temp_ws): os.mkdir(stream_temp_ws) if not os.path.isdir(crt_ws): os.mkdir(crt_ws) if not os.path.isdir(gw_ws): os.mkdir(gw_ws) # Copy CRT executable if necessary crt_exe_name = os.path.basename(crt_exe_path) if not os.path.isfile(os.path.join(crt_ws, crt_exe_name)): shutil.copy(crt_exe_path, crt_ws) if not os.path.isfile(os.path.join(gw_ws, crt_exe_name)): shutil.copy(crt_exe_path, gw_ws) if not os.path.isfile(os.path.join(crt_ws, crt_exe_name)): logging.error( '\nERROR: CRT executable ({}) does not exist\n'.format( os.path.join(crt_ws, crt_exe_name))) sys.exit() # Cascades files crt_hru_casc_path = os.path.join(crt_ws, 'HRU_CASC.DAT') crt_outflow_hru_path = os.path.join(crt_ws, 'OUTFLOW_HRU.DAT') crt_land_elev_path = os.path.join(crt_ws, 'LAND_ELEV.DAT') crt_stream_cells_path = os.path.join(crt_ws, 'STREAM_CELLS.DAT') crt_xy_path = os.path.join(crt_ws, 'XY.DAT') # Groundwater cascades files gw_hru_casc_path = os.path.join(gw_ws, 'HRU_CASC.DAT') gw_outflow_hru_path = os.path.join(gw_ws, 'OUTFLOW_HRU.DAT') gw_land_elev_path = os.path.join(gw_ws, 'LAND_ELEV.DAT') gw_stream_cells_path = os.path.join(gw_ws, 'STREAM_CELLS.DAT') gw_xy_path = os.path.join(gw_ws, 'XY.DAT') # Output names dem_adj_raster_name = 'dem_adj' hru_type_raster_name = 'hru_type' iseg_raster_name = 'iseg' irunbound_raster_name = 'irunbound' subbasin_raster_name = 'sub_basins' segbasin_raster_name = 'seg_basins' # Output raster paths dem_adj_raster = os.path.join(stream_temp_ws, dem_adj_raster_name + '.img') hru_type_raster = os.path.join(stream_temp_ws, hru_type_raster_name + '.img') iseg_raster = os.path.join(stream_temp_ws, iseg_raster_name + '.img') irunbound_raster = os.path.join(stream_temp_ws, irunbound_raster_name + '.img') subbasin_raster = os.path.join(stream_temp_ws, subbasin_raster_name + '.img') segbasin_raster = os.path.join(stream_temp_ws, segbasin_raster_name + '.img') # Output ascii paths a_fmt = '{}_ascii.txt' dem_adj_ascii = os.path.join(stream_temp_ws, a_fmt.format(dem_adj_raster_name)) hru_type_ascii = os.path.join(stream_temp_ws, a_fmt.format(hru_type_raster_name)) iseg_ascii = os.path.join(stream_temp_ws, a_fmt.format(iseg_raster_name)) irunbound_ascii = os.path.join(stream_temp_ws, a_fmt.format(irunbound_raster_name)) subbasin_ascii = os.path.join(stream_temp_ws, a_fmt.format(subbasin_raster_name)) segbasin_ascii = os.path.join(stream_temp_ws, a_fmt.format(segbasin_raster_name)) # Layers hru_polygon_lyr = 'hru_polygon_lyr' # Set ArcGIS environment variables arcpy.CheckOutExtension('Spatial') env.overwriteOutput = True # env.pyramid = 'PYRAMIDS -1' env.pyramid = 'PYRAMIDS 0' env.workspace = stream_temp_ws env.scratchWorkspace = hru.scratch_ws # Add fields if necessary logging.info('\nAdding fields if necessary') support.add_field_func(hru.polygon_path, hru.iseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.irunbound_field, 'LONG') support.add_field_func(hru.polygon_path, hru.flow_dir_field, 'LONG') support.add_field_func(hru.polygon_path, hru.krch_field, 'LONG') support.add_field_func(hru.polygon_path, hru.irch_field, 'LONG') support.add_field_func(hru.polygon_path, hru.jrch_field, 'LONG') support.add_field_func(hru.polygon_path, hru.iseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.reach_field, 'LONG') support.add_field_func(hru.polygon_path, hru.rchlen_field, 'LONG') support.add_field_func(hru.polygon_path, hru.maxreach_field, 'LONG') support.add_field_func(hru.polygon_path, hru.outseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.iupseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.subbasin_field, 'LONG') support.add_field_func(hru.polygon_path, hru.segbasin_field, 'LONG') support.add_field_func(hru.polygon_path, hru.strm_top_field, 'FLOAT') support.add_field_func(hru.polygon_path, hru.strm_slope_field, 'FLOAT') # Calculate KRCH, IRCH, JRCH for stream segments logging.info('\nKRCH, IRCH, & JRCH for streams') fields = [ hru.type_field, hru.iseg_field, hru.row_field, hru.col_field, hru.krch_field, hru.irch_field, hru.jrch_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if (int(row[0]) in [1, 3] and int(row[1]) > 0): row[4], row[5], row[6] = 1, int(row[2]), int(row[3]) else: row[4], row[5], row[6] = 0, 0, 0 update_c.updateRow(row) # Get stream length for each cell logging.info('Stream length') arcpy.MakeFeatureLayer_management(hru.polygon_path, hru_polygon_lyr) arcpy.SelectLayerByAttribute_management( hru_polygon_lyr, 'NEW_SELECTION', ' \"{}\" = 1 And "{}" <> 0'.format(hru.type_field, hru.iseg_field)) length_path = os.path.join('in_memory', 'length') arcpy.Intersect_analysis( [hru_polygon_lyr, streams_path], length_path, 'ALL', '', 'LINE') arcpy.Delete_management(hru_polygon_lyr) length_field = 'LENGTH' arcpy.AddField_management(length_path, length_field, 'LONG') arcpy.CalculateField_management( length_path, length_field, '!shape.length@meters!', 'PYTHON') length_dict = defaultdict(int) # DEADBEEF - This probably needs a maximum limit for row in arcpy.da.SearchCursor( length_path, [hru.id_field, length_field]): length_dict[int(row[0])] += int(row[1]) fields = [hru.type_field, hru.iseg_field, hru.rchlen_field, hru.id_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if (int(row[0]) == 1 and int(row[1]) != 0): row[2] = length_dict[int(row[3])] else: row[2] = 0 update_c.updateRow(row) del length_dict, length_field, fields, hru_polygon_lyr # Get list of segments and downstream cell for each stream/lake cell # Downstream is calculated from flow direction # Use IRUNBOUND instead of ISEG, since ISEG will be zeroed for lakes # DEADBEEF - I don't think ISEG will be zero for lakes anymore logging.info('Cell out-flow dictionary') cell_dict = dict() fields = [ hru.type_field, hru.krch_field, hru.lake_id_field, hru.iseg_field, hru.irunbound_field, hru.dem_adj_field, hru.flow_dir_field, hru.col_field, hru.row_field, hru.id_field] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # Skip inactive cells if int(row[0]) == 0: continue # Skip if not lake and not stream elif (int(row[1]) == 0 and int(row[2]) == 0): continue # ROW / COL cell = (int(row[7]), int(row[8])) # Read in parameters # HRU_ID, ISEG, support.next_row_col(FLOW_DIR, CELL), DEM_ADJ, X, X, X cell_dict[cell] = [ int(row[9]), int(row[4]), support.next_row_col(int(row[6]), cell), float(row[5]), 0, 0, 0] # Build list of unique segments iseg_list = sorted(list(set([v[1] for v in cell_dict.values()]))) # Calculate IREACH and OUTSEG logging.info('Calculate {} and {}'.format( hru.reach_field, hru.outseg_field)) outseg_dict = dict() for iseg in sorted(iseg_list): logging.debug(' Segment: {}'.format(iseg)) # Subset of cell_dict for current iseg iseg_dict = dict([(k, v) for k, v in cell_dict.items() if v[1] == iseg]) # List of all cells in current iseg iseg_cells = iseg_dict.keys() # List of out_cells for all cells in current iseg out_cells = [value[2] for value in iseg_dict.values()] # Every iseg will (should?) have one out_cell out_cell = list(set(out_cells) - set(iseg_cells)) # Process streams and lakes separately # Streams if iseg > 0: # If there is more than one out_cell # there is a problem with the stream network if len(out_cell) != 1: logging.error( '\nERROR: ISEG {} has more than one out put cell' '\n Out cells: {}' '\n Check for streams exiting then re-entering a lake' '\n Lake cell elevations may not be constant\n'.format( iseg, out_cell)) sys.exit() # If not output cell, assume edge of domain try: outseg = cell_dict[out_cell[0]][1] except KeyError: outseg = exit_seg # Track sub-basin outseg outseg_dict[iseg] = outseg # Calculate reach number for each cell reach_dict = dict() start_cell = list(set(iseg_cells) - set(out_cells))[0] for i in range(len(out_cells)): # logging.debug(' Reach: {} Cell: {}'.format(i+1, start_cell)) reach_dict[start_cell] = i + 1 start_cell = iseg_dict[start_cell][2] # For each cell in iseg, save outseg, reach, & maxreach for iseg_cell in iseg_cells: cell_dict[iseg_cell][4:] = [ outseg, reach_dict[iseg_cell], len(iseg_cells)] del reach_dict, start_cell, outseg # Lakes else: # For lake cells, there can be multiple outlets if all of them # are to inactive cells or out of the model # Otherwise, like streams, there should only be one outcell per iseg logging.debug(' Length: {}'.format(len(out_cells))) if len(out_cell) == 1: try: outseg = cell_dict[out_cell[0]][1] except KeyError: outseg = exit_seg elif (len(out_cell) != 1 and all(x[0] not in cell_dict.keys() for x in out_cell)): outseg = exit_seg logging.debug( ' All out cells are inactive, setting outseg ' 'to exit_seg {}'.format(exit_seg)) else: logging.error( '\nERROR: ISEG {} has more than one out put cell' '\n Out cells: {}' '\n Check for streams exiting then re-entering a lake' '\n Lake cell elevations may not be constant\n'.format( iseg, out_cell)) raw_input('ENTER') # Track sub-basin outseg outseg_dict[iseg] = outseg # For each lake segment cell, only save outseg # All lake cells are routed directly to the outseg for iseg_cell in iseg_cells: cell_dict[iseg_cell][4:] = [outseg, 0, 0] del outseg del iseg_dict, iseg_cells, iseg del out_cells, out_cell # Calculate stream elevation logging.info('Stream elevation (DEM_ADJ - 1 for now)') fields = [ hru.type_field, hru.iseg_field, hru.dem_adj_field, hru.strm_top_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if int(row[0]) == 1 and int(row[1]) != 0: row[3] = float(row[2]) - 1 else: row[3] = 0 update_c.updateRow(row) # Saving ireach and outseg logging.info('Save IREACH and OUTSEG') fields = [ hru.type_field, hru.iseg_field, hru.col_field, hru.row_field, hru.outseg_field, hru.reach_field, hru.maxreach_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: # if (int(row[0]) > 0 and int(row[1]) > 0): # DEADBEEF - I'm not sure why only iseg > 0 in above line # DEADBEEF - This should set outseg for streams and lakes if (int(row[0]) > 0 and int(row[1]) != 0): row[4:] = cell_dict[(int(row[2]), int(row[3]))][4:] else: row[4:] = [0, 0, 0] update_c.updateRow(row) # Calculate IUPSEG for all segments flowing out of lakes logging.info('IUPSEG for streams flowing out of lakes') upseg_dict = dict( [(v, k) for k, v in outseg_dict.iteritems() if k < 0]) fields = [hru.type_field, hru.iseg_field, hru.iupseg_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if (int(row[0]) == 1 and int(row[1]) != 0 and int(row[1]) in upseg_dict.keys()): row[2] = upseg_dict[int(row[1])] else: row[2] = 0 update_c.updateRow(row) # Build dictionary of which segments flow into each segment # Used to calculate seg-basins (sub watersheds) for major streams # Also save list of all segments that pour to exit logging.info('Segment in/out-flow dictionary') inseg_dict = defaultdict(list) pourseg_dict = dict() pourseg_list = [] for key, value in outseg_dict.iteritems(): if key == exit_seg: continue # inseg_dict[key].append(key) elif value == exit_seg: pourseg_list.append(key) inseg_dict[key].append(key) else: inseg_dict[value].append(key) # Update pourseg for each segment, working up from initial pourseg # Pourseg is the final exit segment for each upstream segment for pourseg in pourseg_list: testseg_list = inseg_dict[pourseg] while testseg_list: testseg = testseg_list.pop() pourseg_dict[testseg] = pourseg if pourseg == testseg: continue testseg_list.extend(inseg_dict[testseg]) del testseg_list # Calculate SEG_BASIN for all active cells # SEG_BASIN corresponds to the ISEG of the lowest segment logging.info('SEG_BASIN') fields = [hru.type_field, hru.irunbound_field, hru.segbasin_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if int(row[0]) > 0 and int(row[1]) != 0: row[2] = pourseg_dict[int(row[1])] else: row[2] = 0 update_c.updateRow(row) # # Set all swale cells back to hru_type 2 (lake) # logging.info('Swale HRU_TYPE') # with arcpy.da.UpdateCursor(hru.polygon_path, [hru.type_field]) as update_c: # for row in update_c: # if int(row[0]) == 3: # row[0] = 2 # update_c.updateRow(row) # Set all lake iseg to 0 logging.info('Lake ISEG') fields = [hru.type_field, hru.iseg_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as update_c: for row in update_c: if int(row[0]) != 2: continue iseg = int(row[1]) if iseg < 0: row[1] = 0 update_c.updateRow(row) # Set environment parameters env.extent = hru.extent env.cellsize = hru.cs env.outputCoordinateSystem = hru.sr # Build rasters if output_rasters_flag: logging.info('\nOutput model grid rasters') arcpy.PolygonToRaster_conversion( hru.polygon_path, hru.type_field, hru_type_raster, 'CELL_CENTER', '', hru.cs) arcpy.PolygonToRaster_conversion( hru.polygon_path, hru.dem_adj_field, dem_adj_raster, 'CELL_CENTER', '', hru.cs) arcpy.PolygonToRaster_conversion( hru.polygon_path, hru.iseg_field, iseg_raster, 'CELL_CENTER', '', hru.cs) arcpy.PolygonToRaster_conversion( hru.polygon_path, hru.irunbound_field, irunbound_raster, 'CELL_CENTER', '', hru.cs) arcpy.PolygonToRaster_conversion( hru.polygon_path, hru.segbasin_field, segbasin_raster, 'CELL_CENTER', '', hru.cs) arcpy.PolygonToRaster_conversion( hru.polygon_path, hru.subbasin_field, subbasin_raster, 'CELL_CENTER', '', hru.cs) # Build rasters if output_ascii_flag: logging.info('Output model grid ascii') arcpy.RasterToASCII_conversion(hru_type_raster, hru_type_ascii) arcpy.RasterToASCII_conversion(dem_adj_raster, dem_adj_ascii) arcpy.RasterToASCII_conversion(iseg_raster, iseg_ascii) arcpy.RasterToASCII_conversion(irunbound_raster, irunbound_ascii) arcpy.RasterToASCII_conversion(segbasin_raster, segbasin_ascii) arcpy.RasterToASCII_conversion(subbasin_raster, subbasin_ascii) sleep(5) logging.debug('\nRemoving existing CRT fill files') if os.path.isfile(crt_hru_casc_path): os.remove(crt_hru_casc_path) if os.path.isfile(crt_outflow_hru_path): os.remove(crt_outflow_hru_path) if os.path.isfile(crt_land_elev_path): os.remove(crt_land_elev_path) if os.path.isfile(crt_stream_cells_path): os.remove(crt_stream_cells_path) if os.path.isfile(crt_xy_path): os.remove(crt_xy_path) # Input parameters files for Cascade Routing Tool (CRT) logging.info('\nBuilding output CRT files') # Generate STREAM_CELLS.DAT file for CRT # Include non-lake SWALES in streams file logging.info(' {}'.format( os.path.basename(crt_stream_cells_path))) stream_cells_list = [] fields = [ hru.type_field, hru.iseg_field, hru.reach_field, hru.col_field, hru.row_field] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): if int(row[0]) in [1, 3] and int(row[1]) > 0: stream_cells_list.append( [int(row[4]), int(row[3]), int(row[1]), int(row[2]), 1]) if stream_cells_list: with open(crt_stream_cells_path, 'w+') as f: f.write('{} NREACH\n'.format(len(stream_cells_list))) for stream_cells_l in sorted(stream_cells_list): f.write(' '.join(map(str, stream_cells_l)) + '\n') f.close del stream_cells_list # Generate OUTFLOW_HRU.DAT for CRT # Outflow cells exit the model to inactive cells or out of the domain # Outflow field is set in dem_2_streams logging.info(' {}'.format( os.path.basename(crt_outflow_hru_path))) outflow_hru_list = [] fields = [ hru.type_field, hru.outflow_field, hru.subbasin_field, hru.row_field, hru.col_field] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): if int(row[0]) != 0 and int(row[1]) == 1: outflow_hru_list.append([int(row[3]), int(row[4])]) if outflow_hru_list: with open(crt_outflow_hru_path, 'w+') as f: f.write('{} NUMOUTFLOWHRU\n'.format( len(outflow_hru_list))) for i, outflow_hru in enumerate(outflow_hru_list): f.write('{} {} {} OUTFLOW_ID ROW COL\n'.format( i + 1, outflow_hru[0], outflow_hru[1])) f.close() else: logging.error('\nERROR: No OUTFLOWHRU points, exiting') sys.exit() del outflow_hru_list # Generate OUTFLOW_HRU.DAT for CRT # logging.info(' {}'.format( # os.path.basename(crt_outflow_hru_path))) # outflow_hru_list = [] # fields = [ # hru.type_field, hru.iseg_field, hru.outseg_field, hru.reach_field, # hru.maxreach_field, hru.col_field, hru.row_field] # for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # if int(row[0]) != 1 or int(row[1]) == 0: continue # if int(row[2]) == 0 and int(row[3]) == int(row[4]): # outflow_hru_list.append([int(row[6]), int(row[5])]) # if outflow_hru_list: # with open(crt_outflow_hru_path, 'w+') as f: # f.write('{} NUMOUTFLOWHRU\n'.format( # len(outflow_hru_list))) # for i, outflow_hru in enumerate(outflow_hru_list): # f.write('{} {} {} OUTFLOW_ID ROW COL\n'.format( # i+1, outflow_hru[0], outflow_hru[1])) # f.close() # del outflow_hru_list # Generate HRU_CASC.DAT for CRT logging.info(' {}'.format(os.path.basename(crt_hru_casc_path))) with open(hru_type_ascii, 'r') as f: ascii_data = f.readlines() f.close() hru_casc_header = ( '{} {} {} {} {} {} {} {} ' 'HRUFLG STRMFLG FLOWFLG VISFLG IPRN IFILL DPIT OUTITMAX\n').format( crt_hruflg, crt_strmflg, crt_flowflg, crt_visflg, crt_iprn, crt_ifill, crt_dpit, crt_outitmax) with open(crt_hru_casc_path, 'w+') as f: f.write(hru_casc_header) for ascii_line in ascii_data[6:]: f.write(ascii_line) f.close() del hru_casc_header, ascii_data # Generate LAND_ELEV.DAT for CRT logging.info(' {}'.format(os.path.basename(crt_land_elev_path))) with open(dem_adj_ascii, 'r') as f: ascii_data = f.readlines() f.close() with open(crt_land_elev_path, 'w+') as f: f.write('{} {} NROW NCOL\n'.format( ascii_data[1].split()[1], ascii_data[0].split()[1])) for ascii_line in ascii_data[6:]: f.write(ascii_line) f.close() del ascii_data # Generate XY.DAT for CRT logging.info(' {}'.format(os.path.basename(crt_xy_path))) xy_list = [ map(int, row) for row in sorted(arcpy.da.SearchCursor( hru.polygon_path, [hru.id_field, hru.x_field, hru.y_field]))] with open(crt_xy_path, 'w+') as f: for line in sorted(xy_list): f.write(' '.join(map(str, line)) + '\n') f.close() # Run CRT logging.info('\nRunning CRT') subprocess.check_output(crt_exe_name, cwd=crt_ws, shell=True) # Read in outputstat.txt to check for errors logging.info('\nReading CRT {}'.format(output_name)) output_path = os.path.join(crt_ws, output_name) with open(output_path, 'r') as f: output_data = [l.strip() for l in f.readlines()] f.close() # Check if there are errors if 'CRT FOUND UNDECLARED SWALE HRUS' in output_data: logging.error( '\nERROR: CRT found undeclared swale HRUs (sinks)\n' ' All sinks must be filled before generating cascades\n' ' Check the CRT outputstat.txt file\n') sys.exit() elif 'CRT EXECUTION COMPLETE' not in output_data: logging.error('\nERROR: CRT did not successfully complete\n') sys.exit() # Rerun CRT without lakes to build groundwater cascades # This is only needed if there are lakes in the model # For now the input files are being coped from the cascade_work folder # (except HRU_CASC.DAT) logging.debug('\nRemoving existing CRT fill files') if os.path.isfile(gw_hru_casc_path): os.remove(gw_hru_casc_path) if os.path.isfile(gw_outflow_hru_path): os.remove(gw_outflow_hru_path) if os.path.isfile(gw_land_elev_path): os.remove(gw_land_elev_path) if os.path.isfile(gw_stream_cells_path): os.remove(gw_stream_cells_path) if os.path.isfile(gw_xy_path): os.remove(gw_xy_path) logging.info('\nCopying cascade CRT files (except HRU_CASC.DAT)') shutil.copy(crt_outflow_hru_path, gw_outflow_hru_path) shutil.copy(crt_land_elev_path, gw_land_elev_path) shutil.copy(crt_stream_cells_path, gw_stream_cells_path) shutil.copy(crt_xy_path, gw_xy_path) # Input parameters files for Cascade Routing Tool (CRT) logging.info('\nBuilding groundwater cascade CRT files') # Generate HRU_CASC.DAT for CRT logging.info(' {}'.format(os.path.basename(gw_hru_casc_path))) with open(hru_type_ascii, 'r') as f: ascii_data = f.readlines() f.close() hru_casc_header = ( '{} {} {} {} {} {} {} {} ' 'HRUFLG STRMFLG FLOWFLG VISFLG IPRN IFILL DPIT OUTITMAX\n').format( crt_hruflg, crt_strmflg, crt_flowflg, crt_visflg, crt_iprn, crt_ifill, crt_dpit, crt_outitmax) with open(gw_hru_casc_path, 'w+') as f: f.write(hru_casc_header) for ascii_line in ascii_data[6:]: # Convert all lakes to active # Should swales (type 3) be converted also? f.write(ascii_line.replace('2', '1')) f.close() del hru_casc_header, ascii_data # Run CRT logging.info('\nRunning CRT for groundwater cascades') subprocess.check_output(crt_exe_name, cwd=gw_ws, shell=True) # Read in outputstat.txt to check for errors logging.info('\nReading CRT {}'.format(output_name)) output_path = os.path.join(gw_ws, output_name) with open(output_path, 'r') as f: output_data = [l.strip() for l in f.readlines()] f.close() # Check if there are errors if 'CRT FOUND UNDECLARED SWALE HRUS' in output_data: logging.error( '\nERROR: CRT found undeclared swale HRUs (sinks)\n' ' All sinks must be filled before generating cascades\n' ' Check the CRT outputstat.txt file\n') sys.exit() elif 'CRT EXECUTION COMPLETE' not in output_data: logging.error('\nERROR: CRT did not successfully complete\n') sys.exit()
def flow_parameters(config_path, overwrite_flag=False, debug_flag=False): """Calculate GSFLOW Flow Parameters Args: config_file (str): Project config file path ovewrite_flag (bool): if True, overwrite existing files debug_flag (bool): if True, enable debug level logging Returns: None """ # Initialize hru_parameters class hru = support.HRUParameters(config_path) # Open input parameter config file inputs_cfg = ConfigParser.ConfigParser() try: inputs_cfg.readfp(open(config_path)) except Exception as e: logging.error('\nERROR: Config file could not be read, ' 'is not an input file, or does not exist\n' ' config_file = {}\n' ' Exception: {}\n'.format(config_path, e)) sys.exit() # Log DEBUG to file log_file_name = 'dem_2_stream_log.txt' log_console = logging.FileHandler(filename=os.path.join( hru.log_ws, log_file_name), mode='w') log_console.setLevel(logging.DEBUG) log_console.setFormatter(logging.Formatter('%(message)s')) logging.getLogger('').addHandler(log_console) logging.info('\nGSFLOW DEM To Streams') # Check whether lake parameters should be calculated try: set_lake_flag = inputs_cfg.getboolean('INPUTS', 'set_lake_flag') except ConfigParser.NoOptionError: set_lake_flag = False logging.info(' Missing INI parameter, setting {} = {}'.format( 'set_lake_flag', set_lake_flag)) # Model points model_inputs_path = inputs_cfg.get('INPUTS', 'model_points_path') try: model_points_zone_field = inputs_cfg.get('INPUTS', 'model_points_zone_field') except: model_points_zone_field = 'FID' logging.info(' Missing INI parameter, setting {} = {}'.format( 'model_points_zone_field', model_points_zone_field)) try: model_points_type_field = inputs_cfg.get('INPUTS', 'model_points_type_field') except: model_points_type_field = 'TYPE' logging.info(' Missing INI parameter, setting {} = {}'.format( 'model_points_type_field', model_points_type_field)) # Flow parameters flow_acc_threshold = inputs_cfg.getint('INPUTS', 'flow_acc_threshold') flow_length_threshold = inputs_cfg.getint('INPUTS', 'flow_length_threshold') try: calc_flow_dir_points_flag = inputs_cfg.getboolean( 'INPUTS', 'calc_flow_dir_points_flag') except ConfigParser.NoOptionError: calc_flow_dir_points_flag = False logging.info(' Missing INI parameter, setting {} = {}'.format( 'calc_flow_dir_points_flag', calc_flow_dir_points_flag)) try: lake_seg_offset = inputs_cfg.getint('INPUTS', 'lake_seg_offset') except ConfigParser.NoOptionError: lake_seg_offset = 0 logging.info(' Missing INI parameter, setting {} = {}'.format( 'lake_seg_offset', lake_seg_offset)) if lake_seg_offset < 0: logging.error( '\nERROR: lake_seg_offset must be an integer greater than 0') sys.exit() # Check input paths dem_temp_ws = os.path.join(hru.param_ws, 'dem_rasters') dem_path = os.path.join(dem_temp_ws, 'dem.img') if not arcpy.Exists(dem_path): logging.error( '\nERROR: Projected/clipped DEM ({}) does not exist' '\nERROR: Try rerunning dem_parameters.py'.format(dem_path)) sys.exit() if not arcpy.Exists(hru.polygon_path): logging.error('\nERROR: Fishnet ({}) does not exist'.format( hru.polygon_path)) sys.exit() # Check model points if not os.path.isfile(model_inputs_path): logging.error('\nERROR: Model points shapefiles does not exist' '\nERROR: {}'.format(model_inputs_path)) sys.exit() # model_points_path must be a point shapefile elif arcpy.Describe(model_inputs_path).datasetType != 'FeatureClass': logging.error('\nERROR: model_points_path must be a point shapefile') sys.exit() # Build output folder if necessary flow_temp_ws = os.path.join(hru.param_ws, 'flow_rasters') if not os.path.isdir(flow_temp_ws): os.mkdir(flow_temp_ws) # Output paths hru_type_path = os.path.join(flow_temp_ws, 'hru_type.img') dem_adj_path = os.path.join(flow_temp_ws, 'dem_adj.img') lake_id_path = os.path.join(flow_temp_ws, 'lake_id.img') dem_sink_path = os.path.join(flow_temp_ws, 'dem_sink.img') dem_fill_path = os.path.join(flow_temp_ws, 'dem_fill.img') flow_dir_path = os.path.join(flow_temp_ws, 'flow_dir.img') flow_dir_points = os.path.join(flow_temp_ws, 'flow_dir_points.shp') flow_acc_full_path = os.path.join(flow_temp_ws, 'flow_acc_full.img') flow_acc_sub_path = os.path.join(flow_temp_ws, 'flow_acc_sub.img') flow_mask_path = os.path.join(flow_temp_ws, 'flow_mask.img') stream_link_path = os.path.join(flow_temp_ws, 'stream_link.img') stream_link_a_path = os.path.join(flow_temp_ws, 'stream_link_a.img') stream_link_b_path = os.path.join(flow_temp_ws, 'stream_link_b.img') stream_order_path = os.path.join(flow_temp_ws, 'stream_order.img') stream_length_path = os.path.join(flow_temp_ws, 'stream_length.img') watersheds_path = os.path.join(flow_temp_ws, 'watersheds.img') outlet_path = os.path.join(flow_temp_ws, 'outlet.img') swale_path = os.path.join(flow_temp_ws, 'swale.img') subbasin_path = os.path.join(flow_temp_ws, 'subbasin.img') basin_path = os.path.join(flow_temp_ws, 'basin.img') streams_path = os.path.join(flow_temp_ws, 'streams.shp') model_points_path = os.path.join(flow_temp_ws, 'model_points.shp') # Set ArcGIS environment variables arcpy.CheckOutExtension('Spatial') env.overwriteOutput = True # env.pyramid = 'PYRAMIDS -1' env.pyramid = 'PYRAMIDS 0' env.workspace = flow_temp_ws env.scratchWorkspace = hru.scratch_ws # Set environment parameters env.extent = hru.extent env.cellsize = hru.cs env.outputCoordinateSystem = hru.sr # Read in model points shapefile logging.info('\nChecking model points shapefile') model_points_desc = arcpy.Describe(model_inputs_path) model_points_sr = model_points_desc.spatialReference logging.debug(' Points: {}'.format(model_inputs_path)) logging.debug(' Points spat. ref.: {}'.format(model_points_sr.name)) logging.debug(' Points GCS: {}'.format(model_points_sr.GCS.name)) # If model points spat_ref doesn't match hru_param spat_ref # Project model points to hru_param spat ref # Otherwise, read model points directly if hru.sr.name != model_points_sr.name: logging.info(' Model points projection does not match fishnet.\n' ' Projecting model points.\n') # Set preferred transforms transform_str = support.transform_func(hru.sr, model_points_sr) logging.debug(' Transform: {}'.format(transform_str)) arcpy.Project_management(model_inputs_path, model_points_path, hru.sr, transform_str, model_points_sr) else: arcpy.Copy_management(model_inputs_path, model_points_path) model_points_lyr = 'model_points_lyr' arcpy.MakeFeatureLayer_management(model_points_path, model_points_lyr) # Check model_points_zone_field if model_points_zone_field.upper() in ['', 'FID', 'NONE']: model_points_fid_field = arcpy.Describe(model_points_path).OIDFieldName logging.warning(' NOTE: Using {}+1 to set {}'.format( model_points_fid_field, hru.subbasin_field)) model_points_zone_field = 'ZONE_VALUE' if not arcpy.ListFields(model_points_path, model_points_zone_field): arcpy.AddField_management(model_points_path, model_points_zone_field, 'LONG') arcpy.CalculateField_management( model_points_path, model_points_zone_field, '!{}! + 1'.format(model_points_fid_field), 'PYTHON') elif not arcpy.ListFields(model_points_path, model_points_zone_field): logging.error( '\nERROR: model_points_zone_field {} does not exist\n'.format( model_points_zone_field)) sys.exit() # Need to check that model_points_zone_field is an int type elif not [ f.type for f in arcpy.Describe(model_points_path).fields if (f.name == model_points_zone_field and f.type in ['SmallInteger', 'Integer']) ]: logging.error( '\nERROR: model_points_zone_field {} must be an integer type\n'. format(model_points_zone_field)) sys.exit() # Need to check that model_points_zone_field is all positive values if min([ row[0] for row in arcpy.da.SearchCursor(model_points_path, [model_points_zone_field]) ]) <= 0: logging.error( '\nERROR: model_points_zone_field values must be positive\n'. format(model_points_zone_field)) sys.exit() # Check that subbasin values increment from 1 to nsub logging.info(' Checking subbasin numbering') subbasin_id_list = sorted( list( set([ row[0] for row in arcpy.da.SearchCursor( model_points_path, [model_points_zone_field]) ]))) if subbasin_id_list != range(1, len(subbasin_id_list) + 1): logging.error('\nERROR: SUB_BASINs must be sequential starting from 1' '\nERROR: {}'.format(subbasin_id_list)) sys.exit() subbasin_input_count = len(subbasin_id_list) logging.debug(' {} subbasins'.format(subbasin_input_count)) # Check model point types logging.info(' Checking model point types') model_point_types = [ str(r[0]).upper() for r in arcpy.da.SearchCursor( model_points_path, [model_points_type_field]) ] if not set(model_point_types).issubset(set(['OUTLET', 'SUBBASIN', 'SWALE' ])): logging.error( '\nERROR: Unsupported model point type(s) found, exiting') logging.error('\n Model point types: {}\n'.format(model_point_types)) sys.exit() ## elif not set(model_point_types).issubset(set(['OUTLET', 'SWALE'])): ## logging.error( ## '\nERROR: At least one model point must be an OUTLET or SWALE, ' ## 'exiting\n') sys.exit() else: logging.debug(' {}'.format(', '.join(model_point_types))) # Check DEM field logging.info('\nAdding DEM fields if necessary') support.add_field_func(hru.polygon_path, hru.iseg_field, 'LONG') support.add_field_func(hru.polygon_path, hru.irunbound_field, 'LONG') support.add_field_func(hru.polygon_path, hru.flow_dir_field, 'LONG') support.add_field_func(hru.polygon_path, hru.dem_sink_field, 'DOUBLE') support.add_field_func(hru.polygon_path, hru.outflow_field, 'DOUBLE') if set_lake_flag: # Check lake cell elevations logging.info('\nChecking lake cell {}'.format(hru.dem_adj_field)) lake_elev_dict = defaultdict(list) fields = [ hru.type_field, hru.lake_id_field, hru.dem_adj_field, hru.id_field ] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): if int(row[0]) != 2: continue lake_elev_dict[int(row[1])].append(float(row[2])) del fields logging.info(' {:>7} {:>12} {:>12} {:>12} {:>12}'.format( 'Lake ID', 'Minimum', 'Mean', 'Maximum', 'Std. Dev.')) for lake_id, lake_elev_list in lake_elev_dict.items(): lake_elev_array = np.array(lake_elev_list) logging.info(' {:7} {:12f} {:12f} {:12f} {:12f}'.format( lake_id, np.min(lake_elev_array), np.mean(lake_elev_array), np.max(lake_elev_array), np.std(lake_elev_array))) if np.std(lake_elev_array) > 1: logging.warning( ' Please check the lake cell elevations\n' ' They may need to be manually adjusted'.format(lake_id)) raw_input(' Press ENTER to continue') del lake_elev_array # Build Lake raster logging.debug(' LAKE_ID') arcpy.PolygonToRaster_conversion(hru.polygon_path, hru.lake_id_field, lake_id_path, 'CELL_CENTER', '', hru.cs) lake_id_obj = arcpy.sa.Raster(lake_id_path) logging.info('\nExporting HRU polygon parameters to raster') logging.debug(' HRU_TYPE') arcpy.PolygonToRaster_conversion(hru.polygon_path, hru.type_field, hru_type_path, 'CELL_CENTER', '', hru.cs) hru_type_obj = arcpy.sa.Raster(hru_type_path) # Convert DEM_ADJ to raster logging.debug(' DEM_ADJ') arcpy.PolygonToRaster_conversion(hru.polygon_path, hru.dem_adj_field, dem_adj_path, 'CELL_CENTER', '', hru.cs) dem_adj_obj = arcpy.sa.Raster(dem_adj_path) # dem_adj_obj = arcpy.sa.Float(arcpy.sa.Raster(dem_adj_path)) hru_polygon_lyr = 'hru_polygon_lyr' arcpy.MakeFeatureLayer_management(hru.polygon_path, hru_polygon_lyr) arcpy.SelectLayerByAttribute_management(hru_polygon_lyr, 'CLEAR_SELECTION') arcpy.CalculateField_management(hru_polygon_lyr, hru.outflow_field, 0, 'PYTHON') if 'OUTLET' in model_point_types: arcpy.SelectLayerByAttribute_management(model_points_lyr, 'NEW_SELECTION', '"TYPE" = \'OUTLET\'') arcpy.SelectLayerByLocation_management(hru_polygon_lyr, 'INTERSECT', model_points_lyr) arcpy.CalculateField_management(hru_polygon_lyr, hru.outflow_field, 1, 'PYTHON') # The point of all of this code is to determine the flow direction # at the outlet points since it won't be computed. # It might be easier to compute fill and flow dir. on the full raster logging.info(' Computing OUTLET point flow direction') # Get HRU values at outlet points outlet_points = [(int(r[0]), int(r[1])) for r in arcpy.da.SearchCursor( hru_polygon_lyr, [hru.col_field, hru.row_field])] # Get elevations and type of neighboring cells # Multiplying the cellsize by 1.5 is needed to get all possible # neighbors but it can return extra cells that will need to be skipped # It might be easier to use the Select tool directly arcpy.SelectLayerByLocation_management(hru_polygon_lyr, 'WITHIN_A_DISTANCE', model_points_lyr, 1.5 * hru.cs) elev_dict = dict() hru_type_dict = dict() fields = [ hru.col_field, hru.row_field, hru.dem_adj_field, hru.type_field ] for row in arcpy.da.SearchCursor(hru_polygon_lyr, fields): elev_dict[(int(row[0]), int(row[1]))] = float(row[2]) hru_type_dict[(int(row[0]), int(row[1]))] = int(row[3]) # For each outlet cell, cycle through flow directions and find ?. # Outlet cells should exit to an inactive cell or out of the grid. outlet_flowdir = {} for outlet_pt in outlet_points: logging.debug(' Outlet Point: {}'.format(outlet_pt)) outlet_slopes = [] # Search non-diagonals first. for fd in [1, 4, 16, 64, 2, 8, 32, 128]: if support.next_row_col(fd, outlet_pt) not in elev_dict.keys(): # Don't compute other slopes if next cell is outside the grid outlet_slopes.append([-9999, fd]) break elif hru_type_dict[support.next_row_col(fd, outlet_pt)] != 0: # Only compute slope to inactive cells continue else: # Compute slope to next cell slope = (elev_dict[support.next_row_col(fd, outlet_pt)] - elev_dict[outlet_pt]) if fd in [2, 8, 32, 128]: # For diagonals, adjust slope # I think Arc approximates root(2) to 1.5 slope /= 1.5 outlet_slopes.append([slope, fd]) logging.debug(' {:>3d} {}'.format(fd, slope)) if not outlet_slopes: logging.error('\nERROR: The OUTLET model point is not at the ' 'edge of the study area or model grid.\n' ' Col: {0} Rol: {1}'.format(*outlet_pt)) sys.exit() # Assign the flow direction with the steepest (positive) slope outlet_slope, outlet_fd = min(outlet_slopes) outlet_flowdir[outlet_pt] = outlet_fd if outlet_slope > 0: logging.warning( '\n WARNING: The OUTLET model point flow direction may ' 'be invalid') logging.debug(' Flow Direction: {}'.format(outlet_fd)) logging.info(' Building OUTLET point raster') outlet_array = np.zeros((hru.rows, hru.cols)).astype(np.uint8) for outlet_pt in outlet_points: outlet_array[outlet_pt[1] - 1, outlet_pt[0] - 1] = outlet_flowdir[outlet_pt] support.array_to_raster( outlet_array, outlet_path, arcpy.Point(hru.extent.XMin, hru.extent.YMin, 0), hru.cs, outlet_array) outlet_obj = arcpy.sa.Raster(outlet_path) if 'SWALE' in model_point_types: logging.info(' Building SWALE point raster') arcpy.SelectLayerByAttribute_management(model_points_lyr, 'NEW_SELECTION', '"TYPE" = \'SWALE\'') # DEADBEEF - Should SWALE points be written to OUTFLOWHRU.TXT? arcpy.SelectLayerByLocation_management(hru_polygon_lyr, 'INTERSECT', model_points_lyr) arcpy.CalculateField_management(hru_polygon_lyr, hru.outflow_field, 1, 'PYTHON') arcpy.PointToRaster_conversion(model_points_lyr, model_points_type_field, swale_path, "", "", hru.cs) swale_obj = arcpy.sa.Raster(swale_path) arcpy.SelectLayerByAttribute_management(model_points_lyr, 'CLEAR_SELECTION') arcpy.Delete_management(hru_polygon_lyr) logging.info('\nCalculating flow direction') # This will force all active cells to flow to an outlet logging.debug(' Setting DEM_ADJ values to 20000 for inactivate cells') dem_mod_obj = arcpy.sa.Con(hru_type_obj > 0, dem_adj_obj, 20000.0) if 'OUTLET' in model_point_types: logging.debug(' Setting DEM_ADJ values to NoData for OUTLET cells') dem_mod_obj = arcpy.sa.Con(arcpy.sa.IsNull(outlet_obj), dem_mod_obj) if 'SWALE' in model_point_types: logging.debug(' Setting DEM_ADJ values to NoData for SWALE cells') dem_mod_obj = arcpy.sa.Con(arcpy.sa.IsNull(swale_obj), dem_mod_obj) logging.info(' Filling DEM_ADJ (8-way)') dem_fill_obj = arcpy.sa.Fill(dem_mod_obj) del dem_mod_obj if 'OUTLET' in model_point_types: logging.debug(' Resetting OUTLET cell values') dem_fill_obj = arcpy.sa.Con(arcpy.sa.IsNull(outlet_obj), dem_fill_obj, dem_adj_obj) logging.info(' Calculating sinks (8-way)') # Threshold of 0.001 is needed to avoid noise from 32/64 bit conversion dem_sink_obj = arcpy.sa.Con(hru_type_obj > 0, dem_fill_obj - dem_adj_obj) dem_sink_obj = arcpy.sa.Con(dem_sink_obj > 0.001, dem_sink_obj) logging.info(' Calculating flow direction') flow_dir_obj = arcpy.sa.FlowDirection(dem_fill_obj, False) logging.debug(' Setting flow direction to NoData for inactive cells') flow_dir_obj = arcpy.sa.SetNull(hru_type_obj == 0, flow_dir_obj) if 'OUTLET' in model_point_types: logging.debug(' Resetting OUTLET cell flow direction') flow_dir_obj = arcpy.sa.Con(~arcpy.sa.IsNull(outlet_obj), outlet_obj, flow_dir_obj) del outlet_obj if 'SWALE' in model_point_types: logging.debug(' Resetting SWALE cell flow direction') flow_dir_obj = arcpy.sa.Con(~arcpy.sa.IsNull(swale_obj), 1, flow_dir_obj) del swale_obj logging.debug(' Resetting DEM_ADJ values for inactive cell') dem_fill_obj = arcpy.sa.Con(hru_type_obj == 0, dem_adj_obj, dem_fill_obj) flow_dir_obj.save(flow_dir_path) dem_fill_obj.save(dem_fill_path) dem_sink_obj.save(dem_sink_path) del dem_sink_obj # Save flow direction as points if calc_flow_dir_points_flag: logging.info('\nFlow direction points') # ArcGIS fails for raster_to_x conversions on a network path # You have to go through an in_memory file first flow_dir_temp = os.path.join('in_memory', 'flow_dir') arcpy.RasterToPoint_conversion(flow_dir_path, flow_dir_temp) try: arcpy.CopyFeatures_management(flow_dir_temp, flow_dir_points) except: time.sleep(1) logging.warning('Copy feature failed') arcpy.Delete_management(flow_dir_temp) del flow_dir_temp # Reclassify flow directions to angles, assuming 1 is 0 remap_cb = ('def Reclass(value):\n' + ' if value == 1: return 0\n' + ' elif value == 2: return 45\n' + ' elif value == 4: return 90\n' + ' elif value == 8: return 135\n' + ' elif value == 16: return 180\n' + ' elif value == 32: return 225\n' + ' elif value == 64: return 270\n' + ' elif value == 128: return 315\n') arcpy.CalculateField_management(flow_dir_points, 'grid_code', 'Reclass(!{}!)'.format('grid_code'), 'PYTHON', remap_cb) # Write flow direction to hru_polygon logging.debug(' Extracting flow direction at points') vt_list = [[flow_dir_path, hru.flow_dir_field]] mem_point_path = os.path.join('in_memory', 'hru_point') arcpy.CopyFeatures_management(hru.point_path, mem_point_path) arcpy.sa.ExtractMultiValuesToPoints(mem_point_path, vt_list, 'NONE') logging.debug(' Reading flow direction values at point') data_dict = defaultdict(dict) fields = [hru.flow_dir_field, hru.fid_field] with arcpy.da.SearchCursor(mem_point_path, fields) as s_cursor: for row in s_cursor: # Set nodata cells to 0 if row[0] is not None and row[1] is not None: data_dict[int(row[1])][hru.flow_dir_field] = int(row[0]) del row logging.debug(' Writing flow direction values to polygon') fields = [hru.flow_dir_field, hru.fid_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as u_cursor: for row in u_cursor: row_dict = data_dict.get(int(row[-1]), None) for i, field in enumerate(fields[:-1]): if row_dict: row[i] = row_dict[field] else: row[i] = 0 u_cursor.updateRow(row) del row_dict, row # DEADBEEF - This whole section seems to only be needed if the outflows # are not specified by the user. # # Subbasins # # Select the HRU cells that intersect the subbasin point cells # logging.debug(' Reading input subbasin points') # hru_polygon_lyr = 'hru_polygon_lyr' # arcpy.MakeFeatureLayer_management(hru.polygon_path, hru_polygon_lyr) # arcpy.SelectLayerByLocation_management( # hru_polygon_lyr, 'intersect', model_points_path) # input_xy_dict = dict() # fields = [hru.col_field, hru.row_field, hru.x_field, hru.y_field] # for row in arcpy.da.SearchCursor(hru_polygon_lyr, fields): # input_xy_dict[(int(row[0]), int(row[1]))] = (int(row[2]), int(row[3])) # arcpy.Delete_management(hru_polygon_lyr) # del hru_polygon_lyr # # for k,v in input_xy_dict.items(): # # logging.debug(' {} {}'.format(k,v)) # logging.info('\nBuilding all subbasin points') # # First calculate downstream cell for all cells # logging.debug(' Calculating downstream cells') # out_cell_dict = dict() # hru_type_dict = dict() # cell_xy_dict = dict() # fields = [ # hru.type_field, hru.flow_dir_field, hru.id_field, # hru.col_field, hru.row_field, hru.x_field, hru.y_field] # for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # cell = (int(row[3]), int(row[4])) # out_cell_dict[cell] = support.next_row_col(int(row[1]), cell) # hru_type_dict[cell] = int(row[0]) # cell_xy_dict[cell] = (int(row[5]), int(row[6])) # # Identify all active/lake cells that exit the model # # or flow to an inactive cell # logging.debug(' Identifying active cells that exit the model') # out_cell_xy_list = [] # for cell, cell_xy in sorted(cell_xy_dict.items()): # # DEADBEEF - This is finding exit cells that aren't already gauges # # if cell in input_xy_dict.keys(): # # continue # # elif cell not in hru_type_dict.keys(): # if cell not in hru_type_dict.keys(): # continue # elif hru_type_dict[cell] not in [1, 2]: # continue # elif cell not in out_cell_dict.keys(): # continue # elif out_cell_dict[cell] not in hru_type_dict.keys(): # out_cell_xy_list.append(cell_xy) # elif (out_cell_dict[cell] in hru_type_dict.keys() and # hru_type_dict[out_cell_dict[cell]] not in [1, 2]): # out_cell_xy_list.append(cell_xy) # # Outflow cells exit the model to inactive cells or out of the domain # # These cells will be used to set the OUTFLOW_HRU.DAT for CRT # # in crt_fill_parameters and stream_parameters # logging.info(' Flag outflow cells') # fields = [hru.type_field, hru.x_field, hru.y_field, hru.outflow_field] # with arcpy.da.UpdateCursor(hru.polygon_path, fields) as u_cursor: # for row in u_cursor: # cell_xy = (row[1], row[2]) # # Inactive cells can't be outflow cells # if int(row[0]) == 0: # continue # elif out_cell_xy_list and cell_xy in out_cell_xy_list: # row[3] = 1 # else: # row[3] = 0 # u_cursor.updateRow(row) # del out_cell_dict, hru_type_dict, cell_xy_dict # DEADBEEF - This was added for sinks or ocean so that there would be # subbasin points along the edge? # fields = ['SHAPE@XY', model_points_zone_field] # with arcpy.da.InsertCursor(model_points_path, fields) as insert_c: # for out_cell_xy in sorted(out_cell_xy_list): # insert_c.insertRow([out_cell_xy, subbasin_input_count + 1]) # del fields # del out_cell_xy_list # Flow Accumulation logging.info('\nCalculating initial flow accumulation') flow_acc_full_obj = arcpy.sa.FlowAccumulation(flow_dir_obj) logging.info(' Only keeping flow_acc >= {}'.format(flow_acc_threshold)) flow_acc_full_obj = arcpy.sa.Con(flow_acc_full_obj >= flow_acc_threshold, flow_acc_full_obj) flow_acc_full_obj.save(flow_acc_full_path) # Flow accumulation and stream link with lakes logging.info('\nCalculating flow accumulation & stream link (w/ lakes)') flow_acc_obj = arcpy.sa.Con((hru_type_obj >= 1) & (hru_type_obj <= 3), flow_acc_full_obj) stream_link_obj = arcpy.sa.StreamLink(flow_acc_obj, flow_dir_obj) stream_link_obj.save(stream_link_a_path) del flow_acc_obj, stream_link_obj # Flow accumulation and stream link without lakes logging.info('Calculating flow accumulation & stream link (w/o lakes)') flow_acc_obj = arcpy.sa.Con((hru_type_obj == 1) | (hru_type_obj == 3), flow_acc_full_obj) # flow_acc_obj.save(flow_acc_sub_path) stream_link_obj = arcpy.sa.StreamLink(flow_acc_obj, flow_dir_obj) stream_link_obj.save(stream_link_b_path) del flow_acc_obj, stream_link_obj # Initial Stream Link # logging.info('\nCalculating initial stream link') # stream_link_obj = StreamLink(flow_acc_obj, flow_dir_obj) # stream_link_obj.save(stream_link_path) # Calculate stream link with and without lakes # Initial Stream Order (w/ lakes) logging.info('Calculating stream order (w/ lakes)') logging.debug(' Using SHREVE ordering so after 1st order are removed, ' + '2nd order will only be dangles') stream_order_obj = arcpy.sa.StreamOrder(stream_link_a_path, flow_dir_obj, 'SHREVE') stream_order_obj.save(stream_order_path) # Stream Length (cell count w/o lakes) logging.info('Calculating stream length (cell count w/o lakes)') stream_length_obj = arcpy.sa.Lookup(stream_link_b_path, 'Count') stream_length_obj.save(stream_length_path) # Filter 1st order segments logging.info( '\nFilter all 1st order streams with length < {}' + '\nKeep all higher order streams'.format(flow_length_threshold)) # Stream length is nodata for lakes, so put lakes back in # This removes short 1st order streams off of lakes flow_mask_obj = ((hru_type_obj == 3) | (hru_type_obj == 2) | (stream_order_obj >= 2) | ((stream_order_obj == 1) & (stream_length_obj >= flow_length_threshold))) flow_mask_obj.save(flow_mask_path) flow_acc_sub_obj = arcpy.sa.Con(flow_mask_obj, flow_acc_full_obj) flow_acc_sub_obj.save(flow_acc_sub_path) del flow_mask_obj, stream_order_obj, stream_length_obj # Final Stream Link logging.info('\nCalculating final stream link') stream_link_obj = arcpy.sa.StreamLink(flow_acc_sub_obj, flow_dir_obj) # Get count of streams for automatically setting lake_seg_offset if not lake_seg_offset: lake_seg_count = int( arcpy.GetCount_management(stream_link_obj).getOutput(0)) n = 10**math.floor(math.log10(lake_seg_count)) lake_seg_offset = int(math.ceil((lake_seg_count + 1) / n)) * int(n) logging.info(' lake_segment_offset was not set in the input file\n' + ' Using automatic lake segment offset: {}'.format( lake_seg_offset)) elif set_lake_flag: logging.info( ' Using manual lake segment offset: {}'.format(lake_seg_offset)) # Include lake cells into 'stream_link' before calculating watersheds # Watershed function doesn't work for negative values # Convert lakes to large positive numbers for Watershed # ISEG needs to be negative values though if set_lake_flag: logging.info( ' Including lakes as {0} + {1}\n' ' This will allow for a watershed/subbasin for the lakes\n' ' {2} will be save as negative of {0} though'.format( hru.lake_id_field, lake_seg_offset, hru.iseg_field)) stream_link_obj = arcpy.sa.Con((hru_type_obj == 2), (lake_id_obj + lake_seg_offset), stream_link_obj) stream_link_obj.save(stream_link_path) # Watersheds logging.info('Calculating watersheds') watersheds_obj = arcpy.sa.Watershed(flow_dir_obj, stream_link_obj) watersheds_obj.save(watersheds_path) del stream_link_obj, watersheds_obj # Subbasins logging.info('Calculating subbasins') subbasin_obj = arcpy.sa.Watershed(flow_dir_obj, model_points_path, model_points_zone_field) subbasin_obj.save(subbasin_path) del subbasin_obj # Basins logging.info('Calculating basins') basin_obj = arcpy.sa.Basin(flow_dir_obj) basin_obj.save(basin_path) del basin_obj # Clear subbasin value if HRU_TYPE is 0 logging.info('Clearing subbasin ID for inactive cells') subbasin_obj = arcpy.sa.SetNull(hru_type_obj, arcpy.sa.Raster(subbasin_path), 'VALUE=0') subbasin_obj.save(subbasin_path) del subbasin_obj del hru_type_obj # Stream polylines logging.info('Calculating stream polylines') # ArcGIS fails for raster_to_x conversions on a network path # You have to go through an in_memory file first streams_temp = os.path.join('in_memory', 'streams') arcpy.sa.StreamToFeature(stream_link_path, flow_dir_obj, streams_temp, 'NO_SIMPLIFY') arcpy.CopyFeatures_management(streams_temp, streams_path) arcpy.Delete_management(streams_temp) del streams_temp # Write values to hru_polygon logging.info('\nExtracting stream parameters') vt_list = [ [watersheds_path, hru.irunbound_field], [stream_link_path, hru.iseg_field], # [flow_dir_path, hru.flow_dir_field], [subbasin_path, hru.subbasin_field], [hru_type_path, hru.type_field] ] mem_point_path = os.path.join('in_memory', 'hru_point') arcpy.CopyFeatures_management(hru.point_path, mem_point_path) arcpy.sa.ExtractMultiValuesToPoints(mem_point_path, vt_list, 'NONE') del vt_list # Read values from points logging.info(' Reading cell values') data_dict = defaultdict(dict) fields = [ hru.irunbound_field, hru.iseg_field, hru.subbasin_field, hru.type_field, hru.fid_field ] # fields = [ # hru.irunbound_field, hru.iseg_field, hru.flow_dir_field, # hru.subbasin_field, hru.type_field, hru.fid_field] with arcpy.da.SearchCursor(mem_point_path, fields) as s_cursor: for row in s_cursor: for i, field in enumerate(fields[:-1]): # Set nodata or inactive cells to 0 if row[i] is None or (int(row[-2]) == 0): data_dict[int(row[-1])][field] = 0 else: data_dict[int(row[-1])][field] = int(row[i]) del row del fields # ISEG for lake cells must be -1 * LAKE_ID, not LAKE_ID + OFFSET for k in data_dict.keys(): irunbound = data_dict[k][hru.irunbound_field] iseg = data_dict[k][hru.iseg_field] if irunbound > lake_seg_offset: data_dict[k][hru.irunbound_field] = lake_seg_offset - irunbound if iseg > lake_seg_offset: data_dict[k][hru.iseg_field] = lake_seg_offset - iseg # data_dict = dict([(k,v) for k,v in data_dict.items()]) # Write values to polygon logging.info(' Writing values to polygons') fields = [ hru.irunbound_field, hru.iseg_field, hru.subbasin_field, hru.type_field, hru.fid_field ] # fields = [ # hru.irunbound_field, hru.iseg_field, hru.flow_dir_field, # hru.subbasin_field, hru.type_field, hru.fid_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as u_cursor: for row in u_cursor: row_dict = data_dict.get(int(row[-1]), None) for i, field in enumerate(fields[:-1]): if row_dict: row[i] = row_dict[field] else: row[i] = 0 u_cursor.updateRow(row) del row_dict, row del fields # Write sink values to hru_polygon vt_list = [] if arcpy.Exists(dem_sink_path): vt_list.append([dem_sink_path, hru.dem_sink_field]) if vt_list: logging.info('\nExtracting sink values') for vt_item in vt_list: logging.debug(' {}: {}'.format(vt_item[1], os.path.basename(vt_item[0]))) mem_point_path = os.path.join('in_memory', 'hru_point') arcpy.CopyFeatures_management(hru.point_path, mem_point_path) arcpy.sa.ExtractMultiValuesToPoints(mem_point_path, vt_list, 'NONE') # Read sink values from points logging.info(' Reading sink values') data_dict = defaultdict(dict) fields = [field for path, field in vt_list] + [hru.fid_field] with arcpy.da.SearchCursor(mem_point_path, fields) as s_cursor: for row in s_cursor: for i, field in enumerate(fields[:-1]): # Set nodata or inactive cells to 0 if row[i] is None: data_dict[int(row[-1])][field] = 0 else: data_dict[int(row[-1])][field] = float(row[i]) del row # Write sink values to polygon logging.info(' Writing sink values to polygons') fields = [field for path, field in vt_list] + [hru.fid_field] with arcpy.da.UpdateCursor(hru.polygon_path, fields) as u_cursor: for row in u_cursor: row_dict = data_dict.get(int(row[-1]), None) for i, field in enumerate(fields[:-1]): if row_dict: row[i] = row_dict[field] else: row[i] = 0 u_cursor.updateRow(row) del row_dict, row # Cleanup arcpy.Delete_management(mem_point_path) del mem_point_path, vt_list, data_dict, field # Re-Calculate HRU_ELEV # logging.info('Calculating HRU_ELEV from DEM_ADJ') # logging.info(' Converting from meters to feet') # arcpy.CalculateField_management( # hru.polygon_path, hru_elev_field, # # Convert meters to feet # '!{}! * 3.28084'.format(dem_adj_field), 'PYTHON') # Cleanup del dem_fill_obj if set_lake_flag: del lake_id_obj del flow_dir_obj del flow_acc_full_obj del flow_acc_sub_obj
def prms_template_fill(config_path): """Fill PRMS Parameter Template File Parameters ---------- config_path : str Project configuration file (.ini) path. Returns ------- None """ param_formats = {1: '{:d}', 2: '{:f}', 3: '{:f}', 4: '{}'} # Initialize hru_parameters class hru = support.HRUParameters(config_path) # Open input parameter config file inputs_cfg = ConfigParser.ConfigParser() try: inputs_cfg.readfp(open(config_path)) except Exception as e: logging.error('\nERROR: Config file could not be read, ' 'is not an input file, or does not exist\n' ' config_file = {}\n' ' Exception: {}\n'.format(config_path, e)) sys.exit() # Log DEBUG to file log_file_name = 'prms_template_log.txt' log_console = logging.FileHandler(filename=os.path.join( hru.log_ws, log_file_name), mode='w') log_console.setLevel(logging.DEBUG) log_console.setFormatter(logging.Formatter('%(message)s')) logging.getLogger('').addHandler(log_console) logging.info('\nFilling PRMS Parameter File Template') # Read parameters from config file hru.polygon_path = inputs_cfg.get('INPUTS', 'hru_fishnet_path') hru.fid_field = inputs_cfg.get('INPUTS', 'orig_fid_field') parameter_ws = inputs_cfg.get('INPUTS', 'parameter_folder') try: prms_parameter_ws = inputs_cfg.get('INPUTS', 'prms_parameter_folder') except ConfigParser.NoOptionError: prms_parameter_ws = inputs_cfg.get('INPUTS', 'parameter_folder') logging.info(' Missing INI parameter, setting {} = {}'.format( 'prms_parameter_ws', prms_parameter_ws)) prms_dimen_csv_path = inputs_cfg.get('INPUTS', 'prms_dimen_csv_path') prms_param_csv_path = inputs_cfg.get('INPUTS', 'prms_param_csv_path') # Get input DEM units and desired output HRU_ELEV units dem_units = inputs_cfg.get('INPUTS', 'dem_units').lower() dem_unit_types = { 'meters': 'meter', 'm': 'meter', 'meter': 'meter', 'feet': 'feet', 'ft': 'meter', 'foot': 'meter', } try: dem_units = dem_unit_types[dem_units] except: logging.error( '\nERROR: DEM unit "{}" is not supported\n'.format(dem_units)) sys.exit() elev_units = inputs_cfg.getint('INPUTS', 'elev_units') elev_unit_types = {0: 'feet', 1: 'meter'} try: elev_units = elev_unit_types[elev_units] except: logging.error( '\nERROR: elev_units "{}" is not supported\n'.format(elev_units)) sys.exit() if dem_units == 'feet' and elev_units == 'meter': elev_unit_scalar = 0.3048 elif dem_units == 'meter' and elev_units == 'feet': elev_unit_scalar = (1.0 / 0.3048) else: elev_unit_scalar = 1.0 # Temperature calculation method try: temp_calc_method = inputs_cfg.get('INPUTS', 'temperature_calc_method').upper() except: temp_calc_method = '1STA' logging.info(' Defaulting temperature_calc_method = {}'.format( temp_calc_method)) temp_calc_options = ['ZONES', 'LAPSE', '1STA'] if temp_calc_method not in temp_calc_options: logging.error( '\nERROR: Invalid temperature calculation method ({})\n ' 'Valid methods are: {}'.format(temp_calc_method, ', '.join(temp_calc_options))) sys.exit() # Write parameter/dimensions to separate files based on "PARAM_FILE" # value in prms_parameters.csv and prms_dimensions.csv try: single_param_file_flag = inputs_cfg.getboolean( 'INPUTS', 'single_param_file_flag') except ConfigParser.NoOptionError: single_param_file_flag = False logging.info(' Missing INI parameter, setting {} = {}'.format( 'single_param_file_flag', single_param_file_flag)) if single_param_file_flag: try: single_param_file_name = inputs_cfg.get('INPUTS', 'single_param_file_name') except ConfigParser.NoOptionError: single_param_file_name = 'prms_inputs.param' logging.info(' Missing INI parameter, setting {} = {}'.format( 'single_param_file_name', single_param_file_name)) # Write nhru gridded parameters as single column or array try: param_column_flag = inputs_cfg.getboolean('INPUTS', 'param_column_flag') except ConfigParser.NoOptionError: param_column_flag = False logging.info(' Missing INI parameter, setting {} = {}'.format( 'param_column_flag', param_column_flag)) # Scratch workspace try: scratch_name = inputs_cfg.get('INPUTS', 'scratch_name') except ConfigParser.NoOptionError: scratch_name = 'in_memory' logging.info(' Missing INI parameter, setting {} = {}'.format( 'scratch_name', scratch_name)) # Cascades crt_ws = os.path.join(parameter_ws, 'cascade_work') gw_ws = os.path.join(parameter_ws, 'cascade_gw_work') crt_dimension_path = os.path.join(crt_ws, 'parameter_dimensions.txt') crt_parameter_path = os.path.join(crt_ws, 'cascade.param') crt_gw_dimension_path = os.path.join(gw_ws, 'parameter_dimensions.txt') crt_gw_parameter_path = os.path.join(gw_ws, 'groundwater_cascade.param') # Strings to search PRMS parameter file for # Newline character is required after title file_header_str = 'PRMS parameter file generated with gsflow-arcpy-tools version X\n' # file_header_str = 'Default file generated by model\nVersion: 1.7' dimen_header_str = '** Dimensions **' param_header_str = '** Parameters **' break_str = '####' # Check input paths if not arcpy.Exists(hru.polygon_path): logging.error('\nERROR: The fishnet does not exist\n {}'.format( hru.polygon_path)) sys.exit() # if not os.path.isfile(prms_template_path): # logging.error('\nERROR: The template parameter file does not exist\n') # sys.exit() if not os.path.isfile(prms_dimen_csv_path): logging.error( '\nERROR: The dimensions CSV file does not exist\n {}'.format( prms_dimen_csv_path)) sys.exit() if not os.path.isfile(prms_param_csv_path): logging.error( '\nERROR: The parameters CSV file does not exist\n {}'.format( prms_param_csv_path)) sys.exit() if not os.path.isdir(crt_ws): logging.error( '\nERROR: Cascades folder does not exist' '\nERROR: {}' '\nERROR: Try re-running CRT using stream_parameters.py\n'.format( crt_ws)) sys.exit() elif not os.path.isfile(crt_dimension_path): logging.error( '\nERROR: Cascades dimension file does not exist' '\nERROR: {}' '\nERROR: Try re-running CRT using stream_parameters.py\n'.format( crt_dimension_path)) sys.exit() elif not os.path.isfile(crt_parameter_path): logging.error( '\nERROR: Cascades parameter file does not exist' '\nERROR: {}' '\nERROR: Try re-running CRT using stream_parameters.py\n'.format( crt_parameter_path)) sys.exit() if not os.path.isdir(gw_ws): logging.error( '\nERROR: Groundwater cascades folder does not exist' '\nERROR: {}' '\nERROR: Try re-running CRT using stream_parameters.py\n'.format( gw_ws)) sys.exit() elif not os.path.isfile(crt_gw_dimension_path): logging.error( '\nERROR: Groundwater cascades dimension file does not exist' '\nERROR: {}' '\nERROR: Try re-running CRT using stream_parameters.py\n'.format( crt_gw_dimension_path)) sys.exit() elif not os.path.isfile(crt_gw_parameter_path): logging.error( '\nERROR: Groundwater cascades parameter file does not exist' '\nERROR: {}' '\nERROR: Try re-running CRT using stream_parameters\n'.format( crt_gw_parameter_path)) sys.exit() # Get number of cells in fishnet fishnet_count = int( arcpy.GetCount_management(hru.polygon_path).getOutput(0)) logging.info(' Fishnet cells: {}'.format(fishnet_count)) # Read in dimensions from CSV logging.info('\nReading dimensions CSV') dimen_names = dict() dimen_files = dict() dimen_sizes = dict() with open(prms_dimen_csv_path, 'r') as input_f: dimen_lines = input_f.readlines() input_f.close() # Dimensions can be set to a value, a field, or not set dimen_lines = [l.strip().split(',') for l in dimen_lines] header = dimen_lines[0] for line in dimen_lines[1:]: dimen_name = line[header.index('NAME')] dimen_names[dimen_name] = dimen_name logging.debug(' {}'.format(dimen_name)) # What should the default parameter file name be if not set? if single_param_file_flag: dimen_file = os.path.join(prms_parameter_ws, single_param_file_name) elif 'PARAM_FILE' not in header: dimen_file = os.path.join(prms_parameter_ws, 'prms_inputs.param') logging.info(' PARAM_FILE field not in dimensions CSV\n' ' Defaulting to {}'.format(dimen_file)) elif line[header.index('PARAM_FILE')] == '': dimen_file = os.path.join(prms_parameter_ws, 'prms_inputs.param') logging.info(' PARAM_FILE value not set for dimension: {}\n' ' Defaulting to {}'.format(dimen_name, dimen_file)) else: dimen_file = os.path.join( prms_parameter_ws, line[header.index('PARAM_FILE')] + '.param') dimen_files[dimen_name] = dimen_file dimen_size = line[header.index('SIZE')] if dimen_size.lower() in ['calculated', 'config_file']: dimen_sizes[dimen_name] = dimen_size elif not dimen_size: dimen_sizes[dimen_name] = '' else: # Don't force to integer type unless necessary since values are # written back out as strings dimen_sizes[dimen_name] = dimen_size # dimen_sizes[dimen_name] = int(dimen_size) del dimen_size # Set CALCULATED dimension values # These parameters equal the fishnet cell count for dimen_name in ['ngw', 'ngwcell', 'nhru', 'nhrucell', 'nssr']: if dimen_sizes[dimen_name].lower() == 'calculated': dimen_sizes[dimen_name] = fishnet_count logging.info(' {} = {}'.format(dimen_name, dimen_sizes[dimen_name])) # Getting number of lakes if dimen_sizes['nlake'].lower() == 'calculated': logging.info('\nCalculating number of lakes') #logging.info(' Lake cells are {} >= 0'.format(hru.lake_id_field)) value_fields = (hru.id_field, hru.lake_id_field) with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: dimen_sizes['nlake'] = max( list([int(row[1]) for row in s_cursor if int(row[1]) >= 0])) logging.info(' nlakes = {}'.format(dimen_sizes['nlake'])) # Getting number of lake cells if dimen_sizes['nlake_hrus'].lower() == 'calculated': logging.info('\nCalculating number of lake cells') logging.info(' Lake cells are {} >= 0'.format(hru.lake_id_field)) value_fields = (hru.id_field, hru.lake_id_field) with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: dimen_sizes['nlake_hrus'] = len( list([int(row[1]) for row in s_cursor if int(row[1]) >= 0])) logging.info(' nlake cells = {}'.format(dimen_sizes['nlake_hrus'])) # Getting number of stream cells if dimen_sizes['nreach'].lower() == 'calculated': logging.info('Calculating number of stream cells') logging.info(' Stream cells are {} >= 0'.format(hru.krch_field)) value_fields = (hru.id_field, hru.krch_field) with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: dimen_sizes['nreach'] = len( list([int(row[1]) for row in s_cursor if int(row[1]) > 0])) logging.info(' nreach = {}'.format(dimen_sizes['nreach'])) # Getting number of stream segments if dimen_sizes['nsegment'].lower() == 'calculated': logging.info('Calculating number of unique stream segments') logging.info(' Stream segments are {} >= 0'.format(hru.iseg_field)) value_fields = (hru.id_field, hru.iseg_field) with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: dimen_sizes['nsegment'] = len( list(set([int(row[1]) for row in s_cursor if int(row[1]) > 0]))) logging.info(' nsegment = {}'.format(dimen_sizes['nsegment'])) # Getting number of subbasins if dimen_sizes['nsub'].lower() == 'calculated': logging.info('Calculating number of unique subbasins') logging.info(' Subbasins are {} >= 0'.format(hru.subbasin_field)) value_fields = (hru.id_field, hru.subbasin_field) with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: dimen_sizes['nsub'] = len( list(set([int(row[1]) for row in s_cursor if int(row[1]) > 0]))) logging.info(' nsub = {}'.format(dimen_sizes['nsub'])) # Read in CRT cascade dimensions if dimen_sizes['ncascade'].lower() == 'calculated': logging.info('\nReading CRT dimensions') logging.debug(' {}'.format(crt_dimension_path)) with open(crt_dimension_path, 'r') as input_f: crt_dimen_lines = [line.strip() for line in input_f.readlines()] input_f.close() if not crt_dimen_lines: logging.error('\nERROR: The CRT dimensions file is empty\n') sys.exit() crt_dimen_break_i_list = [ i for i, x in enumerate(crt_dimen_lines) if x == break_str ] for i in crt_dimen_break_i_list: if crt_dimen_lines[i + 1] not in ['ncascade']: continue logging.info(' {} = {}'.format(crt_dimen_lines[i + 1], crt_dimen_lines[i + 2])) dimen_sizes[crt_dimen_lines[i + 1]] = int(crt_dimen_lines[i + 2]) del crt_dimen_lines, crt_dimen_break_i_list # Read in CRT groundwater cascade dimensions if dimen_sizes['ncascdgw'].lower() == 'calculated': logging.info('\nReading CRT groundwater cascade dimensions') logging.debug(' {}'.format(crt_gw_dimension_path)) with open(crt_gw_dimension_path, 'r') as input_f: crt_dimen_lines = [line.strip() for line in input_f.readlines()] input_f.close() if not crt_dimen_lines: logging.error( '\nERROR: The CRT groundwater dimensions file is empty\n') sys.exit() crt_dimen_break_i_list = [ i for i, x in enumerate(crt_dimen_lines) if x == break_str ] for i in crt_dimen_break_i_list: if crt_dimen_lines[i + 1] not in ['ncascdgw']: continue logging.info(' {} = {}'.format(crt_dimen_lines[i + 1], crt_dimen_lines[i + 2])) dimen_sizes[crt_dimen_lines[i + 1]] = int(crt_dimen_lines[i + 2]) del crt_dimen_lines, crt_dimen_break_i_list # Set CONFIG file dimension values config_file_dimensions = [ d_name for d_name, d_size in sorted(dimen_sizes.items()) if type(d_size) is str and d_size.lower() == 'config_file' ] if config_file_dimensions: logging.info('Reading configuration file dimensions') for dimen_name in config_file_dimensions: logging.info(' {}'.format(dimen_name)) try: dimen_sizes[dimen_name] = inputs_cfg.getint( 'INPUTS', dimen_name) except ConfigParser.NoOptionError: logging.error( ' Dimension set to "config_file" in {} but not found in ' 'config file, exiting'.format( os.path.basename(prms_dimen_csv_path))) # Link HRU fishnet field names to parameter names in '.param' param_names = dict() param_files = dict() param_dimen_counts = dict() param_dimen_names = dict() param_value_counts = dict() param_types = dict() param_defaults = dict() param_values = defaultdict(dict) # Read in parameters from CSV logging.info('\nReading parameters CSV') with open(prms_param_csv_path, 'r') as input_f: param_lines = input_f.readlines() input_f.close() param_lines = [l.strip().split(',') for l in param_lines] header = param_lines[0] for line in param_lines[1:]: # Get parameters from CSV line param_name = line[header.index('NAME')] logging.debug(' {}'.format(param_name)) # This assumes multiple dimensions are separated by semicolon dimen_names = line[header.index('DIMENSION_NAMES')].split(';') # What should the default parameter file name be if not set? if single_param_file_flag: param_file = os.path.join(prms_parameter_ws, single_param_file_name) elif 'PARAM_FILE' not in header: param_file = os.path.join(prms_parameter_ws, 'prms_inputs.param') logging.info(' PARAM_FILE field not in parameters CSV\n' ' Defaulting to {}'.format(param_file)) elif line[header.index('PARAM_FILE')] == '': param_file = os.path.join(prms_parameter_ws, 'prms_inputs.param') logging.info(' PARAM_FILE value not set for parameter: {}\n' ' Defaulting to {}'.format(param_name, param_file)) else: param_file = os.path.join( prms_parameter_ws, line[header.index('PARAM_FILE')] + '.param') # Check that parameter type is 1, 2, 3, or 4 param_type = int(line[header.index('TYPE')]) if param_type not in [1, 2, 3, 4]: logging.error('\nERROR: Parameter type {} is invalid' '\nERROR: {}'.format(param_type, line)) sys.exit() # This will initially read defaults in as a list param_default = line[header.index('DEFAULT_VALUE'):] # Removing empty strings avoids checking ints/floats param_default = [l for l in param_default if l] # For empty lists, set to none if not param_default: param_default = None # For single value lists, get first value # Check that param_default is a number or field name elif len(param_default) == 1: param_default = param_default[0] if isfloat(param_default) and param_type == 1: param_default = int(param_default) elif isfloat(param_default) and param_type in [2, 3]: param_default = float(param_default) elif param_default.lower() in [ 'calculated', 'config_file', 'crt_file' ]: pass elif arcpy.ListFields(hru.polygon_path, param_default): pass else: logging.error('\nERROR: Default value {} was not parsed' '\nERROR: {}'.format(param_default, line)) sys.exit() # For multi-value lists, convert values to int/float elif len(param_default) >= 2: if param_type == 1: param_default = map(int, param_default) elif param_type in [2, 3]: param_default = map(float, param_default) else: logging.error('\nERROR: Default value {} was not parsed' '\nERROR: {}'.format(param_default, line)) sys.exit() # Check that dimension names are valid for dimen_name in dimen_names: if dimen_name not in dimen_sizes.keys(): logging.error('\nERROR: The dimension {} is not set in the ' 'dimension CSV file'.format(dimen_name)) sys.exit() # Calculate number of dimensions dimen_count = str(len(dimen_names)) # Calculate number of values values_count = prod( [int(dimen_sizes[dn]) for dn in dimen_names if dimen_sizes[dn]]) # Write parameter to dictionaries param_names[param_name] = param_name param_files[param_name] = param_file param_dimen_counts[param_name] = dimen_count param_dimen_names[param_name] = dimen_names param_value_counts[param_name] = values_count param_types[param_name] = param_type param_defaults[param_name] = param_default # Apply default values to full dimension logging.info('\nSetting static parameters from defaults') for param_name, param_default in param_defaults.items(): param_value_count = param_value_counts[param_name] # Skip if not set if param_default is None: continue # Skip if still a string (field names) elif type(param_default) is str: continue # For float/int, apply default across dimension size elif type(param_default) is float or type(param_default) is int: for i in range(param_value_count): param_values[param_name][i] = param_default # For lists of floats, match up one-to-one for now elif len(param_default) == param_value_count: for i in range(param_value_count): param_values[param_name][i] = param_default[i] else: logging.error('\nERROR: The default value(s) ({0}) could not be ' 'broadcast to the dimension length ({1})'.format( param_default, param_value_count)) sys.exit() # Set CONFIG file parameter values config_file_parameters = [ p_name for p_name, p_value in sorted(param_defaults.items()) if type(p_value) is str and p_value.lower() == 'config_file' ] if config_file_parameters: logging.info('Reading configuration file parameters') for param_name in config_file_parameters: logging.info(' {}'.format(param_name)) try: values = inputs_cfg.get('INPUTS', param_name) except ConfigParser.NoOptionError: logging.error( ' Parameter set to "config_file" in {} but not found in ' 'config file, exiting'.format( os.path.basename(prms_dimen_csv_path))) # Convert comma separate strings to lists param_values[param_name] = { i: v for i, v in enumerate(values.split(',')) } # Convert the strings to the appropriate type if param_types[param_name] == 1: param_values[param_name] = { k: int(v) for k, v in param_values[param_name].items() } elif param_types[param_name] in [2, 3]: param_values[param_name] = { k: float(v) for k, v in param_values[param_name].items() } # Try and honor dimension value from CSV # Repeat values if actual value count doesn't match expected count # (from dimensions) # For now, only apply to INI parameters with a single value # and dimensions greater than 1 param_value_count = param_value_counts[param_name] if ((len(param_values[param_name]) != param_value_count) and (len(param_values[param_name]) == 1) and (param_value_count > 1)): value = param_values[param_name].copy() param_values[param_name] = {} for i in range(param_value_count): param_values[param_name][i] = value[0] # Read in HRU parameter data from fishnet polygon logging.info('\nReading in variable parameters from fishnet') param_fields = { k: v for k, v in param_defaults.items() if (type(v) is str and v.lower() not in ['calculated', 'config_file', 'crt_file']) } value_fields = param_fields.values() # Use HRU_ID to uniquely identify each cell if hru.id_field not in value_fields: value_fields.append(hru.id_field) hru_id_i = value_fields.index(hru.id_field) # Read in each cell parameter value with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: for row in s_cursor: for field_i, (param, field) in enumerate(param_fields.items()): if param_types[param] == 1: param_values[param][row[hru_id_i]] = int(row[field_i]) elif param_types[param] in [2, 3]: param_values[param][row[hru_id_i]] = float(row[field_i]) elif param_types[param] == 4: param_values[param][row[hru_id_i]] = row[field_i] # param_values[param][row[hru_id_i]] = row[field_i] # Calculate number of columns with arcpy.da.SearchCursor(hru.polygon_path, (hru.id_field, hru.col_field)) as s_cursor: ncol = len(list(set([int(row[1]) for row in s_cursor]))) # # DEADBEEF - Per Rich this is not needed anymore # # The following will override the parameter CSV values # # Calculate basin_area from active cells (land and lake) # logging.info('\nCalculating basin area') # param_names['basin_area'] = 'basin_area' # param_dimen_counts['basin_area'] = 1 # param_dimen_names['basin_area'] = ['one'] # param_value_counts['basin_area'] = dimen_sizes['one'] # param_types['basin_area'] = 2 # value_fields = (hru.id_field, hru.type_field, hru.area_field) # with arcpy.da.SearchCursor(hru.polygon_path, value_fields) as s_cursor: # param_values['basin_area'][0] = sum( # [float(row[2]) for row in s_cursor if int(row[1]) >= 1]) # logging.info(' basin_area = {} acres'.format( # param_values['basin_area'][0])) # Convert DEM_ADJ units (if necessary) if elev_unit_scalar != 1.0: logging.info('\nScaling DEM_ADJ units') logging.info(' DEM Units: {}'.format(dem_units)) logging.info(' Elev Units: {}'.format(elev_units)) logging.info(' Multiplier: {}'.format(elev_unit_scalar)) param_values['hru_elev'] = { k: v * elev_unit_scalar for k, v in param_values['hru_elev'].items() } # Calculate mean monthly maximum temperature for all active cells logging.info('\nCalculating tmax_index') logging.info(' Converting Celsius to Farenheit') param_names['tmax_index'] = 'tmax_index' param_dimen_counts['tmax_index'] = 1 param_dimen_names['tmax_index'] = ['nmonths'] param_value_counts['tmax_index'] = int(dimen_sizes['nmonths']) param_types['tmax_index'] = 2 tmax_field_list = ['TMAX_{:02d}'.format(m) for m in range(1, 13)] for i, tmax_field in enumerate(tmax_field_list): tmax_values = [ row[1] for row in arcpy.da.SearchCursor( hru.polygon_path, (hru.type_field, tmax_field), where_clause='"{}" >= 1'.format(hru.type_field)) ] tmax_c = sum(tmax_values) / len(tmax_values) tmax_f = 1.8 * tmax_c + 32 param_values['tmax_index'][i] = tmax_f logging.info(' {} = {}'.format(tmax_field, param_values['tmax_index'][i])) del tmax_values logging.info('\nCalculating tmax_adj/tmin_adj') param_names['tmax_adj'] = 'tmax_adj' param_names['tmin_adj'] = 'tmin_adj' param_types['tmax_adj'] = 2 param_types['tmin_adj'] = 2 if temp_calc_method in ['ZONES']: param_dimen_counts['tmax_adj'] = 2 param_dimen_counts['tmin_adj'] = 2 param_dimen_names['tmax_adj'] = ['nhru', 'nmonths'] param_dimen_names['tmin_adj'] = ['nhru', 'nmonths'] param_value_counts['tmax_adj'] = 12 * fishnet_count param_value_counts['tmin_adj'] = 12 * fishnet_count # Read the Tmax/Tmin adjust values from the shapefile # This could probably be simplified to a single search cursor pass tmax_adj_values = [] tmin_adj_values = [] tmax_adj_field_list = [ 'TMX_ADJ_{:02d}'.format(m) for m in range(1, 13) ] tmin_adj_field_list = [ 'TMN_ADJ_{:02d}'.format(m) for m in range(1, 13) ] for i, tmax_adj_field in enumerate(tmax_adj_field_list): tmax_adj_values.extend([ float(row[1]) for row in sorted( arcpy.da.SearchCursor(hru.polygon_path, (hru.id_field, tmax_adj_field))) ]) for i, tmin_adj_field in enumerate(tmin_adj_field_list): tmin_adj_values.extend([ float(row[1]) for row in sorted( arcpy.da.SearchCursor(hru.polygon_path, (hru.id_field, tmin_adj_field))) ]) for i, value in enumerate(tmax_adj_values): param_values['tmax_adj'][i] = value for i, value in enumerate(tmin_adj_values): param_values['tmin_adj'][i] = value del tmax_adj_values, tmin_adj_values # # This needs to be tested/compared with values from the above approach # # Process the tmax/tmin values in one pass of the search cursor # fields = [hru.id_field] + tmax_adj_field_list + tmin_adj_field_list # with arcpy.da.SearchCursor(hru.polygon_path, fields) as search_c: # for r_i, row in enumerate(sorted(search_c)): # for f_i in range(12): # param_values['tmax_adj'][r_i * f_i] = float(row[f_i + 1]) # param_values['tmin_adj'][r_i * f_i] = float(row[f_i + 13]) # # for f_i in range(len(tmax_adj_field_list): # Set/override hru_tsta using HRU_TSTA field param_names['hru_tsta'] = 'hru_tsta' param_dimen_counts['hru_tsta'] = 1 param_dimen_names['hru_tsta'] = ['nhru'] param_value_counts['hru_tsta'] = fishnet_count param_types['hru_tsta'] = 1 fields = (hru.id_field, 'HRU_TSTA') with arcpy.da.SearchCursor(hru.polygon_path, fields) as search_c: for row_i, row in enumerate(sorted(search_c)): param_values['hru_tsta'][row_i] = int(row[1]) # DEADBEEF - Do these parameters need to be set or overridden # ntemp, elev_units, basin_tsta, hru_tlaps, tsta_elev elif temp_calc_method in ['1STA', 'LAPSE']: # Set the tmax_adj/tmin_adj dimensions param_dimen_counts['tmax_adj'] = 1 param_dimen_counts['tmin_adj'] = 1 param_dimen_names['tmax_adj'] = ['nhru'] param_dimen_names['tmin_adj'] = ['nhru'] param_value_counts['tmax_adj'] = fishnet_count param_value_counts['tmin_adj'] = fishnet_count # Read the tmax_adj/tmin_adj parameter values from the shapefile fields = (hru.id_field, 'TMAX_ADJ', 'TMIN_ADJ') with arcpy.da.SearchCursor(hru.polygon_path, fields) as search_c: for row_i, row in enumerate(sorted(search_c)): param_values['tmax_adj'][row_i] = float(row[1]) param_values['tmin_adj'][row_i] = float(row[2]) logging.info('\nCalculating rain_adj/snow_adj') ratio_field_list = ['PPT_RT_{:02d}'.format(m) for m in range(1, 13)] param_names['rain_adj'] = 'rain_adj' param_dimen_counts['rain_adj'] = 2 param_dimen_names['rain_adj'] = ['nhru', 'nmonths'] param_value_counts['rain_adj'] = 12 * fishnet_count param_types['rain_adj'] = 2 param_names['snow_adj'] = 'snow_adj' param_dimen_counts['snow_adj'] = 2 param_dimen_names['snow_adj'] = ['nhru', 'nmonths'] param_value_counts['snow_adj'] = 12 * fishnet_count param_types['snow_adj'] = 2 ratio_values = [] for i, ratio_field in enumerate(ratio_field_list): ratio_values.extend([ float(row[1]) for row in sorted( arcpy.da.SearchCursor(hru.polygon_path, (hru.id_field, ratio_field))) ]) for i, value in enumerate(ratio_values): param_values['rain_adj'][i] = value param_values['snow_adj'][i] = value del ratio_values logging.info('\nCalculating subbasin_down') param_names['subbasin_down'] = 'subbasin_down' param_dimen_counts['subbasin_down'] = 1 param_dimen_names['subbasin_down'] = ['nsub'] param_value_counts['subbasin_down'] = dimen_sizes['nsub'] param_types['subbasin_down'] = 1 # Get list of subbasins and downstream cell for each stream/lake cell # Downstream is calculated from flow direction # logging.info('Cell out-flow dictionary') cell_dict = dict() fields = [ hru.type_field, hru.krch_field, hru.lake_id_field, hru.subbasin_field, hru.flow_dir_field, hru.col_field, hru.row_field, hru.id_field ] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # Skip inactive cells if int(row[0]) == 0: continue # Skip non-lake and non-stream cells elif (int(row[1]) == 0 and int(row[2]) == 0): continue # Read in parameters cell = (int(row[5]), int(row[6])) # support.next_row_col(FLOW_DIR, CELL) # HRU_ID, SUBBASIN, NEXT_CELL cell_dict[cell] = [ int(row[7]), int(row[3]), support.next_row_col(int(row[4]), cell) ] del cell # Get subset of cells if subbasin != next_subbasin subbasin_list = [] # CELL, (HRU_ID, SUBBASIN, NEXT_CELL) # for cell, row in cell_dict.items(): for cell, (hru_id, subbasin, next_cell) in cell_dict.items(): # Skip cells that are already subbasin 0 (inactive?) # If next cell isn't in list, assume next cell is out of the model # and set exit gauge subbasin to 0 # If the subbasin of the current cell doesn't match the subbasin # of the next cell, save the down subbasin if subbasin == 0: continue elif next_cell not in cell_dict.keys(): if [subbasin, 0] not in subbasin_list: subbasin_list.append([subbasin, 0]) elif subbasin != cell_dict[next_cell][1]: subbasin_list.append([subbasin, cell_dict[next_cell][1]]) for i, (subbasin, subbasin_down) in enumerate(sorted(subbasin_list)): param_values['subbasin_down'][i] = subbasin_down logging.debug(' {}'.format(param_values['subbasin_down'][i])) del subbasin_list # Switch SWALE points back to hru_type 1 or 2 logging.info('\nResetting SWALE point HRU_TYPE') fields = [hru.type_field, hru.id_field, hru.lake_id_field] for row in arcpy.da.SearchCursor(hru.polygon_path, fields): # Skip inactive cells if int(row[0]) != 3: continue elif int(row[2]) > 0: param_values['hru_type'][row[1]] = 2 else: param_values['hru_type'][row[1]] = 1 # # DEADBEEF - lake_hru is not used in PRMS 3.0.X or gsflow # # It is used in PRMS 4.0 though # # lake_hru parameter # logging.info('\nCalculating LAKE_HRU from HRU_ID for all lake HRU\'s') # param_names['lake_hru'] = 'lake_hru' # param_dimen_counts['lake_hru'] = 1 # param_dimen_names['lake_hru'] = ['nlake'] # param_value_counts['lake_hru'] = dimen_sizes['nlake'] # param_types['lake_hru'] = 1 # lake_hru_id_list = [ # row[1] for row in arcpy.da.SearchCursor( # hru.polygon_path, (hru.type_field, hru.id_field)) # if int(row[0]) == 2] # for i,lake_hru_id in enumerate(sorted(lake_hru_id_list)): # # logging.debug(' {} {}'.format(i, lake_hru_id)) # param_values['lake_hru'][i] = lake_hru_id # Read in CRT parameters logging.info('\nReading CRT parameters') with open(crt_parameter_path, 'r') as input_f: crt_param_lines = [line.strip() for line in input_f.readlines()] input_f.close() # Using enumerate iterator to get .next method crt_param_enumerate = enumerate(crt_param_lines) for crt_param_line in crt_param_enumerate: if crt_param_line[1] == break_str: # Skip break string crt_param_line = crt_param_enumerate.next() # Read parameter name and get next line param_name = crt_param_line[1] param_names[param_name] = param_name crt_param_line = crt_param_enumerate.next() # Read dimension count and get next line param_dimen_counts[param_name] = int(crt_param_line[1]) crt_param_line = crt_param_enumerate.next() # For each dimen (based on count) read in dimension name param_dimen_names[param_name] = [] for dimen_i in range(param_dimen_counts[param_name]): param_dimen_names[param_name].append(crt_param_line[1]) crt_param_line = crt_param_enumerate.next() # Read in number of parameter values param_value_counts[param_name] = int(crt_param_line[1]) crt_param_line = crt_param_enumerate.next() # Read in parameter type param_types[param_name] = int(crt_param_line[1]) # Read in parameter values # Get next in loop is place intentionally # Placing after getting the value causes it to skip next break for i in range(param_value_counts[param_name]): crt_param_line = crt_param_enumerate.next() if param_types[param_name] == 1: param_values[param_name][i] = int(crt_param_line[1]) if param_types[param_name] in [2, 3]: param_values[param_name][i] = float(crt_param_line[1]) if param_types[param_name] == 4: param_values[param_name][i] = crt_param_line[1] # Read in CRT groundwater parameters logging.info('Reading CRT groundwater parameters') with open(crt_gw_parameter_path, 'r') as input_f: crt_param_lines = [line.strip() for line in input_f.readlines()] input_f.close() # Using enumerate iterator to get .next method crt_param_enumerate = enumerate(crt_param_lines) for crt_param_line in crt_param_enumerate: if crt_param_line[1] == break_str: # Skip break string crt_param_line = crt_param_enumerate.next() # Read parameter name and get next line param_name = crt_param_line[1] param_names[param_name] = param_name crt_param_line = crt_param_enumerate.next() # Read dimension count and get next line param_dimen_counts[param_name] = int(crt_param_line[1]) crt_param_line = crt_param_enumerate.next() # For each dimen (based on count) read in dimension name param_dimen_names[param_name] = [] for dimen_i in range(param_dimen_counts[param_name]): param_dimen_names[param_name].append(crt_param_line[1]) crt_param_line = crt_param_enumerate.next() # Read in number of parameter values param_value_counts[param_name] = int(crt_param_line[1]) crt_param_line = crt_param_enumerate.next() # Read in parameter type param_types[param_name] = int(crt_param_line[1]) # Read in parameter values # Get next in loop is place intentionally # Placing after getting the value causes it to skip next break for i in range(param_value_counts[param_name]): crt_param_line = crt_param_enumerate.next() if param_types[param_name] == 1: param_values[param_name][i] = int(crt_param_line[1]) if param_types[param_name] in [2, 3]: param_values[param_name][i] = float(crt_param_line[1]) if param_types[param_name] == 4: param_values[param_name][i] = crt_param_line[1] del crt_param_enumerate, crt_param_lines # # Add lake HRU's to groundwater cascades # logging.info('Modifying CRT groundwater parameters for all lake HRU\'s') # logging.info(' gw_up_id = HRU_ID (lake)') # logging.info(' gw_down_id = 0') # # logging.info(' gw_strmseg_down_id = OUTSEG') # logging.info(' gw_strmseg_down_id = 2') # logging.info(' gw_pct_up = 1') # field_list = [hru.type_field, hru.id_field, hru.outseg_field, # hru.outflow_field] # lake_hru_id_dict = dict([ # (row[1], row[2]) # for row in arcpy.da.SearchCursor(hru.polygon_path, field_list) # if int(row[0]) == 2 and int(row[3]) == 0]) # for lake_hru_id, outseg in sorted(lake_hru_id_dict.items()): # # if lake_hru_id == 9128: # # print lake_hru_id, outseg # # raw_input('ENTER') # i = dimen_sizes['ncascdgw'] # dimen_sizes['ncascdgw'] += 1 # param_values['gw_up_id'][i] = lake_hru_id # param_values['gw_down_id'][i] = 0 # # DEADBEEF - PRMS didn't like when set to OUTSEG, but 2 worked? # # param_values['gw_strmseg_down_id'][i] = outseg # param_values['gw_strmseg_down_id'][i] = 2 # # DEADBEEF - Trying 0 # # param_values['gw_strmseg_down_id'][i] = 0 # param_values['gw_pct_up'][i] = 1.00 # # print param_values['gw_up_id'][i] # # print param_values['gw_down_id'][i] # # print param_values['gw_strmseg_down_id'][i] # # print param_values['gw_pct_up'][i] # param_value_counts['gw_up_id'] = int(dimen_sizes['ncascdgw']) # param_value_counts['gw_down_id'] = int(dimen_sizes['ncascdgw']) # param_value_counts['gw_strmseg_down_id'] = int(dimen_sizes['ncascdgw']) # param_value_counts['gw_pct_up'] = int(dimen_sizes['ncascdgw']) # logging.info(' ncascade = {}'.format(dimen_sizes['ncascade'])) # logging.info(' ncascdgw = {}'.format(dimen_sizes['ncascdgw'])) # # raw_input('ENTER') # DEADBEEF # Override -999 values # logging.info('\nChanging SOIL_MOIST_MAX nodata (-999) to 2') # for i,v in param_values['soil_moist_max'].items(): # if v == -999: param_values['soil_moist_max'][i] = 2 # logging.info('Changing SOIL_RECHR_MAX nodata (-999) to 1') # for i,v in param_values['soil_rechr_max'].items(): # if v == -999: param_values['soil_rechr_max'][i] = 1 # logging.info('Changing SAT_THRESHOLD nodata (-999) to 4') # for i,v in param_values['sat_threshold'].items(): # if v == -999: param_values['sat_threshold'][i] = 4 # Override negative values # logging.info('Changing negative SSR2GW_RATE (< 0) to 0.1 (PRMS default)') # for i,v in param_values['ssr2gw_rate'].items(): # if v < 0: param_values['ssr2gw_rate'][i] = 0.1 # raw_input('ENTER') # Write dimensions/parameters to PRMS param file logging.info('\nWriting parameter file(s)') prms_parameter_paths = sorted( list(set(param_files.values() + dimen_files.values()))) for prms_parameter_path in prms_parameter_paths: logging.info('{}'.format(prms_parameter_path)) if os.path.isfile(prms_parameter_path): logging.debug(' Removing existing file') os.remove(prms_parameter_path) # Get parameters and dimensions for each file param_name_list = sorted([ p_name for p_name, p_file in param_files.items() if p_file == prms_parameter_path ]) dimen_name_list = sorted([ d_name for d_name, d_file in dimen_files.items() if d_file == prms_parameter_path ]) with open(prms_parameter_path, 'w') as output_f: output_f.write(file_header_str + '\n') # Write dimensions if dimen_name_list: output_f.write(dimen_header_str + '\n') logging.debug(' Set dimensions') for dimen_name in dimen_name_list: try: dimen_size = dimen_sizes[dimen_name] except KeyError: continue if (type(dimen_size) is str and dimen_size.lower() in ['calculated']): logging.debug( ' Dimension {} not calculated'.format(dimen_size)) continue logging.debug(' {}'.format(dimen_name)) output_f.write(break_str + '\n') output_f.write(dimen_name + '\n') output_f.write(str(dimen_size) + '\n') # Then write set parameters if param_name_list: output_f.write(param_header_str + '\n') logging.debug(' Set parameters') for param_name in param_name_list: if param_name not in param_values.keys(): # logging.debug(param_name) continue logging.debug(' {}'.format(param_name)) output_f.write(break_str + '\n') output_f.write('{}\n'.format(param_name)) output_f.write('{}\n'.format(param_dimen_counts[param_name])) for dimen_name in param_dimen_names[param_name]: output_f.write(dimen_name + '\n') output_f.write(str(param_value_counts[param_name]) + '\n') param_type = param_types[param_name] output_f.write(str(param_type) + '\n') # Get list of values sorted by parameter name sorted_param_values = [ v for i, v in sorted(param_values[param_name].items()) ] # If dimension is "nhru", write values as an array. # Write blocks of values for each row if ('nhru' in param_dimen_names[param_name] and not param_column_flag): n = ncol else: n = 1 for i in range(0, len(sorted_param_values), n): values_str = ' '.join([ param_formats[param_type].format(v) for v in sorted_param_values[i:i + n] ]) output_f.write(values_str + '\n') # Close file output_f.close()