def test_parcel_profile_saturated(): """Test parcel_profile works when LCL in levels (issue #232).""" levels = np.array([1000., 700., 500.]) * units.mbar true_prof = np.array([296.95, 284.381, 271.123]) * units.kelvin prof = parcel_profile(levels, 23.8 * units.degC, 23.8 * units.degC) assert_array_almost_equal(prof, true_prof, 2)
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 add_entropy(ax, pressure, temperature, mixing_ratio, ds=100, linewidth=1.0): "add entropy curves and rescale values to fit in by 0.5*entropy + ds" 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 # specific entropy [joule/(kg*K)] # sd : specific entropy of dry air # sm1 : specific entropy of airborne mositure in state 1 (water vapor) # sm2 : specific entropy of airborne mositure in state 2 (saturated water vapor) sd = entropy(T, q * 0, p) sm1 = entropy(T, q, p) sm2 = entropy(T, qs, p) ax.plot(sd.magnitude * 0.5 + ds, p, '--k') ax.plot(sm1.magnitude * 0.5 + ds, p, '--b') ax.plot(sm2.magnitude * 0.5 + ds, p, '--r')
def _plot_profile(self, skew): profiles = self.atmo_profiles # dictionary pres = profiles.get('pres').get('data') temp = profiles.get('temp').get('data') sphum = profiles.get('sphum').get('data') dewpt = mpcalc.dewpoint_from_specific_humidity(sphum, temp, pres).to('degF') # Pressure vs temperature skew.plot(pres, temp, 'r', linewidth=1.5) # Pressure vs dew point temperature skew.plot(pres, dewpt, 'blue', linewidth=1.5) # Compute parcel profile and plot it parcel_profile = mpcalc.parcel_profile(pres, temp[0], dewpt[0]).to('degC') skew.plot( pres, parcel_profile, 'orange', linestyle='dashed', linewidth=1.2, )
def test_parcel_profile(): """Test parcel profile calculation.""" levels = np.array([1000., 900., 800., 700., 600., 500., 400.]) * units.mbar true_prof = np.array([303.15, 294.16, 288.026, 283.073, 277.058, 269.402, 258.966]) * units.kelvin prof = parcel_profile(levels, 30. * units.degC, 20. * units.degC) assert_array_almost_equal(prof, true_prof, 2)
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 test_cape_cin_no_lfc(): """Test that CAPE is zero with no LFC.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 24.6, 22., 20.4, 18., -10.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.0 * units('joule / kilogram'), 6) assert_almost_equal(cin, 0.0 * units('joule / kilogram'), 6)
def test_cape_cin_no_el(): """Test that CAPE works with no EL.""" p = np.array([959., 779.2, 751.3, 724.3]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.08750805 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin(): """Test the basic CAPE and CIN calculation.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]) cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 58.0368212 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin_no_el(): """Tests that CAPE works with no EL.""" p = np.array([959., 779.2, 751.3, 724.3]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.08750805 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin(): """Tests the basic CAPE and CIN calculation.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]) cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 58.0368212 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def add_curves(ax, pressure, temperature, mixing_ratio, altitude, linewidth=1.0, LH_Tdepend=False): """ overlaying new curves of multiple soundings from profiles """ 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. if len(altitude) == len(pressure): # (1) altitudes for whole levels altitude = altitude * units('meter') elif len(altitude ) == 1: # (2) known altitude where the soundings was launched z_surf = altitude.copy() * units('meter') # given altitude altitude = np.zeros((np.size(T))) * units('meter') for i in range(np.size(T)): altitude[i] = mpcalc.thickness_hydrostatic( p[:i + 1], T[:i + 1]) + z_surf # Hypsometric Eq. for height else: print( '***NOTE***: the altitude at the surface is assumed 0 meter, and altitudes are derived based on the hypsometric equation' ) 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 # specific energies if LH_Tdepend == False: mse = mpcalc.moist_static_energy(altitude, T, q) mse_s = mpcalc.moist_static_energy(altitude, T, qs) dse = mpcalc.dry_static_energy(altitude, T) else: # A short course in cloud physics, Roger and Yau (1989) Lvt = (2500.8 - 2.36 * T.magnitude + 0.0016 * T.magnitude**2 - 0.00006 * T.magnitude**3) * units( 'joule/gram') # latent heat of evaporation #Lf = 2834.1 - 0.29*T - 0.004*T**2 # latent heat of fusion mse = Cp_d * T + g * altitude + Lvt * q mse_s = Cp_d * T + g * altitude + Lvt * qs dse = mpcalc.dry_static_energy(altitude, T) ax.plot(dse, p, '--k', linewidth=linewidth) ax.plot(mse, p, '--b', linewidth=linewidth) ax.plot(mse_s, p, '--r', linewidth=linewidth)
def test_cape_cin_custom_profile(): """Test the CAPE and CIN calculation with a custom profile passed to LFC and EL.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]) + 5 * units.delta_degC cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 1443.505086499895 * units('joule / kilogram'), 6) assert_almost_equal(cin, 0.0 * units('joule / kilogram'), 6)
def test_cape_cin_no_lfc(): """Tests that CAPE is zero with no LFC.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 24.6, 22., 20.4, 18., -10.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.0 * units('joule / kilogram'), 6) assert_almost_equal(cin, 0.0 * units('joule / kilogram'), 6)
def test_el_ml(): """Test equilibrium layer calculation for a mixed parcel.""" levels = np.array([959., 779.2, 751.3, 724.3, 700., 400., 269.]) * units.mbar temperatures = np.array([22.2, 14.6, 12., 9.4, 7., -25., -35.]) * units.celsius dewpoints = np.array([19., -11.2, -10.8, -10.4, -10., -35., -53.2]) * units.celsius __, t_mixed, td_mixed = mixed_parcel(levels, temperatures, dewpoints) mixed_parcel_prof = parcel_profile(levels, t_mixed, td_mixed) el_pressure, el_temperature = el(levels, temperatures, dewpoints, mixed_parcel_prof) assert_almost_equal(el_pressure, 355.834 * units.mbar, 3) assert_almost_equal(el_temperature, -28.371 * units.degC, 3)
def test_lfc_ml(): """Test Mixed-Layer LFC calculation.""" levels = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperatures = np.array([22.2, 14.6, 12., 9.4, 7., -49.]) * units.celsius dewpoints = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius __, t_mixed, td_mixed = mixed_parcel(levels, temperatures, dewpoints) mixed_parcel_prof = parcel_profile(levels, t_mixed, td_mixed) lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints, mixed_parcel_prof) assert_almost_equal(lfc_pressure, 631.794 * units.mbar, 2) assert_almost_equal(lfc_temp, -1.862 * units.degC, 2)
def plot_skewt_icon(sounding, parcel=None, base=1000, top=100, skew=45): model_time = np.datetime_as_string(sounding.metadata.model_time, unit='m') valid_time = np.datetime_as_string(sounding.metadata.valid_time, unit='m') top_idx = find_closest_model_level(sounding.p * units.Pa, top * units("hPa")) fig = plt.figure(figsize=(11, 11), constrained_layout=True) skew = SkewT(fig, rotation=skew) skew.plot(sounding.p * units.Pa, sounding.T * units.K, 'r') skew.plot(sounding.p * units.Pa, sounding.Td, 'b') skew.plot_barbs(sounding.p[:top_idx] * units.Pa, sounding.U[:top_idx] * units.mps, sounding.V[:top_idx] * units.mps, plot_units=units.knot, alpha=0.6, xloc=1.13, x_clip_radius=0.3) if parcel == "surface-based": prof = mpcalc.parcel_profile(sounding.p * units.Pa, sounding.T[0] * units.K, sounding.Td[0]).to('degC') skew.plot(sounding.p * units.Pa, prof, 'y', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() skew.plot(sounding.p * units.Pa, np.zeros(len(sounding.p)) * units.degC, "#03d3fc", linewidth=1) skew.ax.set_ylim(base, top) plt.title(f"Model run: {model_time}Z", loc='left') plt.title(f"Valid time: {valid_time}Z", fontweight='bold', loc='right') plt.xlabel("Temperature [°C]") plt.ylabel("Pressure [hPa]") fig.suptitle(f"ICON-EU Model for {sounding.latitude_pretty}, {sounding.longitude_pretty}", fontsize=14) ax1 = plt.gca() ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis color = '#333333' ax2.set_ylabel('Geometric Altitude [kft]', color=color) # we already handled the x-label with ax1 ax2_data = (sounding.p * units.Pa).to('hPa') ax2.plot(np.zeros(len(ax2_data)), ax2_data, color=color, alpha=0.0) ax2.tick_params(axis='y', labelcolor=color) ax2.set_yscale('log') ax2.set_ylim((base, top)) ticks = np.linspace(base, top, num=10) ideal_ticks = np.geomspace(base, top, 20) real_tick_idxs = [find_closest_model_level(sounding.p * units.Pa, p_level * units("hPa")) for p_level in ideal_ticks] ticks = (sounding.p * units.Pa).to("hPa")[real_tick_idxs] full_levels = [full_level_height(sounding.HHL, idx) for idx in real_tick_idxs] tick_labels = np.around((full_levels * units.m).m_as("kft"), decimals=1) ax2.set_yticks(ticks) ax2.set_yticklabels(tick_labels) ax2.minorticks_off() return fig
def test_parcel_profile_below_lcl(): """Test parcel profile calculation when pressures do not reach LCL (#827).""" pressure = np.array([981, 949.2, 925., 913.9, 903, 879.4, 878, 864, 855, 850, 846.3, 838, 820, 814.5, 799, 794]) * units.hPa truth = np.array([276.35, 273.76110242, 271.74910213, 270.81364639, 269.88711359, 267.85332225, 267.73145436, 266.5050728, 265.70916946, 265.264412, 264.93408677, 264.18931638, 262.55585912, 262.0516423, 260.61745662, 260.15057861]) * units.kelvin profile = parcel_profile(pressure, 3.2 * units.degC, -10.8 * units.degC) assert_almost_equal(profile, truth, 6)
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 calcMLCAPE(levels, temperature, dewpoint, depth=100.0 * units.hPa): _, T_parc, Td_par = mixed_parcel( levels, temperature, dewpoint, depth=depth, interpolate=False, ) profile = parcel_profile(levels, T_parc, Td_parc) cape, cin = cape_cin(levels, temperature, dewpoint, profile) return cape
def thermo_plots(pressure, temperature, mixing_ratio): """" plots for vertical profiles of temperature, dewpoint, mixing ratio and relative humidity. Parameters ---------- pressure : array-like Atmospheric pressure profile (surface to TOA) temperature: array-like Atmospheric temperature profile (surface to TOA) dewpoint: array-like Atmospheric dewpoint profile (surface to TOA) Returns ------- """ p = pressure * units('mbar') q = mixing_ratio * units('kilogram/kilogram') T = temperature * units('degC') Td = mpcalc.dewpoint_from_specific_humidity(q, T, p) # dewpoint Tp = mpcalc.parcel_profile(p, T[0], Td[0]) # parcel plt.figure(figsize=(12, 5)) lev = find_nearest(p.magnitude, 100) plt.subplot(1, 3, 1) plt.plot(T[:lev], p[:lev], '-ob') plt.plot(Td[:lev], p[:lev], '-og') plt.plot(Tp[:lev], p[:lev], '-or') plt.xlabel('Temperature [C]', fontsize=12) plt.ylabel('Pressure [hpa]', fontsize=12) plt.gca().invert_yaxis() plt.legend(['Temp', 'Temp_Dew', 'Temp_Parcel'], loc=1) plt.grid() qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p) # Relative humidity RH = q / qs * 100 # Relative humidity plt.subplot(1, 3, 2) plt.plot(q[:lev], p[:lev], '-og') plt.xlabel('Mixing ratio [kg/kg]', fontsize=12) plt.gca().invert_yaxis() plt.grid() plt.subplot(1, 3, 3) plt.plot(RH[:lev], p[:lev], '-og') plt.xlabel('Relative humiduty [%]', fontsize=12) plt.gca().invert_yaxis() plt.grid() plt.tight_layout() return (plt)
def get_cape(inargs, return_parcel_profile=False): pres_prof, temp_prof, dp_prof = inargs try: prof = mpcalc.parcel_profile(pres_prof, temp_prof[0], dp_prof[0]) cape, cin = mpcalc.cape_cin(pres_prof, temp_prof, dp_prof, prof) except Exception: cape, cin, prof = np.NaN, np.NaN, np.NaN print('Problem during CAPE-calculation. Likely NaN-related.') if return_parcel_profile: return cape, cin, prof else: return cape, cin
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 getData(self, time, model_vars, mdl2stnd, previous_data=None): ''' Name: awips_model_base Purpose: A function to get data from NAM40 model to create HDWX products Inputs: request : A DataAccessLayer request object time : List of datatime(s) for data to grab model_vars : Dictionary with variables/levels to get mdl2stnd : Dictionary to convert from model variable names to standardized names Outputs: Returns a dictionary containing all data Keywords: previous_data : Dictionary with data from previous time step ''' log = logging.getLogger(__name__) # Set up function for logger initTime, fcstTime = get_init_fcst_times(time[0]) data = { 'model': self._request.getLocationNames()[0], 'initTime': initTime, 'fcstTime': fcstTime } # Initialize empty dictionary log.info('Attempting to download {} data'.format(data['model'])) for var in model_vars: # Iterate over variables in the vars list log.debug('Getting: {}'.format(var)) self._request.setParameters(*model_vars[var]['parameters']) # Set parameters for the download request self._request.setLevels(*model_vars[var]['levels']) # Set levels for the download request response = DAL.getGridData(self._request, time) # Request the data for res in response: # Iterate over all data request responses varName = res.getParameter() # Get name of the variable in the response varLvl = res.getLevel() # Get level of the variable in the response varName = mdl2stnd[varName] # Convert variable name to local standarized name if varName not in data: data[varName] = {} # If variable name NOT in data dictionary, initialize new dictionary under key data[varName][varLvl] = res.getRawData() # Add data under level name try: # Try to unit = units(res.getUnit()) # Get units and convert to MetPy units except: # On exception unit = '?' # Set units to ? else: # If get units success data[varName][varLvl] *= unit # Get data and create MetPy quantity by multiplying by units log.debug( 'Got data for:\n Var: {}\n Lvl: {}\n Unit: {}'.format( varName, varLvl, unit)) data['lon'], data['lat'] = res.getLatLonCoords() # Get latitude and longitude values data['lon'] *= units('degree') # Add units of degree to longitude data['lat'] *= units('degree') # Add units of degree to latitude # Absolute vorticity dx, dy = lat_lon_grid_deltas(data['lon'], data['lat']) # Get grid spacing in x and y uTag = mdl2stnd[model_vars['wind']['parameters'][0]] # Get initial tag name for u-wind vTag = mdl2stnd[model_vars['wind']['parameters'][1]] # Get initial tag name for v-wind if (uTag in data) and ( vTag in data): # If both tags are in the data structure data['abs_vort'] = {} # Add absolute vorticity key for lvl in model_vars['wind'][ 'levels']: # Iterate over all leves in the wind data if (lvl in data[uTag]) and ( lvl in data[vTag] ): # If given level in both u- and v-wind dictionaries log.debug('Computing absolute vorticity at {}'.format(lvl)) data['abs_vort'][ lvl ] = \ absolute_vorticity( data[uTag][lvl], data[vTag][lvl], dx, dy, data['lat'] ) # Compute absolute vorticity # 1000 MB equivalent potential temperature if ('temperature' in data) and ( 'dewpoint' in data): # If temperature AND depoint data were downloaded data['theta_e'] = {} T, Td = 'temperature', 'dewpoint' if ('1000.0MB' in data[T]) and ( '1000.0MB' in data[Td] ): # If temperature AND depoint data were downloaded log.debug( 'Computing equivalent potential temperature at 1000 hPa') data['theta_e']['1000.0MB'] = equivalent_potential_temperature( 1000.0 * units('hPa'), data[T]['1000.0MB'], data[Td]['1000.0MB']) return data # MLCAPE log.debug('Computing mixed layer CAPE') T_lvl = list(data[T].keys()) Td_lvl = list(data[Td].keys()) levels = list(set(T_lvl).intersection(Td_lvl)) levels = [float(lvl.replace('MB', '')) for lvl in levels] levels = sorted(levels, reverse=True) nLvl = len(levels) if nLvl > 0: log.debug( 'Found {} matching levels in temperature and dewpoint data' .format(nLvl)) nLat, nLon = data['lon'].shape data['MLCAPE'] = np.zeros(( nLat, nLon, ), dtype=np.float32) * units('J/kg') TT = np.zeros(( nLvl, nLat, nLon, ), dtype=np.float32) * units('degC') TTd = np.zeros(( nLvl, nLat, nLon, ), dtype=np.float32) * units('degC') log.debug('Sorting temperature and dewpoint data by level') for i in range(nLvl): key = '{:.1f}MB'.format(levels[i]) TT[i, :, :] = data[T][key].to('degC') TTd[i, :, :] = data[Td][key].to('degC') levels = np.array(levels) * units.hPa depth = 100.0 * units.hPa log.debug('Iterating over grid boxes to compute MLCAPE') for j in range(nLat): for i in range(nLon): try: _, T_parc, Td_parc = mixed_parcel( levels, TT[:, j, i], TTd[:, j, i], depth=depth, interpolate=False, ) profile = parcel_profile(levels, T_parc, Td_parc) cape, cin = cape_cin(levels, TT[:, j, i], TTd[:, j, i], profile) except: log.warning( 'Failed to compute MLCAPE for lon/lat: {}; {}'. format(data['lon'][j, i], data['lat'][j, i])) else: data['MLCAPE'][j, i] = cape return data
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 from metpy.plots import Hodograpgh fig, ax = plt.subplot(1, 1) hodo = Hodograph(ax) hodo.plot(u,v) hodo.add_grid()
# 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 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
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
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 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 cape(filelist,storm,track,show): #Sort filelist. filelist=np.sort(filelist) # Get sampling periods (this will be a dictionary). See the toolbox print('Retrieving sampling periods') sampleperiods=getsamplingperiods(filelist,3.) # Iterate over all sampling periods. for sampindex,periodskey in enumerate(sampleperiods): #Allocate starting (stdt) and ending date (endt). Remeber dt is the convetional short-name for date. stdt=periodskey endt=sampleperiods[periodskey] # Define sampling period string period=str(stdt.hour)+'_'+str(stdt.day)+'-'+str(endt.hour)+'_'+str(endt.day) # Create new-empty lists. lats=[] lons=[] xs=[] ys=[] capes=[] cins=[] distfig = plt.figure(figsize=(13, 9)) ax=distfig.add_subplot(111) print('start filelist loop') # Iterate over all files. for filename in filelist: # Select end-name of file by inspecting filename string. Notice how filename can change how file is read. if 'radazm' in filename.split('/')[-1] or 'eol' in filename.split('/')[-1]: end='radazm' else: end='avp' # Obtain properties of file, i.e., launch time and location into a dictionary (dicc). dicc=findproperties(filename,end) # Condition to see if current file is in sampling period. # Notice how if structure is constructed, condition finds times outside of sampling period and # if found outside the sampling period, continue to next file. if dicc['Launch Time']<stdt or dicc['Launch Time'] > endt: continue nump=np.genfromtxt(filename,skip_header=16,skip_footer=0) temperature=clean1(nump[:,5]) pressure=clean1(nump[:,4]) Height=clean1(nump[:,13]) if np.nanmax(Height)<3500: continue #Clean for cape RelH=clean1(nump[:,7]) lon=clean1(nump[:,14]) lat=clean1(nump[:,15]) lon=clean1(lon) lat=clean1(lat) mlon=np.nanmean(lon) mlat=np.nanmean(lat) RH=RelH/100 T,P,rh,dz=cleanforcape(temperature,pressure,RH,Height) #Metpy set-up T=np.flip(T,0) rh=np.flip(rh,0) p=np.flip(P,0) dz=np.flip(dz,0) p=p*units.hPa T=T*units.celsius mixing=rh*mpcalc.saturation_mixing_ratio(p,T) epsilon=0.6219800858985514 Tv=mpcalc.virtual_temperature(T, mixing, molecular_weight_ratio=epsilon) dwpoint=mpcalc.dewpoint_rh(T, rh) blh_indx=np.where(dz<500) try: parcelprofile=mpcalc.parcel_profile(p,np.nanmean(T[blh_indx])*units.celsius,mpcalc.dewpoint_rh(np.nanmean(T[blh_indx])*units.celsius, np.nanmean(rh[blh_indx]))).to('degC') Tv_parcelprofile=mpcalc.virtual_temperature(parcelprofile, mixing, molecular_weight_ratio=epsilon) cape,cin=cape_cin(p,Tv,dwpoint,Tv_parcelprofile,dz,T) except: continue plotskewT=True if plotskewT==True: os.system('mkdir figs/skewt') fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) skew.plot(p, dwpoint, 'g',label=r'$T_{dp}$') skew.plot(p, Tv, 'r',label=r'$T_v$') plt.text(-120,120,str(np.around(cape,2)),fontsize=14,fontweight='bold') # 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,Tv_parcelprofile,'k',label=r'$T_{v env}$') skew.shade_cin(p, T, parcelprofile,label='CIN') skew.shade_cape(p, Tv, Tv_parcelprofile,label='CAPE') skew.plot_dry_adiabats() skew.plot_moist_adiabats() plt.legend() plt.title(storm + ' on' + period,fontsize=14) plt.savefig('figs/skewt/'+storm+str(dicc['Launch Time'].time())+'.png') #plt.show() plt.close() r,theta=cart_to_cylindr(mlon,mlat,track,dicc['Launch Time']) if not(np.isnan(r)) and not(np.isnan(theta)) and not(np.isnan(cape.magnitude)): xs.append(r*np.cos(theta)) ys.append(r*np.sin(theta)) capes.append(cape.magnitude) cins.append(cin) cs=ax.scatter(xs,ys,c=np.asarray(capes),cmap='jet') for i,xi in enumerate(xs): ax.text(xi,ys[i]+10,str(np.around(capes[i],1))) plt.colorbar(cs) ax.scatter(0,0,marker='v',s=100,color='black') ax.grid() ax.set_xlabel('X distance [km]') ax.set_ylabel('Y distance [km]') ax.set_title('CAPE distribution for '+storm+' on '+period,fontsize=14) distfig.savefig('figs/cape'+storm+period+'.png') if show: plt.show()
# 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) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines()
def main(): img_dir = Path("hail_plots/soundings/") if not img_dir.exists(): img_dir.mkdir(parents=True) data_dir = Path("/HOME/huziy/skynet3_rech1/hail/soundings_from_erai/") # dates = [datetime(1991, 9, 7), datetime(1991, 9, 7, 6), datetime(1991, 9, 7, 12), datetime(1991, 9, 7, 18), # datetime(1991, 9, 8, 0), datetime(1991, 9, 8, 18)] # # dates.extend([datetime(1991, 9, 6, 0), datetime(1991, 9, 6, 6), datetime(1991, 9, 6, 12), datetime(1991, 9, 6, 18)]) # # dates = [datetime(1990, 7, 7), datetime(2010, 7, 12), datetime(1991, 9, 8, 0)] dates_s = """ - 07/09/1991 12:00 - 07/09/1991 18:00 - 08/09/1991 00:00 - 08/09/1991 06:00 - 08/09/1991 12:00 - 13/09/1991 12:00 - 13/09/1991 18:00 - 14/09/1991 00:00 - 14/09/1991 06:00 - 14/09/1991 12:00 """ dates = [datetime.strptime(line.strip()[1:].strip(), "%d/%m/%Y %H:%M") for line in dates_s.split("\n") if line.strip() != ""] def __date_parser(s): return pd.datetime.strptime(s, '%Y-%m-%d %H:%M:%S') tt = pd.read_csv(data_dir.joinpath("TT.csv"), index_col=0, parse_dates=['Time']) uu = pd.read_csv(data_dir.joinpath("UU.csv"), index_col=0, parse_dates=['Time']) vv = pd.read_csv(data_dir.joinpath("VV.csv"), index_col=0, parse_dates=['Time']) hu = pd.read_csv(data_dir.joinpath("HU.csv"), index_col=0, parse_dates=['Time']) print(tt.head()) print([c for c in tt]) print(list(tt.columns.values)) temp_perturbation_degc = 0 for the_date in dates: p = np.array([float(c) for c in tt]) fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) tsel = tt.select(lambda d: d == the_date) usel = uu.select(lambda d: d == the_date) vsel = vv.select(lambda d: d == the_date) husel = hu.select(lambda d: d == the_date) tvals = tsel.values.mean(axis=0) uvals = usel.values.mean(axis=0) * mul_mpers_per_knot vvals = vsel.values.mean(axis=0) * mul_mpers_per_knot huvals = husel.values.mean(axis=0) * units("g/kg") # ignore the lowest level all_vars = [p, tvals, uvals, vvals, huvals] for i in range(len(all_vars)): all_vars[i] = all_vars[i][:-5] p, tvals, uvals, vvals, huvals = all_vars assert len(p) == len(huvals) tdvals = calc.dewpoint(calc.vapor_pressure(p * units.mbar, huvals).to(units.mbar)) print(tvals, tdvals) # Calculate full parcel profile and add to plot as black line parcel_profile = calc.parcel_profile(p[::-1] * units.mbar, (tvals[-1] + temp_perturbation_degc) * units.degC, tdvals[-1]).to('degC') parcel_profile = parcel_profile[::-1] skew.plot(p, parcel_profile, 'k', linewidth=2) # Example of coloring area between profiles greater = tvals * units.degC >= parcel_profile skew.ax.fill_betweenx(p, tvals, parcel_profile, where=greater, facecolor='blue', alpha=0.4) skew.ax.fill_betweenx(p, tvals, parcel_profile, where=~greater, facecolor='red', alpha=0.4) skew.plot(p, tvals, "r") skew.plot(p, tdvals, "g") skew.plot_barbs(p, uvals, vvals) # Plot a zero degree isotherm l = 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() plt.title("{} (dT={})".format(the_date, temp_perturbation_degc)) img_path = "{}_dT={}.png".format(the_date.strftime("%Y%m%d_%H%M%S"), temp_perturbation_degc) img_path = img_dir.joinpath(img_path) fig.savefig(str(img_path), bbox_inches="tight") plt.close(fig)
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]
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)
# 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 # publication. # # The most basic skew-T can be plotted with only five lines of Python. # These lines perform the following tasks: #