def posneg_wetbulb(prof, start=-1): ''' Positive/Negative Wetbulb profile Adapted from SHARP code donated by Rich Thompson (SPC) Description: This routine calculates the positive (above 0 C) and negative (below 0 C) areas of the wet bulb profile starting from a specified pressure (start). If the specified pressure is not given, this routine calls init_phase() to obtain the pressure level the precipitation expected to fall begins at. This is an routine considers the wet-bulb profile instead of the temperature profile in case the profile beneath the profile beneath the falling precipitation becomes saturated. Parameters ---------- prof : Profile object start : the pressure level the precpitation originates from (found by calling init_phase()) Returns ------- pos : the positive area (> 0 C) of the wet-bulb profile in J/kg neg : the negative area (< 0 C) of the wet-bulb profile in J/kg top : the top of the precipitation layer pressure in mb bot : the bottom of the precipitation layer pressure in mb ''' # Needs to be tested # If there is no sounding, don't compute anything if utils.QC(interp.temp(prof, 500)) == False and utils.QC( interp.temp(prof, 850)) == False: return np.ma.masked, np.ma.masked, np.ma.masked, np.ma.masked # Find lowest obs in layer lower = prof.pres[prof.get_sfc()] lptr = prof.get_sfc() # Find the highest obs in the layer if start == -1: lvl, phase, st = init_phase(prof) if lvl > 0: upper = lvl else: upper = 500. else: upper = start # Find the level where the pressure is just greater than the upper pressure idxs = np.where(prof.pres > upper)[0] if len(idxs) == 0: uptr = 0 else: uptr = idxs[-1] # Start with the upper layer pe1 = upper h1 = interp.hght(prof, pe1) te1 = thermo.wetbulb(pe1, interp.temp(prof, pe1), interp.dwpt(prof, pe1)) tp1 = 0 warmlayer = coldlayer = lyre = totp = totn = tote = ptop = pbot = lyrlast = 0 for i in np.arange(uptr, lptr - 1, -1): pe2 = prof.pres[i] h2 = prof.hght[i] te2 = thermo.wetbulb(pe2, interp.temp(prof, pe2), interp.dwpt(prof, pe2)) tp2 = 0 tdef1 = (0 - te1) / thermo.ctok(te1) tdef2 = (0 - te2) / thermo.ctok(te2) lyrlast = lyre lyre = 9.8 * (tdef1 + tdef2) / 2.0 * (h2 - h1) # Has a warm layer been found yet? if te2 > 0: if warmlayer == 0: warmlayer = 1 ptop = pe2 # Has a cold layer been found yet? if te2 < 0: if warmlayer == 1 and coldlayer == 0: coldlayer = 1 pbot = pe2 if warmlayer > 0: if lyre > 0: totp += lyre else: totn += lyre tote += lyre pelast = pe1 pe1 = pe2 h1 = h2 te1 = te2 tp1 = tp2 if warmlayer == 1 and coldlayer == 1: pos = totp neg = totn top = ptop bot = pbot else: neg = 0 pos = 0 bot = 0 top = 0 return pos, neg, top, bot
def qc(val): return -9999. if not utils.QC(val) else val
def total_shear(prof, pbot=850, ptop=250, dp=-1, exact=True): ''' Calculates the total shear (also known as the hodograph length) between the wind at (pbot) and (ptop). Parameters ---------- prof: profile object Profile object pbot : number (optional; default 850 hPa) Pressure of the bottom level (hPa) ptop : number (optional; default 250 hPa) Pressure of the top level (hPa) dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding exact : bool (optional; default = True) Switch to choose between using the exact data (slower) or using interpolated sounding at 'dp' pressure levels (faster) Returns ------- tot_pos_shr : number Total positive shear tot_neg_shr : number Total negative shear tot_net_shr : number Total net shear (subtracting negative shear from positive shear) tot_abs_shr : number Total absolute shear (adding positive and negative shear together) ''' if np.isnan(pbot) or np.isnan(ptop) or \ type(pbot) == type(ma.masked) or type(ptop) == type(ma.masked): print np.ma.masked, np.ma.masked, np.ma.masked, np.ma.masked if exact: ind1 = np.where(pbot > prof.pres)[0].min() ind2 = np.where(ptop < prof.pres)[0].max() u1, v1 = interp.components(prof, pbot) u2, v2 = interp.components(prof, ptop) u = np.concatenate([[u1], prof.u[ind1:ind2+1].compressed(), [u2]]) v = np.concatenate([[v1], prof.v[ind1:ind2+1].compressed(), [v2]]) else: ps = np.arange(pbot, ptop+dp, dp) u, v = interp.components(prof, ps) shu = u[1:] - u[:-1] shv = v[1:] - v[:-1] shr = ( ( np.power(shu, 2) + np.power(shv, 2) ) ** 0.5 ) wdr, wsp = utils.comp2vec(u,v) t_wdr = wdr[1:] mod = 180 - wdr[:-1] t_wdr = t_wdr + mod idx1 = ma.where(t_wdr < 0)[0] idx2 = ma.where(t_wdr >= 360)[0] t_wdr[idx1] = t_wdr[idx1] + 360 t_wdr[idx2] = t_wdr[idx2] - 360 d_wdr = t_wdr - 180 d_wsp = wsp[1:] - wsp[:-1] idxdp = ma.where(d_wdr > 0)[0] idxdn = ma.where(d_wdr < 0)[0] idxsp = ma.where(np.logical_and(d_wdr == 0, d_wsp > 0) == True)[0] idxsn = ma.where(np.logical_and(d_wdr == 0, d_wsp < 0) == True)[0] if not utils.QC(idxdp): pos_dir_shr = ma.masked else: pos_dir_shr = shr[idxdp].sum() if not utils.QC(idxdn): neg_dir_shr = ma.masked else: neg_dir_shr = shr[idxdn].sum() if not utils.QC(idxsp): pos_spd_shr = ma.masked else: pos_spd_shr = shr[idxsp].sum() if not utils.QC(idxsn): neg_spd_shr = ma.masked else: neg_spd_shr = shr[idxsn].sum() tot_pos_shr = pos_dir_shr + pos_spd_shr tot_neg_shr = neg_dir_shr + neg_spd_shr tot_net_shr = tot_pos_shr - tot_neg_shr tot_abs_shr = tot_pos_shr + tot_neg_shr return tot_pos_shr, tot_neg_shr, tot_net_shr, tot_abs_shr
def draw_profile(self, qp): ''' Draws the storm relative wind profile. Parameters ---------- qp: QtGui.QPainter object ''' ## initialize a pen with a red color, thickness of 1, solid line if self.prof.wdir.count() == 0: return pen = QtGui.QPen(self.trace_color, 1) pen.setStyle(QtCore.Qt.SolidLine) ## if there are missing values, get the mask try: mask = np.maximum(self.sru.mask, self.srv.mask) sru = self.sru[~mask] srv = self.srv[~mask] hgt = self.prof.hght[~mask] ## otherwise the data is fine except: sru = self.sru srv = self.srv hgt = self.prof.hght if len(sru) == 0 or len(srv) == 0 or len(hgt) == 0: return max_hght_idx = len(hgt) - 1 while type(hgt[max_hght_idx]) == type(np.ma.masked): max_hght_idx -= 1 interp_hght = np.arange(self.prof.hght[self.prof.sfc], min(hgt[max_hght_idx], self.hmax * 1000.), 10) interp_sru = np.interp(interp_hght, hgt[:(max_hght_idx + 1)], sru[:(max_hght_idx + 1)]) interp_srv = np.interp(interp_hght, hgt[:(max_hght_idx + 1)], srv[:(max_hght_idx + 1)]) sr_spd = np.hypot(interp_sru, interp_srv) qp.setPen(pen) for i in range(interp_hght.shape[0] - 1): spd1 = sr_spd[i] spd2 = sr_spd[i + 1] if utils.QC(spd1) and utils.QC(spd2): hgt1 = (interp_hght[i] - interp_hght[0]) / 1000 hgt2 = (interp_hght[i + 1] - interp_hght[0]) / 1000 ## convert the wind speeds to x pixels x1 = self.speed_to_pix(spd1) x2 = self.speed_to_pix(spd2) ## convert the height values to y pixels y1 = self.hgt_to_pix(hgt1) y2 = self.hgt_to_pix(hgt2) ## draw a line between the two points qp.drawLine(x1, y1, x2, y2) if utils.QC(self.srw_0_2km): # Plot the 0-2 km mean SRW pen = QtGui.QPen(self.m0_2_color, 2) pen.setStyle(QtCore.Qt.SolidLine) qp.setPen(pen) x1 = self.speed_to_pix(self.srw_0_2km) x2 = self.speed_to_pix(self.srw_0_2km) y1 = self.hgt_to_pix(0.0) y2 = self.hgt_to_pix(2.0) qp.drawLine(x1, y1, x2, y2) if utils.QC(self.srw_4_6km): # Plot the 4-6 km mean SRW pen = QtGui.QPen(self.m4_6_color, 2) pen.setStyle(QtCore.Qt.SolidLine) qp.setPen(pen) x1 = self.speed_to_pix(self.srw_4_6km) x2 = self.speed_to_pix(self.srw_4_6km) y1 = self.hgt_to_pix(4.0) y2 = self.hgt_to_pix(6.0) qp.drawLine(x1, y1, x2, y2) if utils.QC(self.srw_9_11km): # Plot the 9-11 km mean SRW pen = QtGui.QPen(self.m9_11_color, 2) pen.setStyle(QtCore.Qt.SolidLine) qp.setPen(pen) x1 = self.speed_to_pix(self.srw_9_11km) x2 = self.speed_to_pix(self.srw_9_11km) y1 = self.hgt_to_pix(9.0) y2 = self.hgt_to_pix(11.0) qp.drawLine(x1, y1, x2, y2)
def helicity(prof, lower, upper, stu=0, stv=0, dp=-1, exact=True): ''' Calculates the relative helicity (m2/s2) of a layer from lower to upper. If storm-motion vector is supplied, storm-relative helicity, both positve and negative, is returned. Parameters ---------- prof : profile object Profile Object lower : number Bottom level of layer (m, AGL) upper : number Top level of layer (m, AGL) stu : number (optional; default = 0) U-component of storm-motion (kts) stv : number (optional; default = 0) V-component of storm-motion (kts) dp : negative integer (optional; default -1) The pressure increment for the interpolated sounding (mb) exact : bool (optional; default = True) Switch to choose between using the exact data (slower) or using interpolated sounding at 'dp' pressure levels (faster) Returns ------- phel+nhel : number Combined Helicity (m2/s2) phel : number Positive Helicity (m2/s2) nhel : number Negative Helicity (m2/s2) ''' if prof.wdir.count() == 0 or not utils.QC(lower) or not utils.QC( upper) or not utils.QC(stu) or not utils.QC(stv): return ma.masked, ma.masked, ma.masked if lower != upper: lower = interp.to_msl(prof, lower) upper = interp.to_msl(prof, upper) plower = interp.pres(prof, lower) pupper = interp.pres(prof, upper) if np.isnan(plower) or np.isnan(pupper) or \ type(plower) == type(ma.masked) or type(pupper) == type(ma.masked): return np.ma.masked, np.ma.masked, np.ma.masked if exact: ind1 = np.where(plower >= prof.pres)[0].min() ind2 = np.where(pupper <= prof.pres)[0].max() u1, v1 = interp.components(prof, plower) u2, v2 = interp.components(prof, pupper) u = np.concatenate([[u1], prof.u[ind1:ind2 + 1].compressed(), [u2]]) v = np.concatenate([[v1], prof.v[ind1:ind2 + 1].compressed(), [v2]]) else: ps = np.arange(plower, pupper + dp, dp) u, v = interp.components(prof, ps) sru = utils.KTS2MS(u - stu) srv = utils.KTS2MS(v - stv) layers = (sru[1:] * srv[:-1]) - (sru[:-1] * srv[1:]) phel = layers[layers > 0].sum() nhel = layers[layers < 0].sum() else: phel = nhel = 0 return phel + nhel, phel, nhel
def haines_low(prof): ''' Haines Index Low Elevation calculation Calculates the Haines Index(Lower Atmosphere Severity Index) using the lower elevation parmeters, used below 1000ft or 305 m. Pressure levels 950 mb and 850 mb Dewpoint depression at 850 mb Lapse Rate Term --------------- 1 : < 4C 2 : 4C to 7C 3 : > 7C Dewpoint Depression Term ------------------------ 1 : < 6C 2 : 6C to 9C 3 : > 9C Adapted from S-591 course Added by Nickolai Reimer (NWS Billings, MT) Parameters ---------- prof : profile object Profile object Returns ------- param : number the Haines Index low ''' tp1 = interp.temp(prof, 950) tp2 = interp.temp(prof, 850) tdp2 = interp.dwpt(prof, 850) if utils.QC(tp1) and utils.QC(tp2) and utils.QC(tdp2): lapse_rate = tp1 - tp2 dewpoint_depression = tp2 - tdp2 if lapse_rate < 4: a = 1 elif 4 <= lapse_rate and lapse_rate <= 7: a = 2 else: a = 3 if dewpoint_depression < 6: b = 1 elif 6 <= dewpoint_depression and dewpoint_depression <= 9: b = 2 else: b = 3 return a + b else: return constants.MISSING
def haines_mid(prof): ''' Haines Index Mid Elevation calculation Calculates the Haines Index(Lower Atmosphere Severity Index) using the middle elevation parmeters, used between 1000 ft or 305 m and 3000 ft or 914 m. Pressure levels 850 mb and 700 mb Dewpoint depression at 850 mb Lapse Rate Term --------------- 1 : < 6C 2 : 6C to 10C 3 : > 10C Dewpoint Depression Term ------------------------ 1 : < 6C 2 : 6C to 12C 3 : > 12C Adapted from S-591 course Added by Nickolai Reimer (NWS Billings, MT) Parameters ---------- prof : profile object Profile object Returns ------- param : number the Haines Index mid ''' tp1 = interp.temp(prof, 850) tp2 = interp.temp(prof, 700) tdp1 = interp.dwpt(prof, 850) if utils.QC(tp1) and utils.QC(tp2) and utils.QC(tdp1): lapse_rate = tp1 - tp2 dewpoint_depression = tp1 - tdp1 if lapse_rate < 6: a = 1 elif 6 <= lapse_rate and lapse_rate <= 10: a = 2 else: a = 3 if dewpoint_depression < 6: b = 1 elif 6 <= dewpoint_depression and dewpoint_depression <= 12: b = 2 else: b = 3 return a + b else: return constants.MISSING