def get_parcels(self): ''' Function to generate various parcels and parcel traces. Returns nothing, but sets the following variables: self.mupcl : Most Unstable Parcel self.sfcpcl : Surface Based Parcel self.mlpcl : Mixed Layer Parcel self.fcstpcl : Forecast Surface Parcel self.ebottom : The bottom pressure level of the effective inflow layer self.etop : the top pressure level of the effective inflow layer self.ebotm : The bottom, meters (agl), of the effective inflow layer self.etopm : The top, meters (agl), of the effective inflow layer Parameters ---------- None Returns ------- None ''' self.mupcl = params.parcelx( self, flag=3 ) if self.mupcl.lplvals.pres == self.pres[self.sfc]: self.sfcpcl = self.mupcl else: self.sfcpcl = params.parcelx( self, flag=1 ) self.fcstpcl = params.parcelx( self, flag=2 ) self.mlpcl = params.parcelx( self, flag=4 ) self.usrpcl = params.Parcel() ## get the effective inflow layer data self.ebottom, self.etop = params.effective_inflow_layer( self, mupcl=self.mupcl ) ## if there was no effective inflow layer, set the values to masked if self.etop is ma.masked or self.ebottom is ma.masked: self.ebotm = ma.masked; self.etopm = ma.masked self.effpcl = self.sfcpcl # Default to surface parcel, as in params.DefineProfile(). ## otherwise, interpolate the heights given to above ground level else: self.ebotm = interp.to_agl(self, interp.hght(self, self.ebottom)) self.etopm = interp.to_agl(self, interp.hght(self, self.etop)) # The below code was adapted from params.DefineProfile() # Lifting one additional parcel probably won't slow the program too much. # It's just one more lift compared to all the lifts in the params.effective_inflow_layer() call. mtha = params.mean_theta(self, self.ebottom, self.etop) mmr = params.mean_mixratio(self, self.ebottom, self.etop) effpres = (self.ebottom+self.etop)/2. efftmpc = thermo.theta(1000., mtha, effpres) effdwpc = thermo.temp_at_mixrat(mmr, effpres) self.effpcl = params.parcelx(self, flag=5, pres=effpres, tmpc=efftmpc, dwpc=effdwpc) #This is the effective parcel.
def test_to_agl(): input_z = 1000. correct_agl = 643.0 returned_agl = interp.to_agl(prof, input_z) npt.assert_almost_equal(returned_agl, correct_agl) input_z = [1000., 3000., 6000.] correct_agl = [643., 2643., 5643.] returned_agl = interp.to_agl(prof, input_z) npt.assert_almost_equal(returned_agl, correct_agl)
def lift_parcels(prof): """Lift all the parcels within a given height interval and return the CAPEs, CINHs, and LFCs""" ## the height bottom, top, and interval zvals = np.arange(0, 5000, 100) pvals = interp.pres(prof, interp.to_msl(prof, zvals)) tvals = interp.temp(prof, pvals) dvals = interp.dwpt(prof, pvals) hvals = interp.hght(prof, pvals) hvals = interp.to_agl(prof, hvals) ## empty lists for storing the result cape_arr = [] cinh_arr = [] lfc_arr = [] ## lift each parcel in the vertical profile for p, t, td, h in zip(pvals, tvals, dvals, hvals): ## use SHARPpy to compute the parcel indices pcl = params.parcelx(prof, pres=p, tmpc=t, dwpc=td) ## store the parcel indices cape_arr.append(pcl.bplus) cinh_arr.append(pcl.bminus) lfc_arr.append(pcl.lfchght - h) ## return the data return np.ma.masked_invalid(cape_arr), np.ma.masked_invalid( cinh_arr), np.ma.masked_invalid(lfc_arr)
def get_fire(self): ''' Function to generate different indices and information regarding any fire weather in the sounding. This helps fill the data shown in the FIRE inset. Parameters ---------- None Returns ------- None ''' self.fosberg = fire.fosberg(self) self.ppbl_top = params.pbl_top(self) self.sfc_rh = thermo.relh(self.pres[self.sfc], self.tmpc[self.sfc], self.dwpc[self.sfc]) pres_sfc = self.pres[self.sfc] pres_1km = interp.pres(self, interp.to_msl(self, 1000.)) pbl_h = interp.to_agl(self, interp.hght(self, self.ppbl_top)) self.rh01km = params.mean_relh(self, pbot=pres_sfc, ptop=pres_1km) self.pblrh = params.mean_relh(self, pbot=pres_sfc, ptop=self.ppbl_top) self.meanwind01km = winds.mean_wind(self, pbot=pres_sfc, ptop=pres_1km) self.meanwindpbl = winds.mean_wind(self, pbot=pres_sfc, ptop=self.ppbl_top) self.pblmaxwind = winds.max_wind(self, lower=0, upper=pbl_h) #self.pblmaxwind = [np.ma.masked, np.ma.masked] mulplvals = params.DefineParcel(self, flag=3, pres=500) mupcl = params.cape(self, lplvals=mulplvals) self.bplus_fire = mupcl.bplus
def add_hodo(ax, prof, lw=1, color='black', ls='solid', size=5, AGLs=[1, 2, 6, 10]): # Return 2D hodograph line and list of text AGL labels. # Draw hodo line u_prof = prof.u[prof.pres >= 100] v_prof = prof.v[prof.pres >= 100] hodo, = ax.plot(u_prof, v_prof, 'k-', lw=lw, color=color, ls=ls) # position AGL text labels bbox_props = dict(boxstyle="square", fc="w", ec="0.5", linewidth=0.5, alpha=0.7) AGL_labels = [] AGL_labels.append(ax.text(-35, -55, 'km AGL', size=size, bbox=bbox_props)) for a in AGLs: in_meters = a * 1000 if in_meters <= np.max(interp.to_agl(prof, prof.hght)): junk = ax.text(0, 0, str(a), ha='center', va='center', size=size, bbox=bbox_props, color=color) ind = np.min(np.where(interp.to_agl(prof, prof.hght) > in_meters)) junk.set_position((prof.u[ind], prof.v[ind])) junk.set_clip_on(True) AGL_labels.append(junk) return hodo, AGL_labels
def draw_ensemble_point(self, qp, prof): # Plot the profile index on the scatter plot if 'pbl_h' not in dir( prof ): # Make sure a PBL top has been found in the profile object ppbl_top = params.pbl_top(prof) setattr(prof, 'pbl_h', interp.to_agl(prof, interp.hght(prof, ppbl_top))) if 'sfcpcl' not in dir( prof ): # Make sure a surface parcel has been lifted in the profile object setattr(prof, 'sfcpcl', params.parcelx(prof, flag=1)) #x = self.x_to_xpix() #y = self.y_to_ypix() color = QtCore.Qt.red qp.setPen(QtGui.QPen(color)) qp.setBrush(QtGui.QBrush(color)) x = self.x_to_xpix(prof.pbl_h) - 50 / 2. y = self.y_to_ypix(prof.sfcpcl.bplus) - (self.fsize - 1) / 2 qp.drawEllipse(x, y, 3, 3) return
def init_phase(prof): ''' Inital Precipitation Phase Adapted from SHARP code donated by Rich Thompson (SPC) This function determines the initial phase of any precipitation source in the profile. It does this either by finding a source of precipitation by searching for the highest 50 mb layer that has a relative humidity greater than 80 percent at the top and the bottom of the layer. This layer may be found either in the lowest 5 km of the profile, and if an OMEG profile is specified in the profile object, it will search for the layers with upward motion. The precipitation type is determined by using a.) the interpolated temperature in the middle of the precipitation source layer and b.) set temperature thresholds to determine the precipitation type. The type may be "Rain", "Freezing Rain", "ZR/S Mix", or "Snow". Parameters ---------- prof : Profile object (omega profile optional) Returns ------- plevel : the pressure level of the precipitation source (mb) phase : the phase type of the precipitation (int) phase == 0 for "Rain" phase == 1 for "Freezing Rain" or "ZR/S Mix" phase == 3 for "Snow" tmp : the temperature at the level that is the precipitation source st : a string naming the precipitation type ''' # Needs to be tested plevel = 0 phase = -1 # First, determine whether Upward VVELS are available. If they are, # use them to determine level where precipitation will develop. avail = np.ma.where(prof.omeg < .1)[0] hght_agl = interp.to_agl(prof, prof.hght) if len(avail) < 5: # No VVELS...must look for saturated level # Find the highest near-saturated 50mb layer below 5km agl below_5km_idx = np.ma.where((hght_agl < 5000.) &\ (hght_agl >= 0))[0] else: # Use the VV to find the source of precip. below_5km_idx = np.ma.where((hght_agl < 5000.) &\ (hght_agl >= 0) &\ (prof.omeg <= 0))[0] # Compute the RH at the top and bottom of 50 mb layers rh = thermo.relh(prof.pres, prof.tmpc, prof.dwpc)[below_5km_idx] sats = np.ma.where(rh > 80)[0] new_pres = prof.pres[below_5km_idx][sats] + 50. new_temp = interp.temp(prof, new_pres) new_dwpt = interp.dwpt(prof, new_pres) rh_plus50 = thermo.relh(new_pres, new_temp, new_dwpt) # Find layers where the RH is >80% at the top and bottom layers_idx = np.ma.where(rh_plus50 > 80)[0] if len(layers_idx) == 0: # Found no precipitation source layers st = "N/A" return prof.missing, phase, prof.missing, st # Find the highest layer up via the largest index top_most_layer = np.ma.max(layers_idx) plevel = new_pres[top_most_layer] - 25. # Determine the initial precip type based on the temp in the layer tmp = interp.temp(prof, plevel) if tmp > 0: phase = 0 st = "Rain" elif tmp <= 0 and tmp > -5: phase = 1 st = "Freezing Rain" elif tmp <=-5 and tmp > -9: phase = 1 st = "ZR/S Mix" elif tmp <= -9: phase = 3 st = "Snow" else: st = "N/A" return plevel, phase, tmp, st
def best_guess_precip(prof, init_phase, init_lvl, init_temp, tpos, tneg): ''' Best Guess Precipitation type Adapted from SHARP code donated by Rich Thompson (SPC) Description: This algorithm utilizes the output from the init_phase() and posneg_temperature() functions to make a best guess at the preciptation type one would observe at the surface given a thermodynamic profile. Precipitation Types Supported: - None - Rain - Snow - Sleet and Snow - Sleet - Freezing Rain/Drizzle - Unknown Parameters ---------- prof : Profile object init_phase : the initial phase of the precipitation (int) (see 2nd value returned from init_phase()) init_lvl : the inital level of the precipitation source (mb) (see 1st value returned from init_phase()) init_temp : the inital level of the precipitation source (C) (see 3rd value returned from init_phase()) tpos : the positive area (> 0 C) in the temperature profile (J/kg) Returns ------- precip_type : a string containing the best guess precipitation type ''' # Needs to be tested precip_type = None # Case: No precip if init_phase < 0: precip_type = "None." # Case: Always too warm - Rain elif init_phase == 0 and tneg >= 0 and prof.tmpc[prof.get_sfc()] > 0: precip_type = "Rain." # Case: always too cold elif init_phase == 3 and tpos <= 0 and prof.tmpc[prof.get_sfc()] <= 0: precip_type = "Snow." # Case: ZR too warm at sfc - Rain elif init_phase == 1 and tpos <= 0 and prof.tmpc[prof.get_sfc()] > 0: precip_type = "Rain." # Case: non-snow init...always too cold - Initphase & sleet elif init_phase == 1 and tpos <= 0 and prof.tmpc[prof.get_sfc()] <= 0: #print interp.to_agl(prof, interp.hght(prof, init_lvl)) if interp.to_agl(prof, interp.hght(prof, init_lvl)) >= 3000: if init_temp <= -4: precip_type = "Sleet and Snow." else: precip_type = "Sleet." else: precip_type = "Freezing Rain/Drizzle." # Case: Snow...but warm at sfc elif init_phase == 3 and tpos <= 0 and prof.tmpc[prof.get_sfc()] > 0: if prof.tmpc[prof.get_sfc()] > 4: precip_type = "Rain." else: precip_type = "Snow." # Case: Warm layer. elif tpos > 0: x1 = tpos y1 = -tneg y2 = (0.62 * x1) + 60.0 if y1 > y2: precip_type = "Sleet." else: if prof.tmpc[prof.get_sfc()] <= 0: precip_type = "Freezing Rain." else: precip_type = "Rain." else: precip_type = "Unknown." return precip_type
def init_phase(prof): ''' Inital Precipitation Phase Adapted from SHARP code donated by Rich Thompson (SPC) This function determines the initial phase of any precipitation source in the profile. It does this either by finding a source of precipitation by searching for the highest 50 mb layer that has a relative humidity greater than 80 percent at the top and the bottom of the layer. This layer may be found either in the lowest 5 km of the profile, and if an OMEG profile is specified in the profile object, it will search for the layers with upward motion. The precipitation type is determined by using a.) the interpolated temperature in the middle of the precipitation source layer and b.) set temperature thresholds to determine the precipitation type. The type may be "Rain", "Freezing Rain", "ZR/S Mix", or "Snow". Parameters ---------- prof : Profile object (omega profile optional) Returns ------- plevel : the pressure level of the precipitation source (mb) phase : the phase type of the precipitation (int) phase == 0 for "Rain" phase == 1 for "Freezing Rain" or "ZR/S Mix" phase == 3 for "Snow" tmp : the temperature at the level that is the precipitation source st : a string naming the precipitation type ''' # Needs to be tested plevel = 0 phase = -1 # First, determine whether Upward VVELS are available. If they are, # use them to determine level where precipitation will develop. avail = np.ma.where(prof.omeg < .1)[0] hght_agl = interp.to_agl(prof, prof.hght) if len(avail) < 5: # No VVELS...must look for saturated level # Find the highest near-saturated 50mb layer below 5km agl below_5km_idx = np.ma.where((hght_agl < 5000.) &\ (hght_agl >= 0))[0] else: # Use the VV to find the source of precip. below_5km_idx = np.ma.where((hght_agl < 5000.) &\ (hght_agl >= 0) &\ (prof.omeg <= 0))[0] # Compute the RH at the top and bottom of 50 mb layers rh = thermo.relh(prof.pres, prof.tmpc, prof.dwpc)[below_5km_idx] sats = np.ma.where(rh > 80)[0] new_pres = prof.pres[below_5km_idx][sats] + 50. new_temp = interp.temp(prof, new_pres) new_dwpt = interp.dwpt(prof, new_pres) rh_plus50 = thermo.relh(new_pres, new_temp, new_dwpt) # Find layers where the RH is >80% at the top and bottom layers_idx = np.ma.where(rh_plus50 > 80)[0] if len(layers_idx) == 0: # Found no precipitation source layers st = "N/A" return prof.missing, phase, prof.missing, st # Find the highest layer up via the largest index top_most_layer = np.ma.max(layers_idx) plevel = new_pres[top_most_layer] - 25. # Determine the initial precip type based on the temp in the layer tmp = interp.temp(prof, plevel) if tmp > 0: phase = 0 st = "Rain" elif tmp <= 0 and tmp > -5: phase = 1 st = "Freezing Rain" elif tmp <= -5 and tmp > -9: phase = 1 st = "ZR/S Mix" elif tmp <= -9: phase = 3 st = "Snow" else: st = "N/A" return plevel, phase, tmp, st
def indices(prof, debug=False): # return a formatted-string list of stability and kinematic indices sfcpcl = params.parcelx(prof, flag=1) mupcl = params.parcelx(prof, flag=3) # most unstable mlpcl = params.parcelx(prof, flag=4) # 100 mb mean layer parcel pcl = mupcl sfc = prof.pres[prof.sfc] p3km = interp.pres(prof, interp.to_msl(prof, 3000.)) p6km = interp.pres(prof, interp.to_msl(prof, 6000.)) p1km = interp.pres(prof, interp.to_msl(prof, 1000.)) mean_3km = winds.mean_wind(prof, pbot=sfc, ptop=p3km) sfc_6km_shear = winds.wind_shear(prof, pbot=sfc, ptop=p6km) sfc_3km_shear = winds.wind_shear(prof, pbot=sfc, ptop=p3km) sfc_1km_shear = winds.wind_shear(prof, pbot=sfc, ptop=p1km) #print "0-3 km Pressure-Weighted Mean Wind (kt):", utils.comp2vec(mean_3km[0], mean_3km[1])[1] #print "0-6 km Shear (kt):", utils.comp2vec(sfc_6km_shear[0], sfc_6km_shear[1])[1] srwind = params.bunkers_storm_motion(prof) srh3km = winds.helicity(prof, 0, 3000., stu=srwind[0], stv=srwind[1]) srh1km = winds.helicity(prof, 0, 1000., stu=srwind[0], stv=srwind[1]) #print "0-3 km Storm Relative Helicity [m2/s2]:",srh3km[0] #### Calculating variables based off of the effective inflow layer: # The effective inflow layer concept is used to obtain the layer of buoyant parcels that feed a storm's inflow. # Here are a few examples of how to compute variables that require the effective inflow layer in order to calculate them: stp_fixed = params.stp_fixed( sfcpcl.bplus, sfcpcl.lclhght, srh1km[0], utils.comp2vec(sfc_6km_shear[0], sfc_6km_shear[1])[1]) ship = params.ship(prof) # If you get an error about not converting masked constant to python int # use the round() function instead of int() - Ahijevych May 11 2016 # 2nd element of list is the # of decimal places indices = { 'SBCAPE': [sfcpcl.bplus, 0, 'J $\mathregular{kg^{-1}}$'], 'SBCIN': [sfcpcl.bminus, 0, 'J $\mathregular{kg^{-1}}$'], 'SBLCL': [sfcpcl.lclhght, 0, 'm AGL'], 'SBLFC': [sfcpcl.lfchght, 0, 'm AGL'], 'SBEL': [sfcpcl.elhght, 0, 'm AGL'], 'SBLI': [sfcpcl.li5, 0, 'C'], 'MLCAPE': [mlpcl.bplus, 0, 'J $\mathregular{kg^{-1}}$'], 'MLCIN': [mlpcl.bminus, 0, 'J $\mathregular{kg^{-1}}$'], 'MLLCL': [mlpcl.lclhght, 0, 'm AGL'], 'MLLFC': [mlpcl.lfchght, 0, 'm AGL'], 'MLEL': [mlpcl.elhght, 0, 'm AGL'], 'MLLI': [mlpcl.li5, 0, 'C'], 'MUCAPE': [mupcl.bplus, 0, 'J $\mathregular{kg^{-1}}$'], 'MUCIN': [mupcl.bminus, 0, 'J $\mathregular{kg^{-1}}$'], 'MULCL': [mupcl.lclhght, 0, 'm AGL'], 'MULFC': [mupcl.lfchght, 0, 'm AGL'], 'MUEL': [mupcl.elhght, 0, 'm AGL'], 'MULI': [mupcl.li5, 0, 'C'], '0-1 km SRH': [srh1km[0], 0, '$\mathregular{m^{2}s^{-2}}$'], '0-1 km Shear': [utils.comp2vec(sfc_1km_shear[0], sfc_1km_shear[1])[1], 0, 'kt'], '0-3 km SRH': [srh3km[0], 0, '$\mathregular{m^{2}s^{-2}}$'], '0-6 km Shear': [utils.comp2vec(sfc_6km_shear[0], sfc_6km_shear[1])[1], 0, 'kt'], 'PWV': [params.precip_water(prof), 2, 'inch'], 'K-index': [params.k_index(prof), 0, ''], 'STP(fix)': [stp_fixed, 1, ''], 'SHIP': [ship, 1, ''] } eff_inflow = params.effective_inflow_layer(prof) if any(eff_inflow): ebot_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[0])) etop_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[1])) #print "Effective Inflow Layer Bottom Height (m AGL):", ebot_hght #print "Effective Inflow Layer Top Height (m AGL):", etop_hght effective_srh = winds.helicity(prof, ebot_hght, etop_hght, stu=srwind[0], stv=srwind[1]) indices['Eff. SRH'] = [ effective_srh[0], 0, '$\mathregular{m^{2}s^{-2}}$' ] #print "Effective Inflow Layer SRH (m2/s2):", effective_srh[0] ebwd = winds.wind_shear(prof, pbot=eff_inflow[0], ptop=eff_inflow[1]) ebwspd = utils.mag(*ebwd) indices['EBWD'] = [ebwspd, 0, 'kt'] #print "Effective Bulk Wind Difference:", ebwspd scp = params.scp(mupcl.bplus, effective_srh[0], ebwspd) indices['SCP'] = [scp, 1, ''] stp_cin = params.stp_cin(mlpcl.bplus, effective_srh[0], ebwspd, mlpcl.lclhght, mlpcl.bminus) indices['STP(cin)'] = [stp_cin, 1, ''] #print "Supercell Composite Parameter:", scp #print "Significant Tornado Parameter (w/CIN):", stp_cin #print "Significant Tornado Parameter (fixed):", stp_fixed # Update the indices within the indices dictionary on the side of the plot. string = '' for index, value in sorted(indices.items()): if np.ma.is_masked(value[0]): if debug: print("skipping masked value for index=", index) continue if debug: print("index=", index) print("value=", value) format = '%.' + str(value[1]) + 'f' string += index + ": " + format % value[0] + " " + value[2] + '\n' return string
''' Create the Sounding (Profile) Object '''
p6km = interp.pres(prof, interp.to_msl(prof, 6000.)) p1km = interp.pres(prof, interp.to_msl(prof, 1000.)) mean_3km = winds.mean_wind(prof, pbot=sfc, ptop=p3km) sfc_6km_shear = winds.wind_shear(prof, pbot=sfc, ptop=p6km) sfc_3km_shear = winds.wind_shear(prof, pbot=sfc, ptop=p3km) sfc_1km_shear = winds.wind_shear(prof, pbot=sfc, ptop=p1km) srwind = params.bunkers_storm_motion(prof) srh3km = winds.helicity(prof, 0, 3000., stu=srwind[0], stv=srwind[1]) srh1km = winds.helicity(prof, 0, 1000., stu=srwind[0], stv=srwind[1]) stp_fixed = params.stp_fixed( sfcpcl.bplus, sfcpcl.lclhght, srh1km[0], utils.comp2vec(sfc_6km_shear[0], sfc_6km_shear[1])[1]) ship = params.ship(prof) eff_inflow = params.effective_inflow_layer(prof) ebot_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[0])) etop_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[1])) effective_srh = winds.helicity(prof, ebot_hght, etop_hght, stu=srwind[0], stv=srwind[1]) ebwd = winds.wind_shear(prof, pbot=eff_inflow[0], ptop=eff_inflow[1]) ebwspd = utils.mag(ebwd[0], ebwd[1]) scp = params.scp(mupcl.bplus, effective_srh[0], ebwspd) stp_cin = params.stp_cin(mlpcl.bplus, effective_srh[0], ebwspd, mlpcl.lclhght, mlpcl.bminus) indices = {'SBCAPE': [int(sfcpcl.bplus), 'J/kg'],\ 'SBCIN': [int(sfcpcl.bminus), 'J/kg'],\ 'SBLCL': [int(sfcpcl.lclhght), 'm AGL'],\
# annotate dewpoint in F at bottom of dewpoint profile dewpointF = skew.ax.text(prof.dwpc[0], prof.pres[0]+10, utils.INT2STR(thermo.ctof(prof.dwpc[0])), verticalalignment='top', horizontalalignment='center', size=7, color=dwpt_trace.get_color()) skew.plot(pcl.ptrace, pcl.ttrace, 'brown', linestyle="dashed" ) # parcel temperature trace skew.ax.set_ylim(1050,100) skew.ax.set_xlim(-50,45) # Plot the effective inflow layer using purple horizontal lines eff_inflow = params.effective_inflow_layer(prof) inflow_bot = skew.ax.axhline(eff_inflow[0], color='purple',xmin=0.38, xmax=0.45) inflow_top = skew.ax.axhline(eff_inflow[1], color='purple',xmin=0.38, xmax=0.45) srwind = params.bunkers_storm_motion(prof) # annotate effective inflow layer SRH if eff_inflow[0]: ebot_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[0])) etop_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[1])) effective_srh = winds.helicity(prof, ebot_hght, etop_hght, stu = srwind[0], stv = srwind[1]) # Set position of label # x position is mean of horizontal line bounds # For some reason this makes a big white space on the left side and for all subsequent plots. inflow_SRH = skew.ax.text( np.mean(inflow_top.get_xdata()), eff_inflow[1], '%.0f' % effective_srh[0] + ' ' + '$\mathregular{m^{2}s^{-2}}$', verticalalignment='bottom', horizontalalignment='center', size=6, transform=inflow_bot.get_transform(), color=inflow_top.get_color() ) # draw indices text string to the right of plot. indices_text = plt.text(1.08, 1.0, myskewt.indices(prof), verticalalignment='top', size=5.6, transform=plt.gca().transAxes) # globe with dot
def append_wbz(): #Load each ERA-Interim netcdf file, and append wbz start_lat = -44.525 end_lat = -9.975 start_lon = 111.975 end_lon = 156.275 domain = [start_lat, end_lat, start_lon, end_lon] model = "erai" region = "aus" dates = [] for y in np.arange(1979, 2019): for m in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]: if (m != 12): dates.append([dt.datetime(y,m,1,0,0,0),\ dt.datetime(y,m+1,1,0,0,0)-dt.timedelta(hours = 6)]) else: dates.append([dt.datetime(y,m,1,0,0,0),\ dt.datetime(y+1,1,1,0,0,0)-dt.timedelta(hours = 6)]) for t in np.arange(0, len(dates)): print(str(dates[t][0]) + " - " + str(dates[t][1])) fname = "/g/data/eg3/ab4502/ExtremeWind/"+region+"/"+model+"/"+model+"_"+\ dt.datetime.strftime(dates[t][0],"%Y%m%d")+"_"+\ dt.datetime.strftime(dates[t][-1],"%Y%m%d")+".nc" ta,dp,hur,hgt,terrain,p,ps,wap,ua,va,uas,vas,tas,ta2d,cp,wg10,cape,lon,lat,date_list = \ read_erai(domain,dates[t]) dp = get_dp(ta, hur, dp_mask=False) agl_idx = (p <= ps) #Replace masked dp values dp = replace_dp(dp) try: prof = profile.create_profile(pres = np.insert(p[agl_idx],0,ps), \ hght = np.insert(hgt[agl_idx],0,terrain), \ tmpc = np.insert(ta[agl_idx],0,tas), \ dwpc = np.insert(dp[agl_idx],0,ta2d), \ u = np.insert(ua[agl_idx],0,uas), \ v = np.insert(va[agl_idx],0,vas), \ strictqc=False, omeg=np.insert(wap[agl_idx],0,wap[agl_idx][0]) ) except: p = p[agl_idx] ua = ua[agl_idx] va = va[agl_idx] hgt = hgt[agl_idx] ta = ta[agl_idx] \ dp = dp[agl_idx] p[0] = ps ua[0] = uas va[0] = vas hgt[0] = terrain ta[0] = tas dp[0] = ta2d prof = profile.create_profile(pres = p, \ hght = hgt, \ tmpc = ta, \ dwpc = dp, \ u = ua, \ v = va, \ strictqc=False, omeg=wap[agl_idx]) pwb0 = params.temp_lvl(prof, 0, wetbulb=True) hwb0 = interp.to_agl(prof, interp.hght(prof, pwb0)) param_file = nc.Dataset(fname, "a") wbz_var = param_file.createVariable("wbz",float,\ ("time","lat","lon")) wbz_var.units = "m" wbz_var.long_name = "wet_bulb_zero_height" wbz_var[:] = hwb0 T1 = abs( thermo.wetlift(prof.pres[0], prof.tmpc[0], 600) - interp.temp(prof, 600)) T2 = abs( thermo.wetlift(pwb0, interp.temp(prof, pwb0), sfc) - prof.tmpc[0]) Vprime = utils.KTS2MS(13 * np.sqrt((T1 + T2) / 2) + (1 / 3 * (Umean01))) Vprime_var = param_file.createVariable("Vprime",float,\ ("time","lat","lon")) Vprime_var.units = "m/s" Vprime_var.long_name = "miller_1972_wind_speed" Vprime_var[:] = Vprime param_file.close()
def best_guess_precip(prof, init_phase, init_lvl, init_temp, tpos, tneg): ''' Best Guess Precipitation type Adapted from SHARP code donated by Rich Thompson (SPC) Description: This algorithm utilizes the output from the init_phase() and posneg_temperature() functions to make a best guess at the preciptation type one would observe at the surface given a thermodynamic profile. Precipitation Types Supported: - None - Rain - Snow - Sleet and Snow - Sleet - Freezing Rain/Drizzle - Unknown Parameters ---------- prof : Profile object init_phase : the initial phase of the precipitation (int) (see 2nd value returned from init_phase()) init_lvl : the inital level of the precipitation source (mb) (see 1st value returned from init_phase()) init_temp : the inital level of the precipitation source (C) (see 3rd value returned from init_phase()) tpos : the positive area (> 0 C) in the temperature profile (J/kg) Returns ------- precip_type : a string containing the best guess precipitation type ''' # Needs to be tested precip_type = None # Case: No precip if init_phase < 0: precip_type = "None." # Case: Always too warm - Rain elif init_phase == 0 and tneg >=0 and prof.tmpc[prof.get_sfc()] > 0: precip_type = "Rain." # Case: always too cold elif init_phase == 3 and tpos <= 0 and prof.tmpc[prof.get_sfc()] <= 0: precip_type = "Snow." # Case: ZR too warm at sfc - Rain elif init_phase == 1 and tpos <= 0 and prof.tmpc[prof.get_sfc()] > 0: precip_type = "Rain." # Case: non-snow init...always too cold - Initphase & sleet elif init_phase == 1 and tpos <= 0 and prof.tmpc[prof.get_sfc()] <= 0: #print interp.to_agl(prof, interp.hght(prof, init_lvl)) if interp.to_agl(prof, interp.hght(prof, init_lvl)) >= 3000: if init_temp <= -4: precip_type = "Sleet and Snow." else: precip_type = "Sleet." else: precip_type = "Freezing Rain/Drizzle." # Case: Snow...but warm at sfc elif init_phase == 3 and tpos <= 0 and prof.tmpc[prof.get_sfc()] > 0: if prof.tmpc[prof.get_sfc()] > 4: precip_type = "Rain." else: precip_type = "Snow." # Case: Warm layer. elif tpos > 0: x1 = tpos y1 = -tneg y2 = (0.62 * x1) + 60.0 if y1 > y2: precip_type = "Sleet." else: if prof.tmpc[prof.get_sfc()] <= 0: precip_type = "Freezing Rain." else: precip_type = "Rain." else: precip_type = "Unknown." return precip_type
linestyle="dashed") # parcel temperature trace # Plot the effective inflow layer using purple horizontal lines eff_inflow = params.effective_inflow_layer(prof) inflow_bot = skew.ax.axhline(eff_inflow[0], color='purple', xmin=0.38, xmax=0.45) inflow_top = skew.ax.axhline(eff_inflow[1], color='purple', xmin=0.38, xmax=0.45) srwind = params.bunkers_storm_motion(prof) # annotate effective inflow layer SRH if eff_inflow[0]: ebot_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[0])) etop_hght = interp.to_agl(prof, interp.hght(prof, eff_inflow[1])) effective_srh = winds.helicity(prof, ebot_hght, etop_hght, stu=srwind[0], stv=srwind[1]) # Set position of label # x position is mean of horizontal line bounds # For some reason this makes a big white space on the left side and for all subsequent plots. inflow_SRH = skew.ax.text(np.mean(inflow_top.get_xdata()), eff_inflow[1], '%.0f' % effective_srh[0] + ' ' + '$\mathregular{m^{2}s^{-2}}$', verticalalignment='bottom', horizontalalignment='center',
def do_sharppy(spc_file): """ Based on the tutorial which can be found here: http://nbviewer.ipython.org/github/sharppy/SHARPpy/blob/master/tutorials/SHARPpy_basics.ipynb SHARPpy can be found here: https://github.com/sharppy/SHARPpy Credit goes to: Patrick Marsh (SPC) Kelton Halbert (OU School of Meteorology) Greg Blumberg (OU/CIMMS) Tim Supinie (OU School of Meteorology) """ import sharppy import sharppy.sharptab.profile as profile import sharppy.sharptab.interp as interp import sharppy.sharptab.winds as winds import sharppy.sharptab.utils as utils import sharppy.sharptab.params as params import sharppy.sharptab.thermo as thermo import matplotlib.pyplot as plt from StringIO import StringIO from matplotlib.axes import Axes import matplotlib.transforms as transforms import matplotlib.axis as maxis import matplotlib.spines as mspines import matplotlib.path as mpath from matplotlib.projections import register_projection spc_file = open('skewt_data', 'r').read() def parseSPC(spc_file): ## read in the file data = np.array([l.strip() for l in spc_file.split('\n')]) ## necessary index points title_idx = np.where( data == '%TITLE%')[0][0] start_idx = np.where( data == '%RAW%' )[0] + 1 finish_idx = np.where( data == '%END%')[0] ## create the plot title data_header = data[title_idx + 1].split() location = data_header[0]+' '+data_header[1] time = data_header[2] title = location+' '+time ## put it all together for StringIO full_data = '\n'.join(data[start_idx : finish_idx][:]) sound_data = StringIO( full_data ) ## read the data into arrays p, h, T, Td, wdir, wspd = np.genfromtxt( sound_data, delimiter=',', comments="%", unpack=True ) return p, h, T, Td, wdir, wspd, title pres, hght, tmpc, dwpc, wdir, wspd, title = parseSPC(spc_file) prof = profile.create_profile(profile='default', pres=pres, hght=hght, tmpc=tmpc, \ dwpc=dwpc, wspd=wspd, wdir=wdir, missing=-9999, strictQC=True) sfcpcl = params.parcelx( prof, flag=1 ) # Surface Parcel fcstpcl = params.parcelx( prof, flag=2 ) # Forecast Parcel mupcl = params.parcelx( prof, flag=3 ) # Most-Unstable Parcel mlpcl = params.parcelx( prof, flag=4 ) # 100 mb Mean Layer Parcel msl_hght = prof.hght[prof.sfc] # Grab the surface height value print "SURFACE HEIGHT (m MSL):",msl_hght agl_hght = interp.to_agl(prof, msl_hght) # Converts to AGL print "SURFACE HEIGHT (m AGL):", agl_hght msl_hght = interp.to_msl(prof, agl_hght) # Converts to MSL print "SURFACE HEIGHT (m MSL):",msl_hght print "Most-Unstable CAPE:", mupcl.bplus # J/kg print "Most-Unstable CIN:", mupcl.bminus # J/kg print "Most-Unstable LCL:", mupcl.lclhght # meters AGL print "Most-Unstable LFC:", mupcl.lfchght # meters AGL print "Most-Unstable EL:", mupcl.elhght # meters AGL print "Most-Unstable LI:", mupcl.li5 # C class SkewXTick(maxis.XTick): def draw(self, renderer): if not self.get_visible(): return renderer.open_group(self.__name__) lower_interval = self.axes.xaxis.lower_interval upper_interval = self.axes.xaxis.upper_interval if self.gridOn and transforms.interval_contains( self.axes.xaxis.get_view_interval(), self.get_loc()): self.gridline.draw(renderer) if transforms.interval_contains(lower_interval, self.get_loc()): if self.tick1On: self.tick1line.draw(renderer) if self.label1On: self.label1.draw(renderer) if transforms.interval_contains(upper_interval, self.get_loc()): if self.tick2On: self.tick2line.draw(renderer) if self.label2On: self.label2.draw(renderer) renderer.close_group(self.__name__) # This class exists to provide two separate sets of intervals to the tick, # as well as create instances of the custom tick class SkewXAxis(maxis.XAxis): def __init__(self, *args, **kwargs): maxis.XAxis.__init__(self, *args, **kwargs) self.upper_interval = 0.0, 1.0 def _get_tick(self, major): return SkewXTick(self.axes, 0, '', major=major) @property def lower_interval(self): return self.axes.viewLim.intervalx def get_view_interval(self): return self.upper_interval[0], self.axes.viewLim.intervalx[1] # This class exists to calculate the separate data range of the # upper X-axis and draw the spine there. It also provides this range # to the X-axis artist for ticking and gridlines class SkewSpine(mspines.Spine): def _adjust_location(self): trans = self.axes.transDataToAxes.inverted() if self.spine_type == 'top': yloc = 1.0 else: yloc = 0.0 left = trans.transform_point((0.0, yloc))[0] right = trans.transform_point((1.0, yloc))[0] pts = self._path.vertices pts[0, 0] = left pts[1, 0] = right self.axis.upper_interval = (left, right) # This class handles registration of the skew-xaxes as a projection as well # as setting up the appropriate transformations. It also overrides standard # spines and axes instances as appropriate. class SkewXAxes(Axes): # The projection must specify a name. This will be used be the # user to select the projection, i.e. ``subplot(111, # projection='skewx')``. name = 'skewx' def _init_axis(self): #Taken from Axes and modified to use our modified X-axis self.xaxis = SkewXAxis(self) self.spines['top'].register_axis(self.xaxis) self.spines['bottom'].register_axis(self.xaxis) self.yaxis = maxis.YAxis(self) self.spines['left'].register_axis(self.yaxis) self.spines['right'].register_axis(self.yaxis) def _gen_axes_spines(self): spines = {'top':SkewSpine.linear_spine(self, 'top'), 'bottom':mspines.Spine.linear_spine(self, 'bottom'), 'left':mspines.Spine.linear_spine(self, 'left'), 'right':mspines.Spine.linear_spine(self, 'right')} return spines def _set_lim_and_transforms(self): """ This is called once when the plot is created to set up all the transforms for the data, text and grids. """ rot = 30 #Get the standard transform setup from the Axes base class Axes._set_lim_and_transforms(self) # Need to put the skew in the middle, after the scale and limits, # but before the transAxes. This way, the skew is done in Axes # coordinates thus performing the transform around the proper origin # We keep the pre-transAxes transform around for other users, like the # spines for finding bounds self.transDataToAxes = self.transScale + (self.transLimits + transforms.Affine2D().skew_deg(rot, 0)) # Create the full transform from Data to Pixels self.transData = self.transDataToAxes + self.transAxes # Blended transforms like this need to have the skewing applied using # both axes, in axes coords like before. self._xaxis_transform = (transforms.blended_transform_factory( self.transScale + self.transLimits, transforms.IdentityTransform()) + transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes # Now register the projection with matplotlib so the user can select # it. register_projection(SkewXAxes) pcl = mupcl # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(6.5875, 6.2125)) ax = fig.add_subplot(111, projection='skewx') ax.grid(True) pmax = 1000 pmin = 10 dp = -10 presvals = np.arange(int(pmax), int(pmin)+dp, dp) # plot the moist-adiabats for t in np.arange(-10,45,5): tw = [] for p in presvals: tw.append(thermo.wetlift(1000., t, p)) ax.semilogy(tw, presvals, 'k-', alpha=.2) def thetas(theta, presvals): return ((theta + thermo.ZEROCNK) / (np.power((1000. / presvals),thermo.ROCP))) - thermo.ZEROCNK # plot the dry adiabats for t in np.arange(-50,110,10): ax.semilogy(thetas(t, presvals), presvals, 'r-', alpha=.2) plt.title(title, fontsize=14, loc='left') # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dicatated by the typical meteorological plot ax.semilogy(prof.tmpc, prof.pres, 'r', lw=2) ax.semilogy(prof.dwpc, prof.pres, 'g', lw=2) ax.semilogy(pcl.ttrace, pcl.ptrace, 'k-.', lw=2) # An example of a slanted line at constant X l = ax.axvline(0, color='b', linestyle='--') l = ax.axvline(-20, color='b', linestyle='--') # Disables the log-formatting that comes with semilogy ax.yaxis.set_major_formatter(plt.ScalarFormatter()) ax.set_yticks(np.linspace(100,1000,10)) ax.set_ylim(1050,100) ax.xaxis.set_major_locator(plt.MultipleLocator(10)) ax.set_xlim(-50,50) plt.show() ##PLOTS SKEWT OK ABOVE HERE ## """
def do_sharppy(spc_file): """ Based on the tutorial which can be found here: http://nbviewer.ipython.org/github/sharppy/SHARPpy/blob/master/tutorials/SHARPpy_basics.ipynb SHARPpy can be found here: https://github.com/sharppy/SHARPpy Credit goes to: Patrick Marsh (SPC) Kelton Halbert (OU School of Meteorology) Greg Blumberg (OU/CIMMS) Tim Supinie (OU School of Meteorology) """ import sharppy import sharppy.sharptab.profile as profile import sharppy.sharptab.interp as interp import sharppy.sharptab.winds as winds import sharppy.sharptab.utils as utils import sharppy.sharptab.params as params import sharppy.sharptab.thermo as thermo import matplotlib.pyplot as plt from StringIO import StringIO from matplotlib.axes import Axes import matplotlib.transforms as transforms import matplotlib.axis as maxis import matplotlib.spines as mspines import matplotlib.path as mpath from matplotlib.projections import register_projection spc_file = open('skewt_data', 'r').read() def parseSPC(spc_file): ## read in the file data = np.array([l.strip() for l in spc_file.split('\n')]) ## necessary index points title_idx = np.where(data == '%TITLE%')[0][0] start_idx = np.where(data == '%RAW%')[0] + 1 finish_idx = np.where(data == '%END%')[0] ## create the plot title data_header = data[title_idx + 1].split() location = data_header[0] + ' ' + data_header[1] time = data_header[2] title = location + ' ' + time ## put it all together for StringIO full_data = '\n'.join(data[start_idx:finish_idx][:]) sound_data = StringIO(full_data) ## read the data into arrays p, h, T, Td, wdir, wspd = np.genfromtxt(sound_data, delimiter=',', comments="%", unpack=True) return p, h, T, Td, wdir, wspd, title pres, hght, tmpc, dwpc, wdir, wspd, title = parseSPC(spc_file) prof = profile.create_profile(profile='default', pres=pres, hght=hght, tmpc=tmpc, \ dwpc=dwpc, wspd=wspd, wdir=wdir, missing=-9999, strictQC=True) sfcpcl = params.parcelx(prof, flag=1) # Surface Parcel fcstpcl = params.parcelx(prof, flag=2) # Forecast Parcel mupcl = params.parcelx(prof, flag=3) # Most-Unstable Parcel mlpcl = params.parcelx(prof, flag=4) # 100 mb Mean Layer Parcel msl_hght = prof.hght[prof.sfc] # Grab the surface height value print "SURFACE HEIGHT (m MSL):", msl_hght agl_hght = interp.to_agl(prof, msl_hght) # Converts to AGL print "SURFACE HEIGHT (m AGL):", agl_hght msl_hght = interp.to_msl(prof, agl_hght) # Converts to MSL print "SURFACE HEIGHT (m MSL):", msl_hght print "Most-Unstable CAPE:", mupcl.bplus # J/kg print "Most-Unstable CIN:", mupcl.bminus # J/kg print "Most-Unstable LCL:", mupcl.lclhght # meters AGL print "Most-Unstable LFC:", mupcl.lfchght # meters AGL print "Most-Unstable EL:", mupcl.elhght # meters AGL print "Most-Unstable LI:", mupcl.li5 # C class SkewXTick(maxis.XTick): def draw(self, renderer): if not self.get_visible(): return renderer.open_group(self.__name__) lower_interval = self.axes.xaxis.lower_interval upper_interval = self.axes.xaxis.upper_interval if self.gridOn and transforms.interval_contains( self.axes.xaxis.get_view_interval(), self.get_loc()): self.gridline.draw(renderer) if transforms.interval_contains(lower_interval, self.get_loc()): if self.tick1On: self.tick1line.draw(renderer) if self.label1On: self.label1.draw(renderer) if transforms.interval_contains(upper_interval, self.get_loc()): if self.tick2On: self.tick2line.draw(renderer) if self.label2On: self.label2.draw(renderer) renderer.close_group(self.__name__) # This class exists to provide two separate sets of intervals to the tick, # as well as create instances of the custom tick class SkewXAxis(maxis.XAxis): def __init__(self, *args, **kwargs): maxis.XAxis.__init__(self, *args, **kwargs) self.upper_interval = 0.0, 1.0 def _get_tick(self, major): return SkewXTick(self.axes, 0, '', major=major) @property def lower_interval(self): return self.axes.viewLim.intervalx def get_view_interval(self): return self.upper_interval[0], self.axes.viewLim.intervalx[1] # This class exists to calculate the separate data range of the # upper X-axis and draw the spine there. It also provides this range # to the X-axis artist for ticking and gridlines class SkewSpine(mspines.Spine): def _adjust_location(self): trans = self.axes.transDataToAxes.inverted() if self.spine_type == 'top': yloc = 1.0 else: yloc = 0.0 left = trans.transform_point((0.0, yloc))[0] right = trans.transform_point((1.0, yloc))[0] pts = self._path.vertices pts[0, 0] = left pts[1, 0] = right self.axis.upper_interval = (left, right) # This class handles registration of the skew-xaxes as a projection as well # as setting up the appropriate transformations. It also overrides standard # spines and axes instances as appropriate. class SkewXAxes(Axes): # The projection must specify a name. This will be used be the # user to select the projection, i.e. ``subplot(111, # projection='skewx')``. name = 'skewx' def _init_axis(self): #Taken from Axes and modified to use our modified X-axis self.xaxis = SkewXAxis(self) self.spines['top'].register_axis(self.xaxis) self.spines['bottom'].register_axis(self.xaxis) self.yaxis = maxis.YAxis(self) self.spines['left'].register_axis(self.yaxis) self.spines['right'].register_axis(self.yaxis) def _gen_axes_spines(self): spines = { 'top': SkewSpine.linear_spine(self, 'top'), 'bottom': mspines.Spine.linear_spine(self, 'bottom'), 'left': mspines.Spine.linear_spine(self, 'left'), 'right': mspines.Spine.linear_spine(self, 'right') } return spines def _set_lim_and_transforms(self): """ This is called once when the plot is created to set up all the transforms for the data, text and grids. """ rot = 30 #Get the standard transform setup from the Axes base class Axes._set_lim_and_transforms(self) # Need to put the skew in the middle, after the scale and limits, # but before the transAxes. This way, the skew is done in Axes # coordinates thus performing the transform around the proper origin # We keep the pre-transAxes transform around for other users, like the # spines for finding bounds self.transDataToAxes = self.transScale + ( self.transLimits + transforms.Affine2D().skew_deg(rot, 0)) # Create the full transform from Data to Pixels self.transData = self.transDataToAxes + self.transAxes # Blended transforms like this need to have the skewing applied using # both axes, in axes coords like before. self._xaxis_transform = ( transforms.blended_transform_factory( self.transScale + self.transLimits, transforms.IdentityTransform()) + transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes # Now register the projection with matplotlib so the user can select # it. register_projection(SkewXAxes) pcl = mupcl # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(6.5875, 6.2125)) ax = fig.add_subplot(111, projection='skewx') ax.grid(True) pmax = 1000 pmin = 10 dp = -10 presvals = np.arange(int(pmax), int(pmin) + dp, dp) # plot the moist-adiabats for t in np.arange(-10, 45, 5): tw = [] for p in presvals: tw.append(thermo.wetlift(1000., t, p)) ax.semilogy(tw, presvals, 'k-', alpha=.2) def thetas(theta, presvals): return ((theta + thermo.ZEROCNK) / (np.power( (1000. / presvals), thermo.ROCP))) - thermo.ZEROCNK # plot the dry adiabats for t in np.arange(-50, 110, 10): ax.semilogy(thetas(t, presvals), presvals, 'r-', alpha=.2) plt.title(title, fontsize=14, loc='left') # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dicatated by the typical meteorological plot ax.semilogy(prof.tmpc, prof.pres, 'r', lw=2) ax.semilogy(prof.dwpc, prof.pres, 'g', lw=2) ax.semilogy(pcl.ttrace, pcl.ptrace, 'k-.', lw=2) # An example of a slanted line at constant X l = ax.axvline(0, color='b', linestyle='--') l = ax.axvline(-20, color='b', linestyle='--') # Disables the log-formatting that comes with semilogy ax.yaxis.set_major_formatter(plt.ScalarFormatter()) ax.set_yticks(np.linspace(100, 1000, 10)) ax.set_ylim(1050, 100) ax.xaxis.set_major_locator(plt.MultipleLocator(10)) ax.set_xlim(-50, 50) plt.show() ##PLOTS SKEWT OK ABOVE HERE ## """