# This is a top-level module file that contains necessary I/O routines for # the MPE workflow. # Logan Karsten # National Center for Atmospheric Research # Research Applications Laboratory # karsten@ucar.edu # 303-497-2693 import os import gribMod2 import numpy as np from netCDF4 import Dataset import shutil import gzip import sys import errMod import datetime def checkStaticMeta(statusMeta): """ Generic function to do a routine check of static metadata for the MPE workflow that will be ran each time the program is initialized. This is sanity checking to ensure expected directories, files, etc, are present. """ if not os.path.isdir(statusMeta.outDir): statusMeta.errMsg = "ERROR: Expected directory: " + statusMeta.outDir + " not found." raise Exception() if not os.path.isdir(statusMeta.tmpDir): statusMeta.errMsg = "ERROR: Expected directory: " + statusMeta.tmpDir + " not found." raise Exception() if not os.path.isdir(statusMeta.mrmsDir): statusMeta.errMsg = "ERROR: Expected directory: " + statusMeta.mrmsDir + " not found." raise Exception() if not os.path.isdir(statusMeta.hrrrDir): statusMeta.errMsg = "ERROR: Expected directory: " + statusMeta.hrrrDir + " not found." raise Exception() if not os.path.isdir(statusMeta.rapDir): statusMeta.errMsg = "ERROR: Expected directory: " + statusMeta.rapDir + " not found." raise Exception() if not os.path.isfile(statusMeta.geoFile): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.geoFile + " not found." raise Exception() if not os.path.isfile(statusMeta.landMetaFile): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.landMetaFile + " not found." raise Exception() if not os.path.isfile(statusMeta.hrrrWghtPath): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.hrrrWghtPath + " not found." raise Exception() if not os.path.isfile(statusMeta.rapWghtPath): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.rapWghtPath + " not found." raise Exception() if not os.path.isfile(statusMeta.mrmsWghtPath): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.mrmsWghtPath + " not found." raise Exception() if not os.path.isfile(statusMeta.hrapNwmWghtPath): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.hrapNwmWghtPath + " not found." raise Exception() if statusMeta.numHoursBack < 1: statusMeta.errMsg = "ERROR: Number of hours to process must be greater than 0." raise Exception() # Make sure the number of hours back is divisible by the accuulated duration hours # specified by the user. if statusMeta.numHoursBack % statusMeta.accDuration != 0: statusMeta.errMsg = "ERROR: Number of hours going back must be divisible by accDuration specified." raise Exception() # Additional sanity checking. if statusMeta.accDuration < 0: statusMeta.errMsg = "ERROR: Must have a non-negative accumulation duration specified." raise Exception() if statusMeta.accDuration > 24: statusMeta.errMsg = "ERROR: Currently, only accumulation durations of 0, 6, 12, and 24 are supported." raise Exception() # Calculate the number of accumulation duration periods we need to loop over. statusMeta.nLoopSteps = int(statusMeta.numHoursBack/statusMeta.accDuration) # Make sure we have a valid RQI threshold for filtering out MRMS data. if statusMeta.rqiThresh < 0.0: statusMeta.errMsg = "ERROR: RQI threshold must be greater than 0.0" raise Exception() if statusMeta.rqiThresh > 1.0: statusMeta.errMsg = "ERROR: RQI threshold must be less than or equal to 1.0" raise Exception() # Ensure we have a valid RQI step in minutes that does not exceed the accumulation # duration period and is divisible by the number of minutes during the accumulation # period. We are also going to place a restriction here that the rqi step must be # a clean multiplier of 2 minutes as RQI files are not generated on odd numbers of # of the hour. if int(statusMeta.accDuration*60.0) % int(statusMeta.rqiStepMin) != 0: statusMeta.errMsg = "ERROR: RQI Steps in Minutes must be an equal divisor of " + \ " the accumulation period being used." raise Exception() if statusMeta.rqiStepMin <= 0: statusMeta.errMsg = "ERROR: RQI Steps in minutes must be greater than 0." raise Exception() if statusMeta.rqiStepMin > int(statusMeta.accDuration*60.0): statusMeta.errMsg = "ERROR: RQI Steps in Minutes must not be greater than " + \ " the accumulation period being used." raise Exception() if int(statusMeta.rqiStepMin) % 2 != 0: statusMeta.errMsg = "ERROR: RQI Steps in minutes must be a an even numbers." raise Exception() # Make sure the grid of RFC values exists. We will do additional sanity checking # on the file later. if not os.path.isfile(statusMeta.rfcMsk): statusMeta.errMsg = "ERROR: Expected file: " + statusMeta.rfcMsk + " not found." raise Exception() # Ensure the HRRR forecast hour specified is a valid option. Only 1,2,3 are allowed. if int(statusMeta.hrrrFcstHr) < 1 or int(statusMeta.hrrrFcstHr > 3): statusMeta.errMsg = "ERROR: Invalid HRRR forecast hour chosen. Only 1,2,3 available." raise Exception() # Ensure the minimum mm threshold for dividing is a reasonable number. if float(statusMeta.threshMM) < 0.0 or float(statusMeta.threshMM) > 1000.0: statusMeta.errMsg = "ERROR: Please specify a minimum precip threshold for dividing between 0 and 1000 mm." raise Exception() class geoConstants: def __init__(self): """ Initialize object to hold geospatial constants used for processing, regridding and output. """ # Establish a global NDV value to be used in processing and outputs. self.noDataValue = -9999.0 # Establish HRAP grid cell index values where the Channel Islands exist. self.CICol = [84, 85, 83, 84, 83, 82, 83, 82, 82, 81, 89, 87, 88, 89, 86, 87, 88, 87, 88, 63, 87, 61, 62, 85, 61, 84, 61, 62, 63, 64, 65, 66, 52, 53, 54, 55, 56, 59, 60, 61, 62, 63, 65, 66, 52, 53, 54, 55, 59, 60, 61, 62, 52, 53, 54, 59, 60, 51, 48, 49, 47, 48] self.CIRow = [276, 276, 277, 277, 278, 279, 279, 280, 281, 283, 289, 290, 290, 290, 291, 291, 291, 292, 292, 293, 293, 294, 294, 294, 295, 295, 315, 315, 315, 315, 315, 315, 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, 316, 317, 317, 317, 317, 317, 317, 317, 317, 318, 318, 318, 318, 318, 319, 320, 320, 321, 321] def getStage4Acc(gridMeta,statusMeta): """ Function to read in STAGE IV data that has been accumulated over the desired accumulation period. This is not to be confused with the hourly STAGE IV files that will be read in for modification by the OWP MPE process. """ # Compose expected file path based on the current datetime information # stored in statusMeta. if statusMeta.ncepWcoss == 1: st4Path = statusMeta.st4Dir + "/pcpanl." + statusMeta.dEndAcc.strftime('%Y%m%d') + \ "/st4_conus." + statusMeta.dEndAcc.strftime('%Y%m%d%H') + "." + \ str(int(statusMeta.accDuration)).zfill(2) + 'h.grb2' else: st4Path = statusMeta.st4Dir + "/" + statusMeta.dEndAcc.strftime('%Y') + \ "/" + statusMeta.dEndAcc.strftime('%Y%m%d') + \ "/ST4." + statusMeta.dEndAcc.strftime('%Y%m%d%H') + "." + \ str(int(statusMeta.accDuration)).zfill(2) + 'h.gz' if not os.path.isfile(st4Path): statusMeta.errMsg = "MISSING " + str(int(statusMeta.accDuration)).zfill(2) + \ "H ACC ST4 FOR " + statusMeta.dEndAcc.strftime('%Y%m%d%H') raise Exception() if statusMeta.ncepWcoss == 1: st4PathTmp2 = statusMeta.tmpDir + "/" + \ "st4_conus." + statusMeta.dEndAcc.strftime('%Y%m%d%H') + "." + \ str(int(statusMeta.accDuration)).zfill(2) + 'h.grb2' # Copy the raw input file over to the scratch directory try: shutil.copy(st4Path,st4PathTmp2) except: statusMeta.errMsg = "Unable to copy: " + st4Path + " to: " + st4PathTmp2 raise Exception() else: # We will need to copy the file over to the temporary directory where it will be # unzipped st4PathTmpGz = statusMeta.tmpDir + "/" + \ "ST4." + statusMeta.dEndAcc.strftime('%Y%m%d%H') + "." + \ str(int(statusMeta.accDuration)).zfill(2) + 'h.gz' st4PathTmp = statusMeta.tmpDir + "/" + \ "ST4." + statusMeta.dEndAcc.strftime('%Y%m%d%H') + "." + \ str(int(statusMeta.accDuration)).zfill(2) + 'h.grb' st4PathTmp2 = statusMeta.tmpDir + "/" + \ "ST4." + statusMeta.dEndAcc.strftime('%Y%m%d%H') + "." + \ str(int(statusMeta.accDuration)).zfill(2) + 'h.grb2' # If the copy exists, remove it to be safe. try: if os.path.isfile(st4PathTmpGz): os.remove(st4PathTmpGz) except: statusMeta.errMsg = "Unable to remove old file: " + st4PathTmpGz raise Exception() # If the unzipped file exists, remove it to be safe. try: if os.path.isfile(st4PathTmp): os.remove(st4PathTmp) except: statusMeta.errMsg = "Unable to remove temporary GRIB file: " + st4PathTmp raise Exception() # Copy the gzipped file over to the scratch directory try: shutil.copy(st4Path,st4PathTmpGz) except: statusMeta.errMsg = "Unable to copy: " + st4Path + " to: " + st4PathTmpGz raise Exception() # Unzip the file in place. try: with gzip.open(st4PathTmpGz,'rb') as fTmpGz: with open(st4PathTmp,'wb') as fTmp: shutil.copyfileobj(fTmpGz,fTmp) except: statusMeta.errMsg = "Unable to unzip: " + st4PathTmpGz raise Exception() # Remove the copy of the gzip file. try: if os.path.isfile(st4PathTmpGz): os.remove(st4PathTmpGz) except: statusMeta.errMsg = "Unable to remove old file: " + st4PathTmpGz raise Exception() # Convert the Stage IV GRIB1 file to GRIB2 try: gribMod2.gribOneToGribTwo(statusMeta,st4PathTmp,st4PathTmp2) except: raise Exception() # Remove the temporary GRIB1 file. try: os.remove(st4PathTmp) except: statusMeta.errMsg = "Unable to remove temporary file: " + st4PathTmp raise Exception() # Double check to make sure GRIB2 file exists. if not os.path.isfile(st4PathTmp2): statusMeta.errMsg = "Expected file: " + st4PathTmp2 + " not found." raise Exception() if not gridMeta: try: gridMeta = gribMod2.gribMeta() gridMeta.readMeta(statusMeta,st4PathTmp2,'APCP') except: raise Exception() # Initialize 2D NumPy array to hold data. st4AccGrid = np.empty([gridMeta.ny,gridMeta.nx],np.float64) st4AccGrid[:,:] = gridMeta.gribNdv # Read in grid from STAGE IV GRIB file. try: gribMod2.getGribVariable(statusMeta,st4PathTmp2,gridMeta,'APCP','APCP_surface', 'surface',0,'kg m**-2',st4AccGrid) except: raise Exception() statusMeta.haveAccData = True # Remove the temporary copy of the GRIB file. try: if os.path.isfile(st4PathTmp2): os.remove(st4PathTmp2) except: statusMeta.errMsg = "Unable to remove old file: " + st4PathTmp2 raise Exception() return st4AccGrid,gridMeta def getRfcMsk(gridMeta,statusMeta): """ Function that will read in the NetCDF file containing a grid on HRAP where each pixel cell contains an integer value corresponding to a unique RFC region. """ # Open the NetCDF file. Since we always check for the existence of parameter # files at the beginning of the workflow, we will forego checking here. try: mskFile = Dataset(statusMeta.rfcMsk,'r') except: statusMeta.errMsg = "Unable to open RFC mask file: " + statusMeta.rfcMsk raise Exception() # Do some sanity checking here to verify the metadata read in from the GRIB # file matches what we have in this file. if int(gridMeta.nx != mskFile.dimensions['x'].size): statusMeta.errMsg = "Stage IV GRIB data contains NX of: " + str(gridMeta.nx) + \ " Expected NX is: " + str(id.dimensions('x').size) raise Exception() if int(gridMeta.ny != mskFile.dimensions['y'].size): statusMeta.errMsg = "Stage IV GRIB data contains NY of: " + str(gridMeta.ny) + \ " Expected NY is: " + str(id.dimensions('y').size) raise Exception() if mskFile.dimensions['RFC'].size != 13: statusMeta.errMsg = "RFC Mask file contains " + str(mskFile.dimensions['RFC'].size) + \ " when expected size is 12." raise Exception() # Pull out x/y coordinates from RFC mask file. These values are the projected coordinates on the # HRAP grid. try: xCoords = mskFile.variables['x'][:] except: statusMeta.errMsg = "Unable to obtain X coordinates from: " + statusMeta.rfcMsk raise Exception() try: yCoords = mskFile.variables['y'][:] except: statusMeta.errMsg = "Unable to obtain Y coordinates from: " + statusMeta.rfcMsk raise Exception() # Pull out the RFC key values that correspond to names. try: rfcVals = mskFile.variables['RFC_grid_value'][:] except: statusMeta.errMsg = "Unable to obtain RFC_grid_value from: " + statusMeta.rfcMsk raise Exception() # Pull out RFC names. try: rfcNames = mskFile.variables['RFC_name'][:] except: statusMeta.errMsg = "Unable to obtain RFC_name from: " + statusMeta.rfcMsk raise Exception() # Pull out the RFC region mask grid. try: rfcMskGrid = mskFile.variables['Data'][:,:].data except: statusMeta.errMsg = "Unable to obtain the mask of RFC regions from: " + statusMeta.rfcMsk raise Exception() # Last, but not least, pull out the lat/lon grids from this file. These lat/lon values were caluclated by # Gregory Fall. His bravery in creating accurate center lat/lon points for the HRAP grid are noted here # and should be awarded with free tickets to the premier showing in Hollywood of the next Star Wars movie. try: gridMeta.gridCenterLats = mskFile.variables['lat'][:,:] except: statusMeta.errMsg = "Unable to extract grid center latitude values from: " + statusMeta.rfcMsk raise Exception() try: gridMeta.gridCenterLons = mskFile.variables['lon'][:,:] except: statusMeta.errMsg = "Unable to extract grid center longitude values from: " + statusMeta.rfcMsk raise Exception() statusMeta.haveRfcMsk = True return xCoords,yCoords,rfcVals,rfcNames,rfcMskGrid,gridMeta def outputNetCDF(statusMeta,inGrid,outFile): """ Generic function to output NetCDF file. """ if os.path.isfile(outFile): statusMeta.errMsg = "Output file: " + outFile + " already exists" raise Exception() try: idOut = Dataset(outFile,'w') except: statusMeta.errMsg = "Unable to create output file: " + outFile raise Exception() try: idOut.createDimension('y',inGrid.shape[0]) except: statusMeta.errMsg = "Unable to create X dimension in: " + outFile raise Exception() try: idOut.createDimension('x',inGrid.shape[1]) except: statusMeta.errMsg = "Unable to create Y dimension in: " + outFile raise Exception() if len(inGrid.shape) == 3: try: idOut.createDimension('time',inGrid.shape[2]) except: statusMeta.errMsg = "Unable to create time dimension in: " + outFile raise Exception() if len(inGrid.shape) == 2: try: idOut.createVariable('varOut','f8',('y','x')) except: statusMeta.errMsg = "Unable to create output variable in: " + outFile raise Exception() if len(inGrid.shape) == 3: try: idOut.createVariable('varOut','f8',('time','y','x')) except: statusMeta.errMsg = "Unable to create output variable in: " + outFile raise Exception() if len(inGrid.shape) == 2: idOut.variables['varOut'][:,:] = inGrid if len(inGrid.shape) == 3: for zTmp in range(0,inGrid.shape[2]): idOut.variables['varOut'][zTmp,:,:] = inGrid[:,:,zTmp] try: idOut.close() except: statusMeta.errMsg = "Unable to close file: " + outFile raise Exception() def outputMpeNetCDF(statusMeta,nwmGeoMeta,outGrid): """ Function to output MPE grids on the 1km NWM grid to CF-compliant NetCDF files. """ # Compose temporary and final output paths. outPathTmp = statusMeta.tmpDir + "/" + statusMeta.dCurr.strftime('%Y%m%d%H') + \ "00.PRECIP_FORCING.nc.tmp" outPath = statusMeta.outDir + "/" + statusMeta.dCurr.strftime('%Y%m%d%H') + \ "00.PRECIP_FORCING.nc" # Calculate time. dEpoch = datetime.datetime(1970,1,1,0) dt = statusMeta.dCurr - dEpoch numMinSinceEpoch = int(dt.days*1440) + int(dt.seconds/3600.0) # If the final output path exists, this means that a previous iteration of the MPE # workflow created the MPE data. We will override any previous data that was created. try: if os.path.isfile(outPathTmp): os.remove(outPathTmp) except: statusMeta.errMsg = "Failure to remove: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() if os.path.isfile(outPath): try: shutil.move(outPath,outPathTmp) except: statusMeta.errMsg = "Failure to move: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() if os.path.isfile(outPathTmp): # Simply open up the temporary file, and replace the existing grid with the new one. try: idTmp = Dataset(outPathTmp,'a') except: statusMeta.errMsg = "Failure to open: " + outPathTmp + " for editing" try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Calculate where non-zero valid values exist. We will need to convert to a precipitation # rate. indValid = np.where((outGrid > 0.0) & (outGrid != -9999)) if len(indValid[0]) == 0: statusMeta.errMsg = "No nonzero precipitation values found in output grid." raise Exception() outGrid[indValid] = outGrid[indValid]/3600.0 # Place data into the output variable. try: idTmp.variables['RAINRATE'][0,:,:] = np.flipud(outGrid) except: statusMeta.errMsg = "Failure to place MPE grid into variable RAINRATE for: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.close() except: statusMeta.errMsg = "Failure to close: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: shutil.move(outPathTmp,outPath) except: statusMeta.errMsg = "Failure to move: " + outPathTmp + " to: " + outPath try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() else: # Create a fresh MPE output file. try: idTmp = Dataset(outPathTmp,'w') except: statusMeta.errMsg = "Failure to open: " + outPathTmp + " for MPE creation." try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Create dimensions try: idTmp.createDimension('x',nwmGeoMeta.nx) except: statusMeta.errMsg = "Failre to create x dimension in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.createDimension('y',nwmGeoMeta.ny) except: statusMeta.errMsg = "Failure to create y dimension in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.createDimension('time',1) except: statusMeta.errMsg = "Failure to create time dimension in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Create variables try: idTmp.createVariable('x','f8',('x')) except: statusMeta.errMsg = "Failure to create x variable in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.createVariable('y','f8',('y')) except: statusMeta.errMsg = "Failure to create y variable in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.createVariable('time','f8',('time')) except: statusMeta.errMsg = "Failure to create time variable in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.createVariable('crs','c') except: statusMeta.errMsg = "Failure to create crs variable in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.createVariable('RAINRATE','f8',('time','y','x'),fill_value=-9999.0) except: statusMeta.errMsg = "Failure to create RAINRATE variable in: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Populate the crs variable try: idTmp.variables['crs']._CoordinateTransformType = nwmGeoMeta.transformType except: statusMeta.errMsg = "Failure to place Proj attribute: CoordinateTranformType into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].transform_name = nwmGeoMeta.transformName except: statusMeta.errMsg = "Failure to place Proj attribute: transform_name into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].grid_mapping_name = nwmGeoMeta.gridMapping except: statusMeta.errMsg = "Failure to place Proj attribute: grid_mapping_name into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs']._CoordinateAxes = nwmGeoMeta.coordAxis except: statusMeta.errMsg = "Failure to place Proj attribute: CoordinateAxes into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].esri_pe_string = nwmGeoMeta.esriString except: statusMeta.errMsg = "Failure to place Proj attribute: esri_pe_string into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].standard_parallel = nwmGeoMeta.stdParallel except: statusMeta.errMsg = "Failure to place Proj attribute: standard_parallel into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].longitude_of_central_meridian = nwmGeoMeta.lon0 except: statusMeta.errMsg = "Failure to place Proj attribute: longitude_of_central_meridian into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].latitude_of_projection_origin = nwmGeoMeta.lat0 except: statusMeta.errMsg = "Failure to place Proj attribute: latitude_of_projection_origin into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].false_easting = nwmGeoMeta.falseEast except: statusMeta.errMsg = "Failure to place Proj attribute: false_easting into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].false_northing = nwmGeoMeta.falseNorth except: statusMeta.errMsg = "Failure to place Proj attribute: false_northing into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['crs'].earth_radius = nwmGeoMeta.radius except: statusMeta.errMsg = "Failure to place Proj attribute: earth_radius into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Populate global attributes try: idTmp.proj4 = nwmGeoMeta.proj4 except: statusMeta.errMsg = "Failure to place Proj attribute: proj4 into file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.conventions = nwmGeoMeta.conventions except: statusMeta.errMsg = "Failure to place global attribute conventions into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Populate variable attributes try: idTmp.variables['x'].standard_name = nwmGeoMeta.xStdName idTmp.variables['y'].standard_name = nwmGeoMeta.yStdName idTmp.variables['RAINRATE'].standard_name = "precipitation_rate" idTmp.variables['time'].standard_name = "time" except: statusMeta.errMsg = "Failure to place standard name attributes into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['x'].long_name = nwmGeoMeta.xLongName idTmp.variables['y'].long_name = nwmGeoMeta.yLongName idTmp.variables['RAINRATE'].long_name = "Precipitation Rate" idTmp.variables['time'].long_name = "Valid date of MPE precipitation rate" except: statusMeta.errMsg = "Failure to place long name attributes into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['x'].units = nwmGeoMeta.xUnits idTmp.variables['y'].units = nwmGeoMeta.yUnits idTmp.variables['RAINRATE'].units = 'mm s-1' idTmp.variables['time'].units = 'Minutes since EPOCH' except: statusMeta.errMsg = "Failure to place units attributes into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['x'].resolution = nwmGeoMeta.xRes idTmp.variables['y'].resolution = nwmGeoMeta.yRes idTmp.variables['RAINRATE'].resolution = nwmGeoMeta.xRes except: statusMeta.errMsg = "Failure to place resolution attributes into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Calculate where non-zero valid values exist. We will need to convert to a precipitation # rate. indValid = np.where((outGrid > 0.0) & (outGrid != -9999)) if len(indValid[0]) == 0: statusMeta.errMsg = "No nonzero precipitation values found in output grid." raise Exception() outGrid[indValid] = outGrid[indValid]/3600.0 # Place data into the output variable. try: idTmp.variables['RAINRATE'][0,:,:] = np.flipud(outGrid) except: statusMeta.errMsg = "Failure to place MPE grid into variable RAINRATE for: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['x'][:] = nwmGeoMeta.xCoords except: statusMeta.errMsg = "Failure to place x array into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['y'][:] = nwmGeoMeta.yCoords except: statusMeta.errMsg = "Failure to place y array into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() try: idTmp.variables['time'][0] = numMinSinceEpoch except: statusMeta.errMsg = "Failure to place time value into: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Close the NetCDF file. try: idTmp.close() except: statusMeta.errMsg = "Failure to close NetCDF file: " + outPathTmp try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception() # Move the temporary file to the final resting place. try: shutil.move(outPathTmp,outPath) except: statusMeta.errMsg = "Failure to move: " + outPathTmp + " to: " + outPath try: errMod.logErr(statusMeta) except: errMod.errOutScreen raise Exception()