def test_lcl_convergence(): """Test LCL calculation convergence failure.""" with pytest.raises(RuntimeError): lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC, max_iters=2)
def parcelUAV(T, Td, p): ''' Inputs: temperature, dewpoint, and pressure Returns: lcl pressure, lcl temperature, isbelowlcl flag, profile temp ''' lclpres, lcltemp = mcalc.lcl(p[0] * units.mbar, T[0] * units.degC, Td[0] * units.degC) print 'LCL Pressure: %5.2f %s' % (lclpres.magnitude, lclpres.units) print 'LCL Temperature: %5.2f %s' % (lcltemp.magnitude, lcltemp.units) # parcel profile # determine if there are points sampled above lcl ilcl = np.squeeze(np.where((p * units.mbar) <= lclpres)) # if not, entire profile dry adiabatic if ilcl.size == 0: prof = mcalc.dry_lapse(p * units.mbar, T[0] * units.degC).to('degC') isbelowlcl = 1 # if there are, need to concat dry & moist profile ascents else: ilcl = ilcl[0] prof_dry = mcalc.dry_lapse(p[:ilcl] * units.mbar, T[0] * units.degC).to('degC') prof_moist = mcalc.moist_lapse(p[ilcl:] * units.mbar, prof_dry[-1]).to('degC') prof = np.concatenate((prof_dry, prof_moist)) * units.degC isbelowlcl = 0 return lclpres, lcltemp, isbelowlcl, prof
def calculate_basic_thermo(self): #Enclose in try, except because not every sounding will have a converging parcel path or CAPE. try: #Precipitable Water self.pw = mc.precipitable_water(self.sounding["pres"], self.sounding["dewp"]) #Lifting condensation level self.lcl_pres, self.lcl_temp = mc.lcl(self.sounding["pres"][0], self.sounding["temp"][0], self.sounding["dewp"][0]) #Surface-based CAPE and CIN self.parcel_path = mc.parcel_profile(self.sounding["pres"], self.sounding["temp"][0], self.sounding["dewp"][0]) self.sfc_cape, self.sfc_cin = mc.cape_cin(self.sounding["pres"], self.sounding["temp"], self.sounding["dewp"], self.parcel_path) #Do this when parcel path fails to converge except Exception as e: print("WARNING: No LCL, CAPE, or PW stats because:\n{}.".format(e)) self.parcel_path = numpy.nan self.pw = numpy.nan self.lcl_pres = numpy.nan self.lcl_temp = numpy.nan self.sfc_cape = numpy.nan self.sfc_cin = numpy.nan #Returning return
def showalter_index(pressure, temperature, dewpt): """Calculate Showalter Index from pressure temperature and 850 hPa lcl. Showalter Index derived from [Galway1956]_: SI = T500 - Tp500 where: T500 is the measured temperature at 500 hPa Tp500 is the temperature of the lifted parcel at 500 hPa Parameters ---------- pressure : `pint.Quantity` Atmospheric pressure level(s) of interest, in order from highest to lowest pressure temperature : `pint.Quantity` Parcel temperature for corresponding pressure dewpt (:class: `pint.Quantity`): Parcel dew point temperatures for corresponding pressure Returns ------- `pint.Quantity` Showalter index in delta degrees celsius """ # find the measured temperature and dew point temperature at 850 hPa. idx850 = np.where(pressure == 850 * units.hPa) T850 = temperature[idx850] Td850 = dewpt[idx850] # find the parcel profile temperature at 500 hPa. idx500 = np.where(pressure == 500 * units.hPa) Tp500 = temperature[idx500] # Calculate lcl at the 850 hPa level lcl_calc = mpcalc.lcl(850 * units.hPa, T850[0], Td850[0]) lcl_calc = lcl_calc[0] # Define start and end heights for dry and moist lapse rate calculations p_strt = 1000 * units.hPa p_end = 500 * units.hPa # Calculate parcel temp when raised dry adiabatically from surface to lcl dl = mpcalc.dry_lapse(lcl_calc, temperature[0], p_strt) dl = (dl.magnitude - 273.15) * units.degC # Change units to C # Calculate parcel temp when raised moist adiabatically from lcl to 500mb ml = mpcalc.moist_lapse(p_end, dl, lcl_calc) # Calculate the Showalter index shox = Tp500 - ml return shox
def plot_sounding(date, station): p, T, Td, u, v, windspeed = get_sounding_data(date, station) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) lfc_pressure, lfc_temperature = mpcalc.lfc(p, T, Td) parcel_path = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(8, 8)) skew = SkewT(fig) # Plot the data temperature_line, = skew.plot(p, T, color='tab:red') dewpoint_line, = skew.plot(p, Td, color='blue') cursor = mplcursors.cursor([temperature_line, dewpoint_line]) # Plot thermodynamic parameters and parcel path skew.plot(p, parcel_path, color='black') if lcl_pressure: skew.ax.axhline(lcl_pressure, color='black') if lfc_pressure: skew.ax.axhline(lfc_pressure, color='0.7') # Add the relevant special lines skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() # Shade areas representing CAPE and CIN skew.shade_cin(p, T, parcel_path) skew.shade_cape(p, T, parcel_path) # Add wind barbs skew.plot_barbs(p, u, v) # Add an axes to the plot ax_hod = inset_axes(skew.ax, '30%', '30%', loc=1, borderpad=3) # Plot the hodograph h = Hodograph(ax_hod, component_range=100.) # Grid the hodograph h.add_grid(increment=20) # Plot the data on the hodograph mask = (p >= 100 * units.mbar) h.plot_colormapped(u[mask], v[mask], windspeed[mask]) # Plot a line colored by wind speed # Set some sensible axis limits skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) return fig, skew
def get_skewt_vars(p, tc, tdc, pro): """This function processes the dataset values and returns a string element which can be used as a subtitle to replicate the styles of NCL Skew-T Diagrams. Args: p (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Pressure level input from dataset tc (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Temperature for parcel from dataset tdc (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Dew point temperature for parcel from dataset pro (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Parcel profile temperature converted to degC Returns: :class: 'str' """ # CAPE cape = mpcalc.cape_cin(p, tc, tdc, pro) cape = cape[0].magnitude # Precipitable Water pwat = mpcalc.precipitable_water(p, tdc) pwat = (pwat.magnitude / 10) * units.cm # Convert mm to cm pwat = pwat.magnitude # Pressure and temperature of lcl lcl = mpcalc.lcl(p[0], tc[0], tdc[0]) plcl = lcl[0].magnitude tlcl = lcl[1].magnitude # Showalter index shox = showalter_index(p, tc, tdc) shox = shox[0].magnitude # Place calculated values in iterable list vals = [plcl, tlcl, shox, pwat, cape] vals = [round(num) for num in vals] # Define variable names for calculated values names = ['Plcl=', 'Tlcl[C]=', 'Shox=', 'Pwat[cm]=', 'Cape[J]='] # Combine the list of values with their corresponding labels lst = list(chain.from_iterable(zip(names, vals))) lst = map(str, lst) # Create one large string for later plotting use joined = ' '.join(lst) return joined
def make_skewt(): # Get the data date = request.args.get('date') time = request.args.get('time') region = request.args.get('region') station = request.args.get('station') date = datetime.strptime(date, '%Y%m%d') date = datetime(date.year, date.month, date.day, int(time)) df = get_sounding_data(date, region, station) p = df['pressure'].values * units(df.units['pressure']) T = df['temperature'].values * units(df.units['temperature']) Td = df['dewpoint'].values * units(df.units['dewpoint']) u = df['u_wind'].values * units(df.units['u_wind']) v = df['v_wind'].values * units(df.units['v_wind']) # Make the Skew-T fig = plt.figure(figsize=(9, 9)) add_metpy_logo(fig, 115, 100) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'tab:red') skew.plot(p, Td, 'tab:green') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') skew.plot(p, prof, 'k', linewidth=2) # Shade areas of CAPE and CIN skew.shade_cin(p, T, prof) skew.shade_cape(p, T, prof) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() canvas = FigureCanvas(fig) img = BytesIO() fig.savefig(img) img.seek(0) return send_file(img, mimetype='image/png')
def plot_skewt(self, station_data): """ :param adjusted_data: receives the post processed dataframe :param valid: :return: """ # We will pull the data out of the example dataset into individual variables # and assign units. p = station_data['pressure'].values * units.hPa T = station_data['Temperature_isobaric'].values * units.degC Td = station_data['Dewpoint'].replace(np.nan, 0.0000001).values * units.degC u = station_data['u-component_of_wind_isobaric'].values * \ units('meters / second').to('knots') v = station_data['v-component_of_wind_isobaric'].values * \ units('meters / second').to('knots') # Create a new figure. The dimensions here give a good aspect ratio. fig = plt.figure(figsize=(12, 9)) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1020, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(p, T[0], Td[0]) skew.plot(p, prof, 'k', linewidth=2) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() skew.shade_cape(p, T, prof) skew.shade_cin(p, T, prof) return skew
def core(p, T, Td, u, v, **kwargs): # Calculate the LCL lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) #print('LCL p, t:', int(lcl_pressure), int(lcl_temperature)) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(8, 8)) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot #skew.plot(p, T, 'k-') skew.plot(p, T, 'r.-', ms=5, lw=2, label='mean T') skew.plot(p, Td, 'g.-', ms=5, lw=2, label='mean Td') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 180) skew.ax.set_xlim(-20, 40) # Plot LCL temperature as black dot skew.plot(lcl_pressure, lcl_temperature, 'k.', markerfacecolor='black') # Plot the parcel profile as a black line skew.plot(p, parcel_prof, 'k', linewidth=2) # Shade areas of CAPE and CIN skew.shade_cin(p, T, parcel_prof) skew.shade_cape(p, T, parcel_prof) # Plot a zero degree isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats(lw=.5) skew.plot_moist_adiabats(lw=.5) skew.plot_mixing_lines(lw=.5) # Show the plot #plt.show() #skew.ax.set_title(time_str) plt.legend(loc='lower left') plt.title(kwargs.get('title')) fname = kwargs.get('saveto', 'profile.png') fig.savefig(fname) print(fname, 'saved.') plt.close()
def plot_skewt(df): # We will pull the data out of the example dataset into individual variables # and assign units. hght = df['height'].values * units.hPa p = df['pressure'].values * units.hPa T = df['temperature'].values * units.degC Td = df['dewpoint'].values * units.degC wind_speed = df['speed'].values * units.knots wind_dir = df['direction'].values * units.degrees u, v = mpcalc.wind_components(wind_speed, wind_dir) # Create a new figure. The dimensions here give a good aspect ratio. fig = plt.figure(figsize=(9, 12)) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') skew.plot(p, prof, 'k', linewidth=2) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() # Create a hodograph ax_hod = inset_axes(skew.ax, '40%', '40%', loc=2) h = Hodograph(ax_hod, component_range=80.) h.add_grid(increment=20) h.plot_colormapped(u, v, hght) return skew
def plot_skewt(p, t, td, puv=None, u=None, v=None, title=None, outfile=None): # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=30) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, t, 'r', linewidth=2) skew.plot(p, td, 'g', linewidth=2) if u is not None and v is not None: skew.plot_barbs(puv, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate the LCL lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], t[0], td[0]) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, t[0], td[0]).to('degC') # Plot LCL temperature as black dot skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Plot the parcel profile as a black line skew.plot(p, parcel_prof, 'k--', linewidth=1) # Shade areas of CAPE and CIN skew.shade_cin(p, t, parcel_prof) skew.shade_cape(p, t, parcel_prof) # Plot a zero degree isotherm #skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() if title is not None: plt.title(title) # Show the plot #plt.show() if outfile is None: outfile = 'skewt.png' fig.savefig(outfile, format='png')
def LCL_metpy(pres, temp, dp): """ Inputs are pressure (hPa), temperature (degC), and dew point (degC) """ # pre-allocate len_time = len(pres.launch_time) lcl_p = [None] * len_time lcl_t = [None] * len_time lcl_ht = [None] * len_time pres_avg = np.zeros(len_time) T_avg = np.zeros(len_time) dp_avg = np.zeros(len_time) for i in range(len_time): # input: sonde mean-value from 60-210 m for pressure, temp, and dew point temp pres_avg[i] = pres[i, 5:20].mean(dim="alt", skipna=True) T_avg[i] = temp[i, 5:20].mean(dim="alt", skipna=True) dp_avg[i] = dp[i, 5:20].mean(dim="alt", skipna=True) # break loop and restart if NaN encountered if np.isnan(T_avg[i]) == True: continue # lcl_p and lcl_t are the values of pressure (hPa) and temperature (K) at LCL, respectively lcl_p[i], lcl_t[i] = (mpcalc.lcl(pres_avg[i] * units.hPa, T_avg[i] * units.degC, dp_avg[i] * units.degC, max_iters=200)) lcl_p[i] = lcl_p[i].magnitude lcl_t[i] = lcl_t[i].magnitude # find height where PLCL- 10hPa < P < PLCL + 10hPa altitude = dp['alt'] lcl_ht[i] = altitude.where((pres[i]<lcl_p[i]+10) & \ (pres[i]>lcl_p[i] - 10)).mean().values # convert None to NaN lcl_ht = np.array(lcl_ht, dtype=float) return lcl_ht
def calculate_sounding_data(df,field_height,field_temp): ###################################### CALCULATION MAGIC ################################################# # We will pull the data out of the latest soudning into individual variables and assign units. # # This will Return a dictionary with all the vallculate values. Keys are: # # "pressure", "temperature", "dewpoint", "height", "windspeed","wind_dir" ###################################### CALCULATION MAGIC ################################################# cache = settings.AppCache #Retrieves Sounding Details and returns them key='calculation'+str(field_height)+'_'+str(field_temp) #If soundings are cached and valid, returns the cache, otherwise retreive new sounding calc = cache.get(key) if calc is not None: logging.info("Calculation Cache Hit") print("Calculation Cahce Hit") return calc logging.info("Calculation Cache Miss") print("Calculation Cahce Miss") p = df['pressure'].values * units.hPa T = df['temperature'].values * units.degC Td = df['dewpoint'].values * units.degC alt = df['height'].values * units.ft wind_speed = df['speed'].values * units.knots wind_dir = df['direction'].values * units.degrees wet_bulb = mpcalc.wet_bulb_temperature(p,T,Td) field_pressure = mpcalc.height_to_pressure_std(field_height*units.ft) adiabat_line = mpcalc.dry_lapse(p,field_temp*units.degC,ref_pressure=mpcalc.height_to_pressure_std(field_height*units.ft)) #Interpolate Missing Values using linear interpolation Td_linear = interp1d(alt.magnitude,Td.magnitude) T_linear = interp1d(alt.magnitude,T.magnitude) #Calculate the LCL Based on Max Temperature lcl_pressure, lcl_temperature = mpcalc.lcl(field_pressure,field_temp*units.degC ,Td_linear(field_height)*units.degC) #parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') calc = MeteoCast.UpperAtmData(df, p, T, Td,alt,wind_speed,wind_dir,wet_bulb,field_pressure,lcl_pressure,lcl_temperature,adiabat_line) cache.set(key, calc, expire=600,tag='Calculation Data ') return calc
u,v = get_wind_components(spd, direc) # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot l = lcl(p[0], C2K(T[0]), C2K(Td[0])) skew.plot(l, K2C(dry_lapse(l, C2K(T[0]), p[0])), 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = K2C(parcel_profile(p, C2K(T[0]), C2K(Td[0]))) skew.plot(p, prof, 'k', linewidth=2) # Example of coloring area between profiles skew.ax.fill_betweenx(p, T, prof, where=T>=prof, facecolor='blue', alpha=0.4) skew.ax.fill_betweenx(p, T, prof, where=T<prof, facecolor='red', alpha=0.4) # An example of a slanted line at constant T -- in this case the 0 # isotherm l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
def EnergyMassPlot(pressure, temperature, dewpoint, height, uwind, vwind, sphum=None, rh=None, label='', size=(12,10), return_fig=False): p=pressure Z=height T=temperature Td=dewpoint if isinstance(sphum,np.ndarray) and isinstance(rh,np.ndarray): q=sphum qs=q/rh else: q = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Td),p) qs= mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T),p) s = g*Z + Cp_d*T sv= g*Z + Cp_d*mpcalc.virtual_temperature(T,q) h = s + Lv*q hs= s + Lv*qs parcel_Tprofile = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') CAPE,CIN = mpcalc.cape_cin(p,T,Td,parcel_Tprofile) ELp,ELT = mpcalc.el(p,T,Td) ELindex = np.argmin(np.abs(p - ELp)) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) p_PWtop = max(200*units.mbar, min(p) +1*units.mbar) PW = mpcalc.precipitable_water(Td,p, top=p_PWtop) PWs = mpcalc.precipitable_water(T,p, top=p_PWtop) CRH = (PW/PWs).magnitude *100. fig,ax=setup_fig(size=size,label=label) ax.plot(s /1000, p, color='r', linewidth=1.5) ### /1000 for kJ/kg ax.plot(sv /1000, p, color='r', linestyle='-.') ax.plot(h /1000, p, color='b', linewidth=1.5) ax.plot(hs /1000, p, color='r', linewidth=1.5) ### RH rulings between s and h lines: annotate near 800 hPa level annot_level = 800 #hPa idx = np.argmin(np.abs(p - annot_level *units.hPa)) right_annot_loc = 380 for iRH in np.arange(10,100,10): ax.plot( (s+ Lv*qs*iRH/100.)/1000, p, linewidth=0.5, linestyle=':', color='k') ax.annotate(str(iRH), xy=( (s[idx]+Lv*qs[idx]*iRH/100.)/1000, annot_level), horizontalalignment='center',fontsize=6) ax.annotate('RH (%)', xy=(right_annot_loc, annot_level), fontsize=10) if not np.isnan(CAPE.magnitude) and CAPE.magnitude >10: parcelh = h [0] # for a layer mean: np.mean(h[idx1:idx2]) parcelsv = sv[0] parcelp0 = p[0] # Undilute parcel ax.plot( (1,1)*parcelh/1000., (1,0)*parcelp0, linewidth=0.5, color='g') maxbindex = np.argmax(parcel_Tprofile - T) ax.annotate('CAPE='+str(int(CAPE.magnitude)), xy=(parcelh/1000., p[maxbindex]), color='g') # Plot LCL at saturation point, above the lifted sv of the surface parcel lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) ax.annotate('LCL', xy=(sv[0]/1000., lcl_pressure), fontsize=10, color='g', horizontalalignment='right') # Purple fill for negative buoyancy below LCL: ax.fill_betweenx(p, sv/1000., parcelsv/1000., where=p>lcl_pressure, facecolor='purple', alpha=0.4) # Positive moist convective buoyancy in green # Above LCL: ax.fill_betweenx(p, hs/1000., parcelh/1000., where= parcelh>hs, facecolor='g', alpha=0.4) # Depict Entraining parcels # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z) # M=1 at ground without loss of generality entrainment_distance = 10000., 5000., 2000. ax.annotate('entrain: 10,5,2 km', xy=(parcelh/1000, 140), color='g', fontsize=8, horizontalalignment='right') ax.annotate('parcel h', xy=(parcelh/1000, 120), color='g', fontsize=10, horizontalalignment='right') for ED in entrainment_distance: eps = 1.0 / (ED*units.meter) M = np.exp(eps * (Z-Z[0]).to('m')).to('dimensionless') # dM is the mass contribution at each level, with 1 at the origin level. M[0] = 0 dM = np.gradient(M) # parcel mass is a sum of all the dM's at each level # conserved linearly-mixed variables like h are weighted averages hent = np.cumsum(dM*h) / np.cumsum(dM) ax.plot( hent[0:ELindex+3]/1000., p[0:ELindex+3], linewidth=0.5, color='g') ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h). dZ = 100 *units.meter # depict displacements at sounding levels nearest these target levels targetlevels = [900,800,700,600,500,400,300,200]*units.hPa for ilev in targetlevels: idx = np.argmin(np.abs(p - ilev)) # dT: Dry lapse rate dT/dz_dry is -g/Cp dT = (-g/Cp_d *dZ).to('kelvin') Tdisp = T[idx].to('kelvin') + dT # dp: hydrostatic rho = (p[idx]/Rd/T[idx]) dp = -rho*g*dZ # dhsat #qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T) ,p) dqs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Tdisp) ,p[idx]+dp) -qs[idx] dhs = g*dZ + Cp_d*dT + Lv*dqs # Whiskers on the data plots ax.plot( (hs[idx]+dhs*(-1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.plot( (s [idx] *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.plot( (h [idx] *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='b') # annotation to explain it if ilev == 600*ilev.units: ax.plot(right_annot_loc*hs.units +dhs*(-1,1)/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.annotate('+/- 100m', xy=(right_annot_loc,600), fontsize=8) ax.annotate(' internal', xy=(right_annot_loc,630), fontsize=8) ax.annotate(' waves', xy=(right_annot_loc,660), fontsize=8) ### Blue fill proportional to precipitable water, and blue annotation ax.fill_betweenx(p, s/1000., h/1000., where=h > s, facecolor='b', alpha=0.4) # Have to specify the top of the PW integral. # I want whole atmosphere of course, but 200 hPa captures it all really. #import metpy.calc as mpcalc p_PWtop = max(200*units.mbar, min(p) +1*units.mbar) PW = mpcalc.precipitable_water(Td,p, top=p_PWtop) PWs = mpcalc.precipitable_water(T,p, top=p_PWtop) CRH = (PW/PWs).magnitude *100. # PW annotation arrow tip at 700 mb idx = np.argmin(np.abs(p - 700*p.units)) centerblue = (s[idx]+h[idx])/2.0 /1000. ax.annotate('CWV='+str(round(PW.to('mm').magnitude, 1))+'mm', xy=(centerblue, 700), xytext=(285, 200), color='blue', fontsize=15, arrowprops=dict(width = 1, edgecolor='blue', shrink=0.02), ) ax.annotate('(' + str(round(CRH,1)) +'% of sat)', xy=(285, 230), color='blue', fontsize=12) ### Surface water values at 1C intervals, for eyeballing surface fluxes sT = np.trunc(T[0].to('degC')) sTint = int(sT.magnitude) for idT in [-2,0,2,4]: ssTint = sTint + idT # UNITLESS degC integers, for labels # Kelvin values for computations ssTC = ssTint * units.degC ssTK = ssTC.to('kelvin') ss = g*Z[0] + Cp_d*ssTK hs = ss + Lv*mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(ssTK) ,p[0]) ax.annotate(str(ssTint), xy=(ss/1000., p[0]+0*p.units), verticalalignment='top', horizontalalignment='center', color='red', fontsize=7) ax.annotate(str(ssTint), xy=(hs/1000., p[0]+0*p.units), verticalalignment='top', horizontalalignment='center', color='red', fontsize=9) ax.annotate('\u00b0C water', xy=(right_annot_loc, p[0]), verticalalignment='top', fontsize=10, color='r') if return_fig: return ax,fig
def plotter(self, skew): boundary = self.plotMeta["boundary"] linestyle = self.plotMeta["linestyle"] linestyle["dry_adiabats"]["t0"] = np.arange(-100, 300, 10) * units.celsius self.fs = 16 ## basic vars P, T, Td, U, V = self.read() ## LCL LCL_parcel_idx = 0 LCL_P, LCL_T = mcalc.lcl(P[LCL_parcel_idx], T[LCL_parcel_idx], Td[LCL_parcel_idx]) ## shift profile profP = P profT = T profTd = Td ## calc # LFC_P, LFC_T = mcalc.lfc(P, T, Td) while (True): try: parcel_prof = mcalc.parcel_profile(profP, T[0], Td[0]).to('degC') break except: profP = profP[1:] profT = profT[1:] profTd = profTd[1:] finally: if (len(profP) == 0): break ##special lines skew.ax.set_xticks( np.arange(boundary["T"][0], boundary["T"][1], boundary["T"][2])) skew.plot_dry_adiabats(**linestyle["dry_adiabats"]) skew.plot_moist_adiabats(**linestyle["moist_adiabats"]) skew.plot_mixing_lines(p=[1000, 500] * units.hPa, **linestyle["mixing_lines"]) skew.ax.set_xlabel('[$\degree C$]', fontsize=self.fs) skew.ax.set_ylabel('[$hPa$]', fontsize=self.fs) skew.ax.set_xlim(-40, 40) skew.ax.set_ylim(boundary["P"][0], boundary["P"][1]) for i in np.arange(boundary["T"][0], boundary["T"][1], boundary["T"][2] * 2): plt.fill_between(range(i, i + boundary["T"][2] + 1), boundary["P"][0], boundary["P"][1], color='yellow', alpha=0.7) ## main data skew.plot(P, T, 'b') skew.plot(P, Td, 'r') ### wind bar # idx = mcalc.resample_nn_1d(P.m, np.array([1000, 975, 950, 925, 900, 850, 800, 750, 700, 650, 600, 500])) # skew.plot_barbs(P[idx], U[idx], V[idx], plot_units = units('knots'), xloc=1.05) skip = self.plotMeta["wind"]["skip"] skew.plot_barbs(P[P.m >= 100][::skip], U[P.m >= 100][::skip], V[P.m >= 100][::skip], plot_units=units('m/s'), xloc=1.05) skew.plot(LCL_P, LCL_T, 'ko', markerfacecolor='black') skew.plot(profP, parcel_prof, 'k', linewidth=2)
def scalardata(field, valid_time, targetdir=".", debug=False): # Get color map, levels, and netCDF variable name appropriate for requested variable (from fieldinfo module). info = fieldinfo[field] if debug: print("scalardata: found", field, "fieldinfo:", info) cmap = colors.ListedColormap(info['cmap']) levels = info['levels'] fvar = info['fname'][0] # Get narr file and filename. ifile = get(valid_time, targetdir=targetdir, narrtype=info['filename']) if debug: print("About to open " + ifile) nc = xarray.open_dataset(ifile) # Tried to rename vars and dimensions so metpy.parse_cf() would not warn "Found latitude/longitude values, assuming latitude_longitude for projection grid_mapping variable" # It didn't help. Only commenting out the metpy.parse_cf() line helped. # It didn't help with MetpyDeprecationWarning: Multidimensional coordinate lat assigned for axis "y". This behavior has been deprecated and will be removed in v1.0 (only one-dimensional coordinates will be available for the "y" axis) either #nc = nc.rename_vars({"gridlat_221": "lat", "gridlon_221" : "lon"}) #nc = nc.rename_dims({"gridx_221": "x", "gridy_221" : "y"}) #nc = nc.metpy.parse_cf() # TODO: figure out why filled contour didn't have .metpy.parse_cf() if fvar not in nc.variables: print(fvar, "not in", ifile, '. Try', nc.var()) sys.exit(1) # Define data array. Speed and shear derived differently. # Define 'long_name' attribute # if field[0:5] == "speed": u = nc[info['fname'][0]] v = nc[info['fname'][1]] data = u # copy metadata/coordinates from u data.values = wind_speed(u, v) data.attrs['long_name'] = "wind speed" elif field[0:3] == 'shr' and '_' in field: du, dv = shear(field, valid_time=valid_time, targetdir=targetdir, debug=debug) ws = wind_speed(du, dv) attrs = { 'long_name': 'wind shear', 'units': str(ws.units), 'verttitle': du.attrs["verttitle"] } # Use .m magnitude because you can't transfer units of pint quantity to xarray numpy array (xarray.values) data = xarray.DataArray(data=ws.m, dims=du.dims, coords=du.coords, name=field, attrs=attrs) elif field == 'theta2': pres = nc[info['fname'][0]] temp = nc[info['fname'][1]] data = pres # retain xarray metadata/coordinates theta = potential_temperature(pres, temp) data.values = theta data.attrs['units'] = str(theta.units) data.attrs['long_name'] = 'potential temperature' elif field == 'thetae2': pres = nc[info['fname'][0]] temp = nc[info['fname'][1]] dwpt = nc[info['fname'][2]] data = pres # retain xarray metadata/coordinates thetae = equivalent_potential_temperature(pres, temp, dwpt) data.values = thetae data.attrs['units'] = str(thetae.units) data.attrs['long_name'] = 'equivalent potential temperature' elif field == 'scp' or field == 'stp' or field == 'tctp': cape = nc[info['fname'][0]] cin = nc[info['fname'][1]] ifile = get(valid_time, targetdir=targetdir, narrtype=narrFlx) ncFlx = xarray.open_dataset(ifile).metpy.parse_cf() srh = ncFlx[info['fname'][2]] shear_layer = info['fname'][3] bulk_shear = scalardata(shear_layer, valid_time, targetdir=targetdir, debug=debug) lifted_condensation_level_height = scalardata('zlcl', valid_time, targetdir=targetdir, debug=debug) if field == 'scp': # In SPC help, cin is positive in SCP formulation. cin_term = -40 / cin cin_term = cin_term.where(cin < -40, other=1) scp = supercell_composite(cape, srh, bulk_shear) * cin_term.metpy.unit_array attrs = { 'units': str(scp.units), 'long_name': 'supercell composite parameter' } data = xarray.DataArray(data=scp, dims=cape.dims, coords=cape.coords, name=field, attrs=attrs) if field == 'stp': cin_term = (200 + cin) / 150 cin_term = cin_term.where(cin <= -50, other=1) cin_term = cin_term.where(cin >= -200, other=0) # CAPE, srh, bulk_shear, cin may be one vertical level, but LCL may be multiple heights. # xarray.broadcast() makes them all multiple heights with same shape, so significant_tornado doesn't # complain about expecting lat/lon 2 dimensions and getting 3 dimensions.. (cape, lifted_condensation_level_height, srh, bulk_shear, cin_term) = xarray.broadcast(cape, lifted_condensation_level_height, srh, bulk_shear, cin_term) stp = significant_tornado(cape, lifted_condensation_level_height, srh, bulk_shear) * cin_term.metpy.unit_array attrs = { 'units': str(stp.units), 'long_name': 'significant tornado parameter', 'verttitle': lifted_condensation_level_height.attrs['verttitle'] } data = xarray.DataArray(data=stp, dims=cape.dims, coords=cape.coords, name=field, attrs=attrs) if field == 'tctp': tctp = srh / (40 * munits['m**2/s**2']) * bulk_shear / ( 12 * munits['m/s']) * (2000 - lifted_condensation_level_height ) / (1400 * munits.m) # But NARR storm relative helicity (srh) is 0-3 km AGL, while original TCTP expects 0-1 km AGL. # So the shear term is too large using the NARR srh. Normalize the srh term with a larger denominator. # In STP, srh is normalized by 150 m**2/s**2. Use that. tctp_0_3kmsrh = srh / (150 * munits['m**2/s**2']) * bulk_shear / ( 12 * munits['m/s']) * (2000 - lifted_condensation_level_height ) / (1400 * munits.m) attrs = { 'units': 'dimensionless', 'long_name': 'TC tornado parameter' } data = xarray.DataArray(data=tctp_0_3kmsrh, dims=cape.dims, coords=cape.coords, name=field, attrs=attrs) elif field == 'lcl': pres = nc[info['fname'][0]] temp = nc[info['fname'][1]] dwpt = nc[info['fname'][2]] LCL_pressure, LCL_temperature = lcl(pres.fillna(pres.mean()), temp.fillna(temp.mean()), dwpt.fillna(dwpt.mean())) # convert units to string or xarray.DataArray.metpy.unit_array dies with ttributeError: 'NoneType' object has no attribute 'evaluate' attrs = { "long_name": "lifted condensation level", "units": str(LCL_pressure.units), "from": "metpy.calc.lcl" } data = xarray.DataArray(data=LCL_pressure, coords=pres.coords, dims=pres.dims, name='LCL', attrs=attrs) elif field == 'zlcl': LCL_pressure = scalardata('lcl', valid_time, targetdir=targetdir, debug=debug) ifile = get(valid_time, targetdir=targetdir, narrtype=narr3D) nc3D = xarray.open_dataset(ifile).metpy.parse_cf() hgt3D = nc3D["HGT_221_ISBL"] data = pressure_to_height(LCL_pressure, hgt3D, targetdir=targetdir) else: data = nc[fvar] data = units(data, info, debug=debug) data = vertical(data, info, debug=debug) data = temporal(data, info, debug=debug) data.attrs['field'] = field data.attrs['ifile'] = os.path.realpath(ifile) data.attrs['levels'] = levels data.attrs['cmap'] = cmap if data.min() > levels[-1] or data.max() < levels[0]: print('levels', levels, 'out of range of data', data.min(), data.max()) sys.exit(2) return data
########################################################################## # Thermodynamic Calculations # -------------------------- # # Often times we will want to calculate some thermodynamic parameters of a # sounding. The MetPy calc module has many such calculations already implemented! # # * **Lifting Condensation Level (LCL)** - The level at which an air parcel's # relative humidity becomes 100% when lifted along a dry adiabatic path. # * **Parcel Path** - Path followed by a hypothetical parcel of air, beginning # at the surface temperature/pressure and rising dry adiabatically until # reaching the LCL, then rising moist adiabatically. # Calculate the LCL lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) print(lcl_pressure, lcl_temperature) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') ########################################################################## # Skew-T Plotting # ------------------------ # # Fiducial lines indicating dry adiabats, moist adiabats, and mixing ratio are # useful when performing further analysis on the Skew-T diagram. Often the # 0C isotherm is emphasized and areas of CAPE and CIN are shaded. # Create a new figure. The dimensions here give a good aspect ratio
def test_lcl(): """Test LCL calculation.""" lcl_pressure, lcl_temperature = lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC) assert_almost_equal(lcl_pressure, 864.761 * units.mbar, 2) assert_almost_equal(lcl_temperature, 17.676 * units.degC, 2)
def plot_from_u_and_v( self, u_field, v_field, p_field, t_field, td_field, dsname=None, subplot_index=(0, ), p_levels_to_plot=None, show_parcel=True, shade_cape=True, shade_cin=True, set_title=None, plot_barbs_kwargs=dict(), plot_kwargs=dict(), ): """ This function will plot a Skew-T from a sounding dataset. The wind data must be given in u and v. Parameters ---------- u_field: str The name of the field containing the u component of the wind. v_field: str The name of the field containing the v component of the wind. p_field: str The name of the field containing the pressure. t_field: str The name of the field containing the temperature. td_field: str The name of the field containing the dewpoint temperature. dsname: str or None The name of the datastream to plot. Set to None to make ACT attempt to automatically determine this. subplot_index: tuple The index of the subplot to make the plot on. p_levels_to_plot: 1D array The pressure levels to plot the wind barbs on. Set to None to have ACT to use neatly spaced defaults of 50, 100, 200, 300, 400, 500, 600, 700, 750, 800, 850, 900, 950, and 1000 hPa. show_parcel: bool Set to True to show the temperature of a parcel lifted from the surface. shade_cape: bool Set to True to shade the CAPE red. shade_cin: bool Set to True to shade the CIN blue. set_title: None or str The title of the plot is set to this. Set to None to use a default title. plot_barbs_kwargs: dict Additional keyword arguments to pass into MetPy's SkewT.plot_barbs. plot_kwargs: dict Additional keyword arguments to pass into MetPy's SkewT.plot. Returns ------- ax: matplotlib axis handle The axis handle to the plot. """ if dsname is None and len(self._arm.keys()) > 1: raise ValueError(("You must choose a datastream when there are 2 " "or more datasets in the TimeSeriesDisplay " "object.")) elif dsname is None: dsname = list(self._arm.keys())[0] if p_levels_to_plot is None: p_levels_to_plot = np.array([ 50., 100., 200., 300., 400., 500., 600., 700., 750., 800., 850., 900., 950., 1000. ]) T = self._arm[dsname][t_field] T_units = self._arm[dsname][t_field].attrs["units"] if T_units == "C": T_units = "degC" T = T.values * getattr(units, T_units) Td = self._arm[dsname][td_field] Td_units = self._arm[dsname][td_field].attrs["units"] if Td_units == "C": Td_units = "degC" Td = Td.values * getattr(units, Td_units) u = self._arm[dsname][u_field] u_units = self._arm[dsname][u_field].attrs["units"] u = u.values * getattr(units, u_units) v = self._arm[dsname][v_field] v_units = self._arm[dsname][v_field].attrs["units"] v = v.values * getattr(units, v_units) p = self._arm[dsname][p_field] p_units = self._arm[dsname][p_field].attrs["units"] p = p.values * getattr(units, p_units) u_red = np.zeros_like(p_levels_to_plot) * getattr(units, u_units) v_red = np.zeros_like(p_levels_to_plot) * getattr(units, v_units) p_levels_to_plot = p_levels_to_plot * getattr(units, p_units) for i in range(len(p_levels_to_plot)): index = np.argmin(np.abs(p_levels_to_plot[i] - p)) u_red[i] = u[index].magnitude * getattr(units, u_units) v_red[i] = v[index].magnitude * getattr(units, v_units) self.SkewT[subplot_index].plot(p, T, 'r', **plot_kwargs) self.SkewT[subplot_index].plot(p, Td, 'g', **plot_kwargs) self.SkewT[subplot_index].plot_barbs(p_levels_to_plot, u_red, v_red, **plot_barbs_kwargs) prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') if show_parcel: # Only plot where prof > T lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) self.SkewT[subplot_index].plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black', **plot_kwargs) self.SkewT[subplot_index].plot(p, prof, 'k', linewidth=2, **plot_kwargs) if shade_cape: self.SkewT[subplot_index].shade_cape(p, T, prof, linewidth=2) if shade_cin: self.SkewT[subplot_index].shade_cin(p, T, prof, linewidth=2) # Set Title if set_title is None: set_title = ' '.join([ dsname, 'on', dt_utils.numpy_to_arm_date(self._arm[dsname].time.values[0]) ]) self.axes[subplot_index].set_title(set_title) # Set Y Limit if hasattr(self, 'yrng'): # Make sure that the yrng is not just the default if not np.all(self.yrng[subplot_index] == 0): self.set_yrng(self.yrng[subplot_index], subplot_index) else: our_data = p.magnitude if np.isfinite(our_data).any(): yrng = [np.nanmax(our_data), np.nanmin(our_data)] else: yrng = [1000., 100.] self.set_yrng(yrng, subplot_index) # Set X Limit xrng = [T.magnitude.min() - 10., T.magnitude.max() + 10.] self.set_xrng(xrng, subplot_index) return self.axes[subplot_index]
########################################################################## # Thermodynamic Calculations # -------------------------- # # Often times we will want to calculate some thermodynamic parameters of a # sounding. The MetPy calc module has many such calculations already implemented! # # * **Lifting Condensation Level (LCL)** - The level at which an air parcel's # relative humidity becomes 100% when lifted along a dry adiabatic path. # * **Parcel Path** - Path followed by a hypothetical parcel of air, beginning # at the surface temperature/pressure and rising dry adiabatically until # reaching the LCL, then rising moist adiabatially. # Calculate the LCL lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) print(lcl_pressure, lcl_temperature) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') ########################################################################## # Basic Skew-T Plotting # --------------------- # # The Skew-T (log-P) diagram is the standard way to view rawinsonde data. The # y-axis is height in pressure coordinates and the x-axis is temperature. The # y coordinates are plotted on a logarithmic scale and the x coordinate system # is skewed. An explanation of skew-T interpretation is beyond the scope of this # tutorial, but here we will plot one that can be used for analysis or
########################################################################## # Thermodynamic Calculations # -------------------------- # # Often times we will want to calculate some thermodynamic parameters of a # sounding. The MetPy calc module has many such calculations already implemented! # # * **Lifting Condensation Level (LCL)** - The level at which an air parcel's # relative humidity becomes 100% when lifted along a dry adiabatic path. # * **Parcel Path** - Path followed by a hypothetical parcel of air, beginning # at the surface temperature/pressure and rising dry adiabatically until # reaching the LCL, then rising moist adiabatially. # Calculate the LCL level parcel_lcl = mpcalc.lcl(p[0], T[0], Td[0]) # Determine the LCL temperature by lifting a parcel from the surface # pressure and temperature via a dry adiabatic process. lcl_temperature = mpcalc.dry_lapse(concatenate((p[0], parcel_lcl)), T[0])[-1].to('degC') print(parcel_lcl, lcl_temperature) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') ########################################################################## # Basic Skew-T Plotting # --------------------- #
def draw_sta_skewT(p=None, T=None, Td=None, wind_speed=None, wind_dir=None, u=None, v=None, fcst_info=None, output_dir=None): fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) plt.rcParams['font.sans-serif'] = ['SimHei'] # 步骤一(替换sans-serif字体) plt.rcParams['axes.unicode_minus'] = False # 步骤二(解决坐标轴负数的负号显示问题) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot. skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot. Because `p`'s first value is # ~1000 mb and its last value is ~250 mb, the `0` index is selected for # `p`, `T`, and `Td` to lift the parcel from the surface. If `p` was inverted, # i.e. start from low value, 250 mb, to a high value, 1000 mb, the `-1` index # should be selected. lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') skew.plot(p, prof, 'k', linewidth=2) # Shade areas of CAPE and CIN skew.shade_cin(p, T, prof) skew.shade_cape(p, T, prof) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() #forecast information bax = plt.axes([0.12, 0.88, .25, .07], facecolor='#FFFFFFCC') bax.axis('off') bax.axis([0, 10, 0, 10]) initTime = pd.to_datetime(str( fcst_info['forecast_reference_time'].values)).replace( tzinfo=None).to_pydatetime() if (sys.platform[0:3] == 'lin'): locale.setlocale(locale.LC_CTYPE, 'zh_CN.utf8') if (sys.platform[0:3] == 'win'): locale.setlocale(locale.LC_CTYPE, 'chinese') plt.text(2.5, 7.5, '起报时间: ' + initTime.strftime("%Y年%m月%d日%H时"), size=11) plt.text(2.5, 5.0, '[' + str(fcst_info.attrs['model']) + '] ' + str(int(fcst_info['forecast_period'].values[0])) + '小时预报探空', size=11) plt.text(2.5, 2.5, '预报点: ' + str(fcst_info.attrs['points']['lon']) + ', ' + str(fcst_info.attrs['points']['lat']), size=11) plt.text(2.5, 0.5, 'www.nmc.cn', size=11) utl.add_logo_extra_in_axes(pos=[0.1, 0.88, .07, .07], which='nmc', size='Xlarge') # Show the plot if (output_dir != None): plt.savefig( output_dir + '时间剖面产品_起报时间_' + str(fcst_info['forecast_reference_time'].values)[0:13] + '_预报时效_' + str(int(fcst_info.attrs['forecast_period'].values)) + '.png', dpi=200, bbox_inches='tight') else: plt.show()
def plot_upper_air(station='11035', date=False): ''' ----------------------------- Default use of plot_upper_air: This will plot a SkewT sounding for station '11035' (Wien Hohe Warte) plot_upper_air(station='11035', date=False) ''' # sns.set(rc={'axes.facecolor':'#343837', 'figure.facecolor':'#343837', # 'grid.linestyle':'','axes.labelcolor':'#04d8b2','text.color':'#04d8b2', # 'xtick.color':'#04d8b2','ytick.color':'#04d8b2'}) # Get time in UTC station = str(station) if date is False: now = datetime.utcnow() # If morning then 0z sounding, otherwise 12z if now.hour < 12: hour = 0 else: hour = 12 date = datetime(now.year, now.month, now.day, hour) datestr = date.strftime('%Hz %Y-%m-%d') print('{}'.format(date)) else: year = int(input('Please specify the year: ')) month = int(input('Please specify the month: ')) day = int(input('Please specify the day: ')) hour = int(input('Please specify the hour: ')) if hour < 12: hour = 0 else: hour = 12 date = datetime(year, month, day, hour) datestr = date.strftime('%Hz %Y-%m-%d') print('You entered {}'.format(date)) # This requests the data 11035 is df = WyomingUpperAir.request_data(date, station) # Create single variables wih the right units p = df['pressure'].values * units.hPa T = df['temperature'].values * units.degC Td = df['dewpoint'].values * units.degC wind_speed = df['speed'].values * units.knots wind_dir = df['direction'].values * units.degrees wind_speed_6k = df['speed'][df.height <= 6000].values * units.knots wind_dir_6k = df['direction'][df.height <= 6000].values * units.degrees u, v = mpcalc.get_wind_components(wind_speed, wind_dir) u6, v6 = mpcalc.get_wind_components(wind_speed_6k, wind_dir_6k) # Calculate the LCL lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) print(lcl_pressure, lcl_temperature) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') cape, cin = mpcalc.cape_cin(p, T, Td, parcel_prof) ############################# # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(9, 9)) gs = gridspec.GridSpec(3, 3) skew = SkewT(fig, rotation=45, subplot=gs[:, :2]) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-45, 40) # Plot LCL as black dot skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Plot the parcel profile as a black line skew.plot(p, parcel_prof, 'k', linewidth=2) # Shade areas of CAPE and CIN skew.shade_cin(p, T, parcel_prof) skew.shade_cape(p, T, parcel_prof) # Plot a zero degree isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) skew.ax.set_title('Station: ' + str(station) + '\n' + datestr) # set title skew.ax.set_xlabel('Temperature (C)') skew.ax.set_ylabel('Pressure (hPa)') # Add the relevant special lines skew.plot_dry_adiabats(linewidth=0.7) skew.plot_moist_adiabats(linewidth=0.7) skew.plot_mixing_lines(linewidth=0.7) # Create a hodograph # Create an inset axes object that is 40% width and height of the # figure and put it in the upper right hand corner. # ax_hod = inset_axes(skew.ax, '40%', '40%', loc=1) ax = fig.add_subplot(gs[0, -1]) h = Hodograph(ax, component_range=60.) h.add_grid(increment=20) # Plot a line colored by windspeed h.plot_colormapped(u6, v6, wind_speed_6k) # add another subplot for the text of the indices # ax_t = fig.add_subplot(gs[1:,2]) skew2 = SkewT(fig, rotation=0, subplot=gs[1:, 2]) skew2.plot(p, T, 'r') skew2.plot(p, Td, 'g') # skew2.plot_barbs(p, u, v) skew2.ax.set_ylim(1000, 700) skew2.ax.set_xlim(-30, 10) # Show the plot plt.show() return cape
def calculate_stability_indicies( ds, temp_name='temperature', td_name='dewpoint_temperature', p_name='pressure', rh_name='relative_humidity', moving_ave_window=0, ): """ Function for calculating stability indices from sounding data. Parameters ---------- ds : ACT dataset The dataset to compute the stability indicies of. Must have temperature, dewpoint, and pressure in vertical coordinates. temp_name : str The name of the temperature field. td_name : str The name of the dewpoint field. p_name : str The name of the pressure field. rh_name : str The name of the relative humidity field. moving_ave_window : int Number of points to do a moving average on sounding data to reduce noise. This is useful if noise in the sounding is preventing parcel ascent. Returns ------- ds : ACT dataset An ACT dataset with additional stability indicies added. """ if not METPY_AVAILABLE: raise ImportError( 'MetPy need to be installed on your system to ' + 'calculate stability indices' ) t = ds[temp_name] td = ds[td_name] p = ds[p_name] rh = ds[rh_name] if not hasattr(t, 'units'): raise AttributeError('Temperature field must have units' + ' for ACT to discern!') if not hasattr(td, 'units'): raise AttributeError('Dewpoint field must have units' + ' for ACT to discern!') if not hasattr(p, 'units'): raise AttributeError('Pressure field must have units' + ' for ACT to discern!') if t.units == 'C': t_units = units.degC else: t_units = getattr(units, t.units) if td.units == 'C': td_units = units.degC else: td_units = getattr(units, td.units) p_units = getattr(units, p.units) rh_units = getattr(units, rh.units) # Sort all values by decreasing pressure t_sorted = np.array(t.values) td_sorted = np.array(td.values) p_sorted = np.array(p.values) rh_sorted = np.array(rh.values) ind_sort = np.argsort(p_sorted) t_sorted = t_sorted[ind_sort[-1:0:-1]] td_sorted = td_sorted[ind_sort[-1:0:-1]] p_sorted = p_sorted[ind_sort[-1:0:-1]] rh_sorted = rh_sorted[ind_sort[-1:0:-1]] if moving_ave_window > 0: t_sorted = np.convolve(t_sorted, np.ones((moving_ave_window,)) / moving_ave_window) td_sorted = np.convolve(td_sorted, np.ones((moving_ave_window,)) / moving_ave_window) p_sorted = np.convolve(p_sorted, np.ones((moving_ave_window,)) / moving_ave_window) rh_sorted = np.convolve(rh_sorted, np.ones((moving_ave_window,)) / moving_ave_window) t_sorted = t_sorted * t_units td_sorted = td_sorted * td_units p_sorted = p_sorted * p_units rh_sorted = rh_sorted * rh_units # Calculate mixing ratio mr = mpcalc.mixing_ratio_from_relative_humidity(p_sorted, t_sorted, rh_sorted) # Discussion of issue #361 use virtual temperature. vt = mpcalc.virtual_temperature(t_sorted, mr) t_profile = mpcalc.parcel_profile(p_sorted, t_sorted[0], td_sorted[0]) # Calculate parcel trajectory ds['parcel_temperature'] = t_profile.magnitude ds['parcel_temperature'].attrs['units'] = t_profile.units # Calculate CAPE, CIN, LCL sbcape, sbcin = mpcalc.surface_based_cape_cin(p_sorted, vt, td_sorted) lcl = mpcalc.lcl(p_sorted[0], t_sorted[0], td_sorted[0]) try: lfc = mpcalc.lfc(p_sorted[0], t_sorted[0], td_sorted[0]) except IndexError: lfc = np.nan * p_sorted.units mucape, mucin = mpcalc.most_unstable_cape_cin(p_sorted, vt, td_sorted) where_500 = np.argmin(np.abs(p_sorted - 500 * units.hPa)) li = t_sorted[where_500] - t_profile[where_500] ds['surface_based_cape'] = sbcape.magnitude ds['surface_based_cape'].attrs['units'] = 'J/kg' ds['surface_based_cape'].attrs['long_name'] = 'Surface-based CAPE' ds['surface_based_cin'] = sbcin.magnitude ds['surface_based_cin'].attrs['units'] = 'J/kg' ds['surface_based_cin'].attrs['long_name'] = 'Surface-based CIN' ds['most_unstable_cape'] = mucape.magnitude ds['most_unstable_cape'].attrs['units'] = 'J/kg' ds['most_unstable_cape'].attrs['long_name'] = 'Most unstable CAPE' ds['most_unstable_cin'] = mucin.magnitude ds['most_unstable_cin'].attrs['units'] = 'J/kg' ds['most_unstable_cin'].attrs['long_name'] = 'Most unstable CIN' ds['lifted_index'] = li.magnitude ds['lifted_index'].attrs['units'] = t_profile.units ds['lifted_index'].attrs['long_name'] = 'Lifted index' ds['level_of_free_convection'] = lfc.magnitude ds['level_of_free_convection'].attrs['units'] = lfc.units ds['level_of_free_convection'].attrs['long_name'] = 'Level of free convection' ds['lifted_condensation_level_temperature'] = lcl[1].magnitude ds['lifted_condensation_level_temperature'].attrs['units'] = lcl[1].units ds['lifted_condensation_level_temperature'].attrs[ 'long_name' ] = 'Lifted condensation level temperature' ds['lifted_condensation_level_pressure'] = lcl[0].magnitude ds['lifted_condensation_level_pressure'].attrs['units'] = lcl[0].units ds['lifted_condensation_level_pressure'].attrs[ 'long_name' ] = 'Lifted condensation level pressure' return ds
def msed_plots(pressure, temperature, mixing_ratio, h0_std=2000, ensemble_size=20, ent_rate=np.arange(0, 2, 0.05), entrain=False): """ plotting the summarized static energy diagram with annotations and thermodynamic parameters """ p = pressure * units('mbar') T = temperature * units('degC') q = mixing_ratio * units('kilogram/kilogram') qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p) Td = mpcalc.dewpoint(mpcalc.vapor_pressure(p, q)) # dewpoint Tp = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') # parcel profile # Altitude based on the hydrostatic eq. altitude = np.zeros((np.size(T))) * units('meter') # surface is 0 meter for i in range(np.size(T)): altitude[i] = mpcalc.thickness_hydrostatic( p[:i + 1], T[:i + 1]) # Hypsometric Eq. for height # Static energy calculations mse = mpcalc.moist_static_energy(altitude, T, q) mse_s = mpcalc.moist_static_energy(altitude, T, qs) dse = mpcalc.dry_static_energy(altitude, T) # Water vapor calculations p_PWtop = max(200 * units.mbar, min(p) + 1 * units.mbar) # integrating until 200mb cwv = mpcalc.precipitable_water(Td, p, top=p_PWtop) # column water vapor [mm] cwvs = mpcalc.precipitable_water( T, p, top=p_PWtop) # saturated column water vapor [mm] crh = (cwv / cwvs) * 100. # column relative humidity [%] #================================================ # plotting MSE vertical profiles fig = plt.figure(figsize=[12, 8]) ax = fig.add_axes([0.1, 0.1, 0.6, 0.8]) ax.plot(dse, p, '-k', linewidth=2) ax.plot(mse, p, '-b', linewidth=2) ax.plot(mse_s, p, '-r', linewidth=2) # mse based on different percentages of relative humidity qr = np.zeros((9, np.size(qs))) * units('kilogram/kilogram') mse_r = qr * units('joule/kilogram') # container for i in range(9): qr[i, :] = qs * 0.1 * (i + 1) mse_r[i, :] = mpcalc.moist_static_energy(altitude, T, qr[i, :]) for i in range(9): ax.plot(mse_r[i, :], p[:], '-', color='grey', linewidth=0.7) ax.text(mse_r[i, 3].magnitude / 1000 - 1, p[3].magnitude, str((i + 1) * 10)) # drawing LCL and LFC levels [lcl_pressure, lcl_temperature] = mpcalc.lcl(p[0], T[0], Td[0]) lcl_idx = np.argmin(np.abs(p.magnitude - lcl_pressure.magnitude)) [lfc_pressure, lfc_temperature] = mpcalc.lfc(p, T, Td) lfc_idx = np.argmin(np.abs(p.magnitude - lfc_pressure.magnitude)) # conserved mse of air parcel arising from 1000 hpa mse_p = np.squeeze(np.ones((1, np.size(T))) * mse[0].magnitude) # illustration of CAPE el_pressure, el_temperature = mpcalc.el(p, T, Td) # equilibrium level el_idx = np.argmin(np.abs(p.magnitude - el_pressure.magnitude)) ELps = [el_pressure.magnitude ] # Initialize an array of EL pressures for detrainment profile [CAPE, CIN] = mpcalc.cape_cin(p[:el_idx], T[:el_idx], Td[:el_idx], Tp[:el_idx]) plt.plot(mse_p, p, color='green', linewidth=2) ax.fill_betweenx(p[lcl_idx:el_idx + 1], mse_p[lcl_idx:el_idx + 1], mse_s[lcl_idx:el_idx + 1], interpolate=True, color='green', alpha='0.3') ax.fill_betweenx(p, dse, mse, color='deepskyblue', alpha='0.5') ax.set_xlabel('Specific static energies: s, h, hs [kJ kg$^{-1}$]', fontsize=14) ax.set_ylabel('Pressure [hpa]', fontsize=14) ax.set_xticks([280, 300, 320, 340, 360, 380]) ax.set_xlim([280, 390]) ax.set_ylim(1030, 120) if entrain is True: # Depict Entraining parcels # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z) # M=1 at ground without loss of generality # Distribution of surface parcel h offsets H0STDEV = h0_std # J/kg h0offsets = np.sort(np.random.normal( 0, H0STDEV, ensemble_size)) * units('joule/kilogram') # Distribution of entrainment rates entrainment_rates = ent_rate / (units('km')) for h0offset in h0offsets: h4ent = mse.copy() h4ent[0] += h0offset for eps in entrainment_rates: M = np.exp(eps * (altitude - altitude[0])).to('dimensionless') # dM is the mass contribution at each level, with 1 at the origin level. M[0] = 0 dM = np.gradient(M) # parcel mass is a sum of all the dM's at each level # conserved linearly-mixed variables like h are weighted averages hent = np.cumsum(dM * h4ent) / np.cumsum(dM) # Boolean for positive buoyancy, and its topmost altitude (index) where curve is clippes posboy = (hent > mse_s) posboy[0] = True # so there is always a detrainment level ELindex_ent = np.max(np.where(posboy)) # Plot the curve plt.plot(hent[0:ELindex_ent + 2], p[0:ELindex_ent + 2], linewidth=0.25, color='g') # Keep a list for a histogram plot (detrainment profile) if p[ELindex_ent].magnitude < lfc_pressure.magnitude: # buoyant parcels only ELps.append(p[ELindex_ent].magnitude) # Plot a crude histogram of parcel detrainment levels NBINS = 20 pbins = np.linspace(1000, 150, num=NBINS) # pbins for detrainment levels hist = np.zeros((len(pbins) - 1)) for x in ELps: for i in range(len(pbins) - 1): if (x < pbins[i]) & (x >= pbins[i + 1]): hist[i] += 1 break det_per = hist / sum(hist) * 100 # percentages of detrainment ensumbles at levels ax2 = fig.add_axes([0.705, 0.1, 0.1, 0.8], facecolor=None) ax2.barh(pbins[1:], det_per, color='lightgrey', edgecolor='k', height=15 * (20 / NBINS)) ax2.set_xlim([0, max(det_per)]) ax2.set_ylim([1030, 120]) ax2.set_xlabel('Detrainment [%]') ax2.grid() ax2.set_zorder(2) ax.plot([400, 400], [1100, 0]) ax.annotate('Detrainment', xy=(362, 320), color='dimgrey') ax.annotate('ensemble: ' + str(ensemble_size * len(entrainment_rates)), xy=(364, 340), color='dimgrey') ax.annotate('Detrainment', xy=(362, 380), color='dimgrey') ax.annotate(' scale: 0 - 2 km', xy=(365, 400), color='dimgrey') # Overplots on the mess: undilute parcel and CAPE, etc. ax.plot((1, 1) * mse[0], (1, 0) * (p[0]), color='g', linewidth=2) # Replot the sounding on top of all that mess ax.plot(mse_s, p, color='r', linewidth=1.5) ax.plot(mse, p, color='b', linewidth=1.5) # label LCL and LCF ax.plot((mse_s[lcl_idx] + (-2000, 2000) * units('joule/kilogram')), lcl_pressure + (0, 0) * units('mbar'), color='orange', linewidth=3) ax.plot((mse_s[lfc_idx] + (-2000, 2000) * units('joule/kilogram')), lfc_pressure + (0, 0) * units('mbar'), color='magenta', linewidth=3) ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h). #dZ = 100 *mpunits.units.meter dp = 1000 * units.pascal # depict displacements at sounding levels nearest these target levels targetlevels = [900, 800, 700, 600, 500, 400, 300, 200] * units.hPa for ilev in targetlevels: idx = np.argmin(np.abs(p - ilev)) # dp: hydrostatic rho = (p[idx]) / Rd / (T[idx]) dZ = -dp / rho / g # dT: Dry lapse rate dT/dz_dry is -g/Cp dT = (-g / Cp_d * dZ).to('kelvin') Tdisp = T[idx].to('kelvin') + dT # dhsat dqs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Tdisp), p[idx] + dp) - qs[idx] dhs = g * dZ + Cp_d * dT + Lv * dqs # Whiskers on the data plots ax.plot((mse_s[idx] + dhs * (-1, 1)), p[idx] + dp * (-1, 1), linewidth=3, color='r') ax.plot((dse[idx] * (1, 1)), p[idx] + dp * (-1, 1), linewidth=3, color='k') ax.plot((mse[idx] * (1, 1)), p[idx] + dp * (-1, 1), linewidth=3, color='b') # annotation to explain it if ilev == 400 * ilev.units: ax.plot(360 * mse_s.units + dhs * (-1, 1) / 1000, 440 * units('mbar') + dp * (-1, 1), linewidth=3, color='r') ax.annotate('+/- 10mb', xy=(362, 440), fontsize=8) ax.annotate(' adiabatic displacement', xy=(362, 460), fontsize=8) # Plot a crude histogram of parcel detrainment levels # Text parts ax.text(290, pressure[3], 'RH (%)', fontsize=11, color='k') ax.text(285, 200, 'CAPE = ' + str(np.around(CAPE.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(285, 250, 'CIN = ' + str(np.around(CIN.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(285, 300, 'LCL = ' + str(np.around(lcl_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='darkorange') ax.text(285, 350, 'LFC = ' + str(np.around(lfc_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='magenta') ax.text(285, 400, 'CWV = ' + str(np.around(cwv.magnitude, decimals=2)) + ' [mm]', fontsize=12, color='deepskyblue') ax.text(285, 450, 'CRH = ' + str(np.around(crh.magnitude, decimals=2)) + ' [%]', fontsize=12, color='blue') ax.legend(['DSE', 'MSE', 'SMSE'], fontsize=12, loc=1) ax.set_zorder(3) return (ax)
def test_lcl(): """Test LCL calculation.""" l = lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC) assert_almost_equal(l, 864.761 * units.mbar, 2)
def plot(self, t, td, p, u, v, lat, long, time): r"""Displays the Skew-T data on a matplotlib figure. Args: t (array-like): A list of temperature values. td (array-like): A list of dewpoint values. p (array-like): A list of pressure values. u (array-like): A list of u-wind component values. v (array-like): A list of v-wind component values. lat (string): A string containing the requested latitude value. long (string): A string containing the requested longitude value. time (string): A string containing the UTC time requested with seconds truncated. Returns: None. Raises: None. """ # Create a new figure. The dimensions here give a good aspect ratio self.skew = SkewT(self.figure, rotation=40) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot self.skew.plot(p, t, 'r') self.skew.plot(p, td, 'g') self.skew.plot_barbs(p, u, v, barbcolor='#FF0000', flagcolor='#FF0000') self.skew.ax.set_ylim(1000, 100) self.skew.ax.set_xlim(-40, 60) # Axis colors self.skew.ax.tick_params(axis='x', colors='#A3A3A4') self.skew.ax.tick_params(axis='y', colors='#A3A3A4') # Calculate LCL height and plot as black dot l = lcl(p[0], t[0], td[0]) lcl_temp = dry_lapse(concatenate((p[0], l)), t[0])[-1].to('degC') self.skew.plot(l, lcl_temp, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = parcel_profile(p, t[0], td[0]).to('degC') self.skew.plot(p, prof, 'k', linewidth=2) # Color shade areas between profiles self.skew.ax.fill_betweenx(p, t, prof, where=t >= prof, facecolor='#5D8C53', alpha=0.7) self.skew.ax.fill_betweenx(p, t, prof, where=t < prof, facecolor='#CD6659', alpha=0.7) # Add the relevant special lines self.skew.plot_dry_adiabats() self.skew.plot_moist_adiabats() self.skew.plot_mixing_lines() # Set title deg = u'\N{DEGREE SIGN}' self.skew.ax.set_title('Sounding for ' + lat + deg + ', ' + long + deg + ' at ' + time + 'z', y=1.02, color='#A3A3A4') # Discards old graph, works poorly though # skew.ax.hold(False) # Figure and canvas widgets that display the figure in the GUI # set canvas size to display Skew-T appropriately self.canvas.setMaximumSize(QtCore.QSize(800, 2000)) # refresh canvas self.canvas.draw()
def calculate_stability_indicies(ds, temp_name="temperature", td_name="dewpoint_temperature", p_name="pressure", moving_ave_window=0): """ Function for calculating stability indices from sounding data. Parameters ---------- ds : ACT dataset The dataset to compute the stability indicies of. Must have temperature, dewpoint, and pressure in vertical coordinates. temp_name : str The name of the temperature field. td_name : str The name of the dewpoint field. p_name : str The name of the pressure field. moving_ave_window : int Number of points to do a moving average on sounding data to reduce noise. This is useful if noise in the sounding is preventing parcel ascent. Returns ------- ds : ACT dataset An ACT dataset with additional stability indicies added. """ if not METPY_AVAILABLE: raise ImportError("MetPy need to be installed on your system to " + "calculate stability indices") t = ds[temp_name] td = ds[td_name] p = ds[p_name] if not hasattr(t, "units"): raise AttributeError("Temperature field must have units" + " for ACT to discern!") if not hasattr(td, "units"): raise AttributeError("Dewpoint field must have units" + " for ACT to discern!") if not hasattr(p, "units"): raise AttributeError("Pressure field must have units" + " for ACT to discern!") if t.units == "C": t_units = units.degC else: t_units = getattr(units, t.units) if td.units == "C": td_units = units.degC else: td_units = getattr(units, td.units) p_units = getattr(units, p.units) # Sort all values by decreasing pressure t_sorted = np.array(t.values) td_sorted = np.array(td.values) p_sorted = np.array(p.values) ind_sort = np.argsort(p_sorted) t_sorted = t_sorted[ind_sort[-1:0:-1]] td_sorted = td_sorted[ind_sort[-1:0:-1]] p_sorted = p_sorted[ind_sort[-1:0:-1]] if moving_ave_window > 0: t_sorted = np.convolve( t_sorted, np.ones((moving_ave_window,)) / moving_ave_window) td_sorted = np.convolve( td_sorted, np.ones((moving_ave_window,)) / moving_ave_window) p_sorted = np.convolve( p_sorted, np.ones((moving_ave_window,)) / moving_ave_window) t_sorted = t_sorted * t_units td_sorted = td_sorted * td_units p_sorted = p_sorted * p_units t_profile = mpcalc.parcel_profile( p_sorted, t_sorted[0], td_sorted[0]) # Calculate parcel trajectory ds["parcel_temperature"] = t_profile.magnitude ds["parcel_temperature"].attrs['units'] = t_profile.units # Calculate CAPE, CIN, LCL sbcape, sbcin = mpcalc.surface_based_cape_cin( p_sorted, t_sorted, td_sorted) lcl = mpcalc.lcl( p_sorted[0], t_sorted[0], td_sorted[0]) try: lfc = mpcalc.lfc( p_sorted[0], t_sorted[0], td_sorted[0]) except IndexError: lfc = np.nan * p_sorted.units mucape, mucin = mpcalc.most_unstable_cape_cin( p_sorted, t_sorted, td_sorted) where_500 = np.argmin(np.abs(p_sorted - 500 * units.hPa)) li = t_sorted[where_500] - t_profile[where_500] ds["surface_based_cape"] = sbcape.magnitude ds["surface_based_cape"].attrs['units'] = "J/kg" ds["surface_based_cape"].attrs['long_name'] = "Surface-based CAPE" ds["surface_based_cin"] = sbcin.magnitude ds["surface_based_cin"].attrs['units'] = "J/kg" ds["surface_based_cin"].attrs['long_name'] = "Surface-based CIN" ds["most_unstable_cape"] = mucape.magnitude ds["most_unstable_cape"].attrs['units'] = "J/kg" ds["most_unstable_cape"].attrs['long_name'] = "Most unstable CAPE" ds["most_unstable_cin"] = mucin.magnitude ds["most_unstable_cin"].attrs['units'] = "J/kg" ds["most_unstable_cin"].attrs['long_name'] = "Most unstable CIN" ds["lifted_index"] = li.magnitude ds["lifted_index"].attrs['units'] = t_profile.units ds["lifted_index"].attrs['long_name'] = "Lifted index" ds["level_of_free_convection"] = lfc.magnitude ds["level_of_free_convection"].attrs['units'] = lfc.units ds["level_of_free_convection"].attrs['long_name'] = "Level of free convection" ds["lifted_condensation_level_temperature"] = lcl[1].magnitude ds["lifted_condensation_level_temperature"].attrs['units'] = lcl[1].units ds["lifted_condensation_level_temperature"].attrs['long_name'] = "Lifted condensation level temperature" ds["lifted_condensation_level_pressure"] = lcl[0].magnitude ds["lifted_condensation_level_pressure"].attrs['units'] = lcl[0].units ds["lifted_condensation_level_pressure"].attrs['long_name'] = "Lifted condensation level pressure" return ds
########################################### # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot l = mpcalc.lcl(p[0], T[0], Td[0]) lcl_temp = mpcalc.dry_lapse(concatenate((p[0], l)), T[0])[-1].to('degC') skew.plot(l, lcl_temp, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') skew.plot(p, prof, 'k', linewidth=2) # Example of coloring area between profiles greater = T >= prof skew.ax.fill_betweenx(p, T, prof, where=greater, facecolor='blue', alpha=0.4) skew.ax.fill_betweenx(p, T, prof, where=~greater, facecolor='red', alpha=0.4) # An example of a slanted line at constant T -- in this case the 0 # isotherm l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
def atmCalc(height, temp, humid): print("ATMCALC", height, temp, humid, file=sys.stderr) mtny = windh(MTNX, height, ratio=1, yoffset=0) windx = XVALUES windy = windh(windx, height) temp_ = temp * units.degC initp = mc.height_to_pressure_std(windy[0] * units.meters) dewpt = mc.dewpoint_from_relative_humidity(temp_, humid / 100.) lcl_ = mc.lcl(initp, temp_, dewpt, max_iters=50, eps=1e-5) LCL = mc.pressure_to_height_std(lcl_[0]) if (lcl_[0] > mc.height_to_pressure_std(max(windy) * units.meters) and LCL > windy[0] * units.meters * 1.000009): # add LCL to x xlcl = windh(LCL.to('meters').magnitude, height, inv=True) windx = np.sort(np.append(windx, xlcl)) windy = windh(windx, height) pressures = mc.height_to_pressure_std(windy * units.meters) wvmr0 = mc.mixing_ratio_from_relative_humidity(initp, temp_, humid / 100.) # now calculate the air parcel temperatures and RH at each position if (lcl_[0] <= min(pressures)): T = mc.dry_lapse(pressures, temp_) RH = [ mc.relative_humidity_from_mixing_ratio( wvmr0, t, p) for t, p in zip( T, pressures)] else: mini = np.argmin(pressures) p1 = pressures[:mini + 1] p2 = pressures[mini:] # with an overlap p11 = p1[p1 >= lcl_[0] * .9999999] # lower (with tol) with lcl p12 = p1[p1 < lcl_[0] * 1.000009] # upper (with tol) with lcl T11 = mc.dry_lapse(p11, temp_) T12 = mc.moist_lapse(p12, lcl_[1]) T1 = concatenate((T11[:-1], T12)) T2 = mc.dry_lapse(p2, T1[-1]) T = concatenate((T1, T2[1:])) wvmrtop = mc.saturation_mixing_ratio(pressures[mini], T[mini]) RH=[] for i in range(len(pressures)): if pressures[i] > lcl_[0] and i <= mini: v=mc.relative_humidity_from_mixing_ratio(pressures[i], T[i], wvmr0) else: if i < mini: v=1 else: v=mc.relative_humidity_from_mixing_ratio(pressures[i], T[i], wvmrtop) RH.append(v) #RH = [mc.relative_humidity_from_mixing_ratio(*tp, wvmr0) if tp[1] > lcl_[ #0] and i <= mini else 1.0 if i < mini else #mc.relative_humidity_from_mixing_ratio(*tp, wvmrtop) #for i, tp in enumerate(zip(pressures, T))] RH = concatenate(RH) return windx, mtny, windy, lcl_, LCL, T.to("degC"), RH
def plot_skewt(self): """ :param adjusted_data: receives the post processed dataframe :param valid: :return: """ for area in self.airports: for airport in self.airports[area]: lon = self.airports[area][airport]['lon'] lat = self.airports[area][airport]['lat'] pressure_levels = self.levels * units.hPa tair = list( self.create_profile_variable(self.tair, lat, lon).values()) * units.degC dewp = list( self.create_profile_variable(self.dewp, lat, lon).values()) * units.degC u_wnd = list(self.create_profile_variable(self.u_wnd, lat, lon).values()) * \ units('meters / second').to('knots') v_wnd = list(self.create_profile_variable(self.v_wnd, lat, lon).values()) * \ units('meters / second').to('knots') # Create a new figure. The dimensions here give a good aspect ratio. fig = plt.figure(figsize=(12, 9)) skew = SkewT(fig, rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(pressure_levels, tair, 'r') skew.plot(pressure_levels, dewp, 'g') skew.plot_barbs(pressure_levels, u_wnd, v_wnd) skew.ax.set_ylim(1020, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot lcl_pressure, lcl_temperature = mpcalc.lcl( pressure_levels[0], tair[0], dewp[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(pressure_levels, tair[0], dewp[0]) skew.plot(pressure_levels, prof, 'k', linewidth=2) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() skew.shade_cape(pressure_levels, tair, prof) skew.shade_cin(pressure_levels, tair, prof) plt.title( f'Perfil vertical (GFS) de {airport} valido para {self.time_stamp}', fontsize=16, ha='center') sounding_output_path = f'{self.output_path}/{area}/{airport}' Path(sounding_output_path).mkdir(parents=True, exist_ok=True) plt.savefig( f'{sounding_output_path}/sounding_{airport}_{self.time_step:02d}.jpg' ) return skew
skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) # Y limits from 1000 in the botton to 100 millibars to the top skew.ax.set_xlim(-40, 60) # X limits # Add the relevant special lines\n", skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() plt.show() # Calculate LCL height and plot as black dot\n", from metpy.calc import lcl, dry_lapse from metpy.units import units, concatenate l = lcl(p[0], T[0], Td[0]) # we can calculate the lcl level from the starting p, T, Td lcl_temp = dry_lapse(concatenate((p[0], l)), T[0])[-1].to('degC') # Temperature of the lcl using the dry lapse skew.plot(l, lcl_temp, 'ko', markerfacecolor='black') # plot the lcl level on the temperature with a black circle (ko) filled # Calculate full parcel profile and add to plot as black line\n", from metpy.calc import parcel_profile prof = parcel_profile(p, T[0], Td[0]).to('degC') skew.plot(p, prof, 'k', linewidth=2) # Example of coloring area between profiles skew.ax.fill_betweenx(p, T, prof, where=T>= prof, facecolor='blue', alpha=0.4) skew.ax.fill_betweenx(p, T, prof, where=T< prof, facecolor='red', alpha=0.4) # # An example of a slanted line at constant T -- in this case the 0 isotherm level = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # highligh the 0 degree isoterm
###################################################### ## Calculate thermodymanic and convective variables ## ###################################################### theta = np.array( mcalc.potential_temperature(pres * units.mbar, (Tmean + 273.15) * units.kelvin)) Td = np.array(mcalc.dewpoint_rh(Tmean * units.degC, RHmean / 100.)) ws = np.array( mcalc.saturation_mixing_ratio(pres * units.mbar, (Tmean + 273.15) * units.kelvin)) w = np.multiply(RHmean / 100., ws * 1000.) # check for RH if isRH: lclpres, lcltemp = mcalc.lcl(pres[0] * units.mbar, Tmean[0] * units.degC, Td[0] * units.degC) print 'LCL Pressure: {}'.format(lclpres) print 'LCL Temperature: {}'.format(lcltemp) # parcel profile # determine if there are points sampled above lcl ilcl = np.squeeze(np.where((pres * units.mbar) <= lclpres)) # if not, entire profile dry adiabatic if ilcl.size == 0: prof = mcalc.dry_lapse(pres * units.mbar, Tmean[0] * units.degC).to('degC') isbelowlcl = 1 # if there are, need to concat dry & moist profile ascents else: ilcl = ilcl[0] prof_dry = mcalc.dry_lapse(pres[:ilcl] * units.mbar,