def test_precipitable_water_descriptive_bound_error(): """Test that error is raised when bound is outside profile after nan have been removed.""" pressure = np.array([ 1001, 1000, 997, 977.9, 977, 957, 937.8, 925, 906, 899.3, 887, 862.5, 854, 850, 800, 793.9, 785, 777, 771, 762, 731.8, 726, 703, 700, 655, 630, 621.2, 602, 570.7, 548, 546.8, 539, 513, 511, 485, 481, 468, 448, 439, 424, 420, 412 ]) * units.hPa dewpoint = np.array([ np.nan, np.nan, -26.8, np.nan, -27.3, -28.2, np.nan, -27.2, -26.6, np.nan, -27.4, np.nan, -23.5, -23.5, -25.1, np.nan, -22.9, -17.8, -16.6, np.nan, np.nan, -16.4, np.nan, -18.5, -21., -23.7, np.nan, -28.3, np.nan, -32.6, np.nan, -33.8, -35., -35.1, -38.1, -40., -43.3, -44.6, -46.4, np.nan, np.nan, np.nan ]) * units.degC # Top bound is above highest pressure in profile with pytest.raises(ValueError, match='The pressure and dewpoint profile ranges from'): precipitable_water(pressure, dewpoint, top=units.Quantity(415, 'hPa')) # Bottom bound is below lowest pressure in profile with pytest.raises(ValueError, match='The pressure and dewpoint profile ranges from'): precipitable_water(pressure, dewpoint, bottom=units.Quantity(999, 'hPa'))
def wrap_xr_metpy_pw(dewpt, pressure, bottom=None, top=None, verbose=False, cumulative=False): from metpy.calc import precipitable_water from metpy.units import units import numpy as np # try: # T_unit = dewpt.attrs['units'] # assert T_unit == 'degC' # except KeyError: # T_unit = 'degC' # if verbose: # print('assuming dewpoint units are degC...') dew_values = dewpt.values * units('K') try: P_unit = pressure.attrs['units'] assert P_unit == 'hPa' except KeyError: P_unit = 'hPa' if verbose: print('assuming pressure units are hPa...') if top is not None: top_with_units = top * units(P_unit) else: top_with_units = None if bottom is not None: bottom_with_units = bottom * units(P_unit) else: bottom_with_units = None pressure_values = pressure.values * units(P_unit) if cumulative: pw_list = [] # first value is nan: pw_list.append(np.nan) for pre_val in pressure_values[1:]: if np.isnan(pre_val): pw_list.append(np.nan) continue pw = precipitable_water(pressure_values, dew_values, bottom=None, top=pre_val) pw_units = pw.units.format_babel('~P') pw_list.append(pw.magnitude) pw = np.array(pw_list) return pw, pw_units else: pw = precipitable_water(pressure_values, dew_values, bottom=bottom_with_units, top=top_with_units) pw_units = pw.units.format_babel('~P') return pw.magnitude, pw_units
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 test_precipitable_water(): """Test precipitable water with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') pw = precipitable_water(data['dewpoint'], data['pressure'], top=400 * units.hPa) truth = (0.8899441949243486 * units('inches')).to('millimeters') assert_array_equal(pw, truth)
def test_precipitable_water(): """Test precipitable water with observed sounding.""" with UseSampleData(): data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC', source='wyoming') pw = precipitable_water(data.variables['dewpoint'][:], data.variables['pressure'][:]) truth = (0.8899441949243486 * units('inches')).to('millimeters') assert_array_equal(pw, truth)
def do_profile(cursor, fid, gdf): """Process this profile.""" profile = gdf[pd.notnull(gdf['tmpc']) & pd.notnull(gdf['dwpc'])] if profile.empty: LOG.info("profile %s is empty, skipping", fid) return if profile['pressure'].min() > 400: raise ValueError("Profile only up to %s mb" % (profile['pressure'].min(), )) pwater = precipitable_water(profile['dwpc'].values * units.degC, profile['pressure'].values * units.hPa) (sbcape, sbcin) = surface_based_cape_cin( profile['pressure'].values * units.hPa, profile['tmpc'].values * units.degC, profile['dwpc'].values * units.degC, ) (mucape, mucin) = most_unstable_cape_cin( profile['pressure'].values * units.hPa, profile['tmpc'].values * units.degC, profile['dwpc'].values * units.degC, ) cursor.execute( """ UPDATE raob_flights SET sbcape_jkg = %s, sbcin_jkg = %s, mucape_jkg = %s, mucin_jkg = %s, pwater_mm = %s, computed = 't' WHERE fid = %s """, (nonull(sbcape.to(units('joules / kilogram')).m), nonull(sbcin.to(units('joules / kilogram')).m), nonull(mucape.to(units('joules / kilogram')).m), nonull(mucin.to(units('joules / kilogram')).m), nonull(pwater.to(units('mm')).m), fid))
def test_precipitable_water_xarray(): """Test precipitable water with xarray input.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') press = xr.DataArray(data['pressure'], attrs={'units': str(data['pressure'].units)}) dewp = xr.DataArray(data['dewpoint'], dims=('press',), coords=(press,)) pw = precipitable_water(press, dewp, top=400 * units.hPa) truth = 22.60430651 * units.millimeters assert_almost_equal(pw, truth)
def test_precipitable_water(): """Test precipitable water with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') pw = precipitable_water(data['pressure'], data['dewpoint'], top=400 * units.hPa) truth = 22.60430651 * units.millimeters assert_array_almost_equal(pw, truth, 4)
def test_precipitable_water(): """Test precipitable water with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') pw = precipitable_water(data['pressure'], data['dewpoint'], top=400 * units.hPa) truth = (0.8899441949243486 * units('inches')).to('millimeters') assert_array_equal(pw, truth)
def test_precipitable_water_bound_error(): """Test with no top bound given and data that produced floating point issue #596.""" pressure = np.array([993., 978., 960.5, 927.6, 925., 895.8, 892., 876., 45.9, 39.9, 36., 36., 34.3]) * units.hPa dewpoint = np.array([25.5, 24.1, 23.1, 21.2, 21.1, 19.4, 19.2, 19.2, -87.1, -86.5, -86.5, -86.5, -88.1]) * units.degC pw = precipitable_water(pressure, dewpoint) truth = 89.86846252697836 * units('millimeters') assert_almost_equal(pw, truth, 5)
def test_precipitable_water_no_bounds(): """Test precipitable water with observed sounding and no bounds given.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') dewpoint = data['dewpoint'] pressure = data['pressure'] inds = pressure >= 400 * units.hPa pw = precipitable_water(pressure[inds], dewpoint[inds]) truth = (0.8899441949243486 * units('inches')).to('millimeters') assert_array_equal(pw, truth)
def test_precipitable_water_bound_error(): """Test with no top bound given and data that produced floating point issue #596.""" pressure = np.array([993., 978., 960.5, 927.6, 925., 895.8, 892., 876., 45.9, 39.9, 36., 36., 34.3]) * units.hPa dewpoint = np.array([25.5, 24.1, 23.1, 21.2, 21.1, 19.4, 19.2, 19.2, -87.1, -86.5, -86.5, -86.5, -88.1]) * units.degC pw = precipitable_water(dewpoint, pressure) truth = 89.86955998646951 * units('millimeters') assert_almost_equal(pw, truth, 8)
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 test_precipitable_water_no_bounds(): """Test precipitable water with observed sounding and no bounds given.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') dewpoint = data['dewpoint'] pressure = data['pressure'] inds = pressure >= 400 * units.hPa pw = precipitable_water(dewpoint[inds], pressure[inds]) truth = (0.8899441949243486 * units('inches')).to('millimeters') assert_array_equal(pw, truth)
def test_precipitable_water_no_bounds(): """Test precipitable water with observed sounding and no bounds given.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') dewpoint = data['dewpoint'] pressure = data['pressure'] inds = pressure >= 400 * units.hPa pw = precipitable_water(pressure[inds], dewpoint[inds]) truth = 22.60430651 * units.millimeters assert_array_almost_equal(pw, truth, 4)
def tpw(self, top=None): """ Calculates total precipitable water taking only water vapors into the account""" if top is None: top = np.min(self.p)*units.hectopascal + .01 * units.hPa self.total_pw = mcalc.precipitable_water(dewpt=self.dew *units.K, pressure=self.p *units.hectopascal, top=top) # has a padding factor to avoid error due to float point comparision return self.total_pw
def test_precipitable_water_nans(): """Test that PW returns appropriate number if NaNs are present.""" pressure = np.array([1001, 1000, 997, 977.9, 977, 957, 937.8, 925, 906, 899.3, 887, 862.5, 854, 850, 800, 793.9, 785, 777, 771, 762, 731.8, 726, 703, 700, 655, 630, 621.2, 602, 570.7, 548, 546.8, 539, 513, 511, 485, 481, 468, 448, 439, 424, 420, 412]) * units.hPa dewpoint = np.array([-25.1, -26.1, -26.8, np.nan, -27.3, -28.2, np.nan, -27.2, -26.6, np.nan, -27.4, np.nan, -23.5, -23.5, -25.1, np.nan, -22.9, -17.8, -16.6, np.nan, np.nan, -16.4, np.nan, -18.5, -21., -23.7, np.nan, -28.3, np.nan, -32.6, np.nan, -33.8, -35., -35.1, -38.1, -40., -43.3, -44.6, -46.4, -47., -49.2, -50.7]) * units.degC pw = precipitable_water(pressure, dewpoint) truth = 4.003660322395436 * units.mm assert_almost_equal(pw, truth, 5)
def calculate_pwv_from_kit_rs(path=des_path): """ for kit_rs that go up to 12kms""" from metpy.calc import precipitable_water import xarray as xr ds = read_kit_rs(path) dew = ds['DewPoint'] P = ds['Pressure'] times = [] for dt in P.time: d = dew.sel(time=dt) p = P.sel(time=dt) pwv = precipitable_water(p, d).magnitude times.append(pwv) pwv_rs = xr.DataArray(times, dims=['time']) pwv_rs['time'] = P.time pwv_rs.name = 'pwv' pwv_rs.attrs['long_name'] = 'precipitable water' pwv_rs.attrs['units'] = 'mm' pwv_rs.attrs['action'] = 'proccesed by metpy.calc on KIT-RS data' return pwv_rs
def do_profile(cursor, fid, gdf, nt): """Process this profile.""" # The inbound profile may contain mandatory level data that is below # the surface. It seems the best we can do here is to ensure both # temperature and dewpoint are valid and call that the bottom. td_profile = gdf[pd.notnull(gdf["tmpc"]) & pd.notnull(gdf["dwpc"])] wind_profile = gdf[pd.notnull(gdf["u"])] # Presently we are all or nothing here. The length is arb if len(td_profile.index) < 5 or len(wind_profile.index) < 5: msg = ("quorum fail td: %s wind: %s, skipping") % ( len(td_profile.index), len(wind_profile.index), ) raise ValueError(msg) if gdf["pressure"].min() > 500: raise ValueError("Profile only up to %s mb" % (gdf["pressure"].min(), )) # Does a crude check that our metadata station elevation is within 50m # of the profile bottom, otherwise we ABORT station_elevation_m = get_station_elevation(td_profile, nt) # get surface wind u_sfc, v_sfc = get_surface_winds(wind_profile) u_1km, v_1km = get_aloft_winds(wind_profile, station_elevation_m + 1000.0) u_3km, v_3km = get_aloft_winds(wind_profile, station_elevation_m + 3000.0) u_6km, v_6km = get_aloft_winds(wind_profile, station_elevation_m + 6000.0) shear_sfc_1km_smps = np.sqrt((u_1km - u_sfc)**2 + (v_1km - v_sfc)**2) shear_sfc_3km_smps = np.sqrt((u_3km - u_sfc)**2 + (v_3km - v_sfc)**2) shear_sfc_6km_smps = np.sqrt((u_6km - u_sfc)**2 + (v_6km - v_sfc)**2) total_totals = compute_total_totals(td_profile) sweat_index = compute_sweat_index(td_profile, total_totals) try: bunkers_rm, bunkers_lm, mean0_6_wind = bunkers_storm_motion( wind_profile["pressure"].values * units.hPa, wind_profile["u"].values * units("m/s"), wind_profile["v"].values * units("m/s"), wind_profile["height"].values * units("m"), ) except ValueError: # Profile may not go up high enough bunkers_rm = [np.nan * units("m/s"), np.nan * units("m/s")] bunkers_lm = [np.nan * units("m/s"), np.nan * units("m/s")] mean0_6_wind = [np.nan * units("m/s"), np.nan * units("m/s")] bunkers_rm_smps = wind_speed(bunkers_rm[0], bunkers_rm[1]) bunkers_rm_drct = wind_direction(bunkers_rm[0], bunkers_rm[1]) bunkers_lm_smps = wind_speed(bunkers_lm[0], bunkers_lm[1]) bunkers_lm_drct = wind_direction(bunkers_lm[0], bunkers_lm[1]) mean0_6_wind_smps = wind_speed(mean0_6_wind[0], mean0_6_wind[1]) mean0_6_wind_drct = wind_direction(mean0_6_wind[0], mean0_6_wind[1]) try: ( srh_sfc_1km_pos, srh_sfc_1km_neg, srh_sfc_1km_total, ) = storm_relative_helicity( wind_profile["u"].values * units("m/s"), wind_profile["v"].values * units("m/s"), wind_profile["height"].values * units("m"), 1000.0 * units("m"), ) except ValueError: srh_sfc_1km_pos = np.nan * units("m") # blah srh_sfc_1km_neg = np.nan * units("m") # blah srh_sfc_1km_total = np.nan * units("m") # blah try: ( srh_sfc_3km_pos, srh_sfc_3km_neg, srh_sfc_3km_total, ) = storm_relative_helicity( wind_profile["u"].values * units("m/s"), wind_profile["v"].values * units("m/s"), wind_profile["height"].values * units("m"), 3000.0 * units("m"), ) except ValueError: srh_sfc_3km_pos = np.nan * units("m") # blah srh_sfc_3km_neg = np.nan * units("m") # blah srh_sfc_3km_total = np.nan * units("m") # blah pwater = precipitable_water( td_profile["dwpc"].values * units.degC, td_profile["pressure"].values * units.hPa, ) (sbcape, sbcin) = surface_based_cape_cin( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) (mucape, mucin) = most_unstable_cape_cin( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) el_p, el_t = el( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) lfc_p, lfc_t = lfc( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) (lcl_p, lcl_t) = lcl( td_profile["pressure"].values[0] * units.hPa, td_profile["tmpc"].values[0] * units.degC, td_profile["dwpc"].values[0] * units.degC, ) vals = [ el_p.to(units("hPa")).m, lfc_p.to(units("hPa")).m, lcl_p.to(units("hPa")).m, ] [el_hght, lfc_hght, lcl_hght] = log_interp( np.array(vals, dtype="f"), td_profile["pressure"].values[::-1], td_profile["height"].values[::-1], ) el_agl = gt1(el_hght - station_elevation_m) lcl_agl = gt1(lcl_hght - station_elevation_m) lfc_agl = gt1(lfc_hght - station_elevation_m) args = ( nonull(sbcape.to(units("joules / kilogram")).m), nonull(sbcin.to(units("joules / kilogram")).m), nonull(mucape.to(units("joules / kilogram")).m), nonull(mucin.to(units("joules / kilogram")).m), nonull(pwater.to(units("mm")).m), nonull(el_agl), nonull(el_p.to(units("hPa")).m), nonull(el_t.to(units.degC).m), nonull(lfc_agl), nonull(lfc_p.to(units("hPa")).m), nonull(lfc_t.to(units.degC).m), nonull(lcl_agl), nonull(lcl_p.to(units("hPa")).m), nonull(lcl_t.to(units.degC).m), nonull(total_totals), nonull(sweat_index), nonull(bunkers_rm_smps.m), nonull(bunkers_rm_drct.m), nonull(bunkers_lm_smps.m), nonull(bunkers_lm_drct.m), nonull(mean0_6_wind_smps.m), nonull(mean0_6_wind_drct.m), nonull(srh_sfc_1km_pos.m), nonull(srh_sfc_1km_neg.m), nonull(srh_sfc_1km_total.m), nonull(srh_sfc_3km_pos.m), nonull(srh_sfc_3km_neg.m), nonull(srh_sfc_3km_total.m), nonull(shear_sfc_1km_smps), nonull(shear_sfc_3km_smps), nonull(shear_sfc_6km_smps), fid, ) cursor.execute( """ UPDATE raob_flights SET sbcape_jkg = %s, sbcin_jkg = %s, mucape_jkg = %s, mucin_jkg = %s, pwater_mm = %s, el_agl_m = %s, el_pressure_hpa = %s, el_tmpc = %s, lfc_agl_m = %s, lfc_pressure_hpa = %s, lfc_tmpc = %s, lcl_agl_m = %s, lcl_pressure_hpa = %s, lcl_tmpc = %s, total_totals = %s, sweat_index = %s, bunkers_rm_smps = %s, bunkers_rm_drct = %s, bunkers_lm_smps = %s, bunkers_lm_drct = %s, mean_sfc_6km_smps = %s, mean_sfc_6km_drct = %s, srh_sfc_1km_pos = %s, srh_sfc_1km_neg = %s, srh_sfc_1km_total = %s, srh_sfc_3km_pos = %s, srh_sfc_3km_neg = %s, srh_sfc_3km_total = %s, shear_sfc_1km_smps = %s, shear_sfc_3km_smps = %s, shear_sfc_6km_smps = %s, computed = 't' WHERE fid = %s """, args, )
def fmi2skewt(station, time, img_name): apikey = 'e72a2917-1e71-4d6f-8f29-ff4abfb8f290' url = 'http://data.fmi.fi/fmi-apikey/' + str( apikey ) + '/wfs?request=getFeature&storedquery_id=fmi::observations::weather::sounding::multipointcoverage&fmisid=' + str( station) + '&starttime=' + str(time) + '&endtime=' + str(time) + '&' req = requests.get(url) xmlstring = req.content tree = ET.ElementTree(ET.fromstring(xmlstring)) root = tree.getroot() #reading location and time data to "positions" from XML positions = "" for elem in root.getiterator( tag='{http://www.opengis.net/gmlcov/1.0}positions'): positions = elem.text #'positions' is string type variable #--> split positions into a list by " " #then remove empty chars and "\n" # from pos_split --> data into positions_data try: pos_split = positions.split(' ') except NameError: return "Sounding data not found: stationid " + station + " time " + time pos_split = positions.split(' ') positions_data = [] for i in range(0, len(pos_split)): if not (pos_split[i] == "" or pos_split[i] == "\n"): positions_data.append(pos_split[i]) #index for height: 2,6,10 etc in positions_data height = [] myList = range(2, len(positions_data)) for i in myList[::4]: height.append(positions_data[i]) p = [] for i in range(0, len(height)): p.append(height2pressure(float(height[i]))) #reading wind speed, wind direction, air temperature and dew point data to 'values' values = "" for elem in root.getiterator( tag='{http://www.opengis.net/gml/3.2}doubleOrNilReasonTupleList'): values = elem.text #split 'values' into a list by " " #then remove empty chars and "\n" val_split = values.split(' ') values_data = [] for i in range(0, len(val_split)): if not (val_split[i] == "" or val_split[i] == "\n"): values_data.append(val_split[i]) #data in values_data: w_speed, w_dir, t_air, t_dew wind_speed = [] wind_dir = [] T = [] Td = [] myList = range(0, len(values_data)) for i in myList[::4]: wind_speed.append(float(values_data[i])) wind_dir.append(float(values_data[i + 1])) T.append(float(values_data[i + 2])) Td.append(float(values_data[i + 3])) if stationid == "101104": loc_time = "Jokioinen Ilmala " + time elif stationid == "101932": loc_time = "Sodankyla Tahtela " + time else: return None #calculate wind components u,v: u = [] v = [] for i in range(0, len(wind_speed)): u1, v1 = getWindComponent(wind_speed[i], wind_dir[i]) u.append(u1) v.append(v1) #find index for pressure < 100hPa (for number of wind bars) if min(p) > 100: wthin = len(p) / 20 u_plot = u v_plot = v p_plot = p else: for i in range(0, len(p)): if p[i] - 100 <= 0: wthin = i / 20 u_plot = u[0:i] v_plot = v[0:i] p_plot = p[0:i] break #units wind_speed = wind_speed * units("m/s") wind_dir = wind_dir * units.deg T = T * units.degC Td = Td * units.degC p = p * units("hPa") #calculate pwat, lcl, cape, cin and plot cape pwat = mpcalc.precipitable_water(Td, p, bottom=None, top=None) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') try: cape, cin = mpcalc.cape_cin(p, T, Td, prof) except IndexError: cape = 0 * units("J/kg") cin = 0 * units("J/kg") #__________________plotting__________________ fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) font_par = { 'family': 'monospace', 'color': 'darkred', 'weight': 'normal', 'size': 10, } font_title = { 'family': 'monospace', 'color': 'black', 'weight': 'normal', 'size': 20, } font_axis = { 'family': 'monospace', 'color': 'black', 'weight': 'normal', 'size': 10, } # 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, Td, 'b') skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-50, 30) skew.plot_barbs(p_plot[0::wthin], u_plot[0::wthin], v_plot[0::wthin]) skew.plot_dry_adiabats(alpha=0.4) skew.plot_moist_adiabats(alpha=0.4) skew.plot_mixing_lines(alpha=0.4) #skew.shade_cape(p, T, prof,color="orangered") plt.title(loc_time, fontdict=font_title) plt.xlabel("T (C)", fontdict=font_axis) plt.ylabel("P (hPa)", fontdict=font_axis) #round and remove units from cape,cin,plcl,tlcl,pwat if cape.magnitude > 0: capestr = str(np.round(cape.magnitude)) else: capestr = "NaN" if cin.magnitude > 0: cinstr = str(np.round(cin.magnitude)) else: cinstr = "NaN" lclpstr = str(np.round(lcl_pressure.magnitude)) lclTstr = str(np.round(lcl_temperature.magnitude)) pwatstr = str(np.round(pwat.magnitude)) # str_par = "CAPE[J/kg]=" + capestr + " CIN[J/kg]=" + cinstr + " Plcl[hPa]=" + lclpstr + " Tlcl[C]=" + lclTstr + " pwat[mm]=" + pwatstr # font = {'family': 'monospace', # 'color': 'darkred', # 'weight': 'normal', # 'size': 10, # } # plt.text(-20,1250,str_par,fontdict=font_par) save_file = img_name plt.savefig(save_file)
# 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.ax.set_xlabel('Temperature (Celsius)') skew.plot(p, Td, 'g') #plots the barbs my_interval = np.arange(100, 1000, 20) * units('mbar') ix = resample_nn_1d(p, my_interval) skew.plot_barbs(p[ix], u[ix], v[ix]) skew.ax.set_ylim(1075, 100) skew.ax.set_ylabel('Pressure (hPa)') lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) #LCL pwat = mpcalc.precipitable_water(Td, p, 500 * units.hectopascal).to('in') #PWAT cape, cin = mpcalc.most_unstable_cape_cin(p[:], T[:], Td[:]) #MUCAPE cape_sfc, cin_sfc = mpcalc.surface_based_cape_cin(p, T, Td) #SBCAPE prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') #parcel profile equiv_pot_temp = mpcalc.equivalent_potential_temperature(p, T, Td) #equivalent potential temperature el_pressure, el_temperature = mpcalc.el(p, T, Td) #elevated level lfc_pressure, lfc_temperature = mpcalc.lfc(p, T, Td) #LFC #calculates shear u_threekm_bulk_shear, v_threekm_bulk_shear = mpcalc.bulk_shear(p, u, v, hgt, bottom = min(hgt), depth = 3000 * units.meter) threekm_bulk_shear = mpcalc.get_wind_speed(u_threekm_bulk_shear, v_threekm_bulk_shear) u_onekm_bulk_shear, v_onekm_bulk_shear = mpcalc.bulk_shear(p, u, v, hgt, bottom = min(hgt), depth = 1000 * units.meter) onekm_bulk_shear = mpcalc.get_wind_speed(u_onekm_bulk_shear, v_onekm_bulk_shear) #shows the level of the LCL, LFC, and EL. skew.ax.text(T[0].magnitude, p[0].magnitude + 5, str(int(np.round(T[0].to('degF').magnitude))), fontsize = 'medium', horizontalalignment = 'left', verticalalignment = 'top', color = 'red')
EL_temperature, 'bo', markersize=8, fillstyle='none', label='EL') # Calculate most unstable parcel mup_pressure, mup_temperature, mup_dewTemperature, mup_index \ = mpcalc.most_unstable_parcel(p, T, Td, heights=prof_0, bottom=1050 * units.hPa) skew.plot(p[mup_index - 1:mup_index + 1], T[mup_index - 1:mup_index + 1], 'b', label='the most unstable level') # Calculate precipitable water precipitable_water = mpcalc.precipitable_water(Td, p) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='brown', linestyle='-', linewidth=1, label='isothermal') for i in range(46): for j in range(1, 10): skew.ax.axvline(i * 5 - 160 + j, color='brown', linestyle='-', linewidth=0.3) skew.ax.axvline(i * 5 - 160,
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 entropy_plots(pressure, temperature, mixing_ratio, altitude, h0_std=2000, ensemble_size=20, ent_rate=np.arange(0, 2, 0.05), entrain=False): """ plotting the summarized entropy 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. 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 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.magnitude, q.magnitude * 1e-6, p.magnitude) sm1 = entropy(T.magnitude, q.magnitude, p.magnitude) sm2 = entropy(T.magnitude, qs.magnitude, p.magnitude) ############################### # Water vapor calculations p_PWtop = min(p) #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(sd, p, '-k', linewidth=2) ax.plot(sm1, p, '-b', linewidth=2) ax.plot(sm2, p, '-r', linewidth=2) # mse based on different percentages of relative humidity qr = np.zeros((9, np.size(qs))) * units('kilogram/kilogram') sm1_r = qr # container for i in range(9): qr[i, :] = qs * 0.1 * (i + 1) sm1_r[i, :] = entropy(T.magnitude, qr[i, :].magnitude, p.magnitude) for i in range(9): ax.plot(sm1_r[i, :], p[:], '-', color='grey', linewidth=0.7) ax.text(sm1_r[i, 3].magnitude - 2, 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 sm1_p = np.squeeze(np.ones((1, np.size(T))) * sm1[0]) # 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(sm1_p, p, color='green', linewidth=2) #ax.fill_betweenx(p[lcl_idx:el_idx+1],sm1_p[lcl_idx:el_idx+1],sm2[lcl_idx:el_idx+1],interpolate=True # ,color='green',alpha='0.3') ax.fill_betweenx(p, sd, sm1, color='deepskyblue', alpha='0.5') ax.set_xlabel('Specific entropies: sd, sm, sm_sat [J K$^{-1}$ kg$^{-1}$]', fontsize=14) ax.set_ylabel('Pressure [hPa]', fontsize=14) ax.set_xticks([0, 50, 100, 150, 200, 250, 300, 350]) ax.set_xlim([0, 440]) 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 h0offsets = np.sort(np.random.normal( 0, h0_std, ensemble_size)) * units('joule/kilogram') # Distribution of entrainment rates entrainment_rates = ent_rate / (units('km')) for h0offset in h0offsets: h4ent = sm1.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 if eps.magnitude == 0.0: hent = np.ones(len(h4ent)) * h4ent[0] # no mixing else: hent = np.cumsum(dM * h4ent) / np.cumsum(dM) # Boolean for positive buoyancy, and its topmost altitude (index) where curve is clippes posboy = (hent > sm2) posboy[0] = True # so there is always a detrainment level # defining the first EL by posboy as the detrainment layer, swiching from positive buoyancy to # negative buoyancy (0 to 1) and skipping the surface ELindex_ent = 0 for idx in range(len(posboy) - 1): if posboy[idx + 1] == 0 and posboy[idx] == 1 and idx > 0: ELindex_ent = idx break # Plot the curve plt.plot(hent[0:ELindex_ent + 2], p[0:ELindex_ent + 2], linewidth=0.6, color='g') #plt.plot( hent[0:], p[0:], linewidth=0.6, 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, 100]) ax2.set_xticks([0, 20, 40, 60, 80, 100]) 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) * sm1[0], (1, 0) * (p[0]), color='g', linewidth=2) # Replot the sounding on top of all that mess ax.plot(sm2, p, color='r', linewidth=1.5) ax.plot(sm1, p, color='b', linewidth=1.5) # label LCL and LCF ax.plot((sm2[lcl_idx] + (-2000, 2000) * units('joule/kilogram')), lcl_pressure + (0, 0) * units('mbar'), color='orange', linewidth=3) ax.plot((sm2[lfc_idx] + (-2000, 2000) * units('joule/kilogram')), lfc_pressure + (0, 0) * units('mbar'), color='magenta', linewidth=3) # Plot a crude histogram of parcel detrainment levels # Text parts ax.text(30, pressure[3], 'RH (%)', fontsize=11, color='k') ax.text(20, 200, 'CAPE = ' + str(np.around(CAPE.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(20, 250, 'CIN = ' + str(np.around(CIN.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(20, 300, 'LCL = ' + str(np.around(lcl_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='darkorange') ax.text(20, 350, 'LFC = ' + str(np.around(lfc_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='magenta') ax.text(20, 400, 'CWV = ' + str(np.around(cwv.magnitude, decimals=2)) + ' [mm]', fontsize=12, color='deepskyblue') ax.text(20, 450, 'CRH = ' + str(np.around(crh.magnitude, decimals=2)) + ' [%]', fontsize=12, color='blue') ax.legend(['DEnt', 'MEnt', 'SMEnt'], fontsize=12, loc=1) ax.set_zorder(3) return (ax)
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 process_skewt(self): # Calculation index_p100 = get_pressure_level_index(self.p_i, 100) lcl_p, lcl_t = mpcalc.lcl(self.p_i[0], self.t_i[0], self.td_i[0]) lfc_p, lfc_t = mpcalc.lfc(self.p_i, self.t_i, self.td_i) el_p, el_t = mpcalc.el(self.p_i, self.t_i, self.td_i) prof = mpcalc.parcel_profile(self.p_i, self.t_i[0], self.td_i[0]).to('degC') cape, cin = mpcalc.cape_cin(self.p_i, self.t_i, self.td_i, prof) mucape, mucin = mpcalc.most_unstable_cape_cin(self.p_i, self.t_i, self.td_i) pwat = mpcalc.precipitable_water(self.td_i, self.p_i) i8 = get_pressure_level_index(self.p_i, 850) i7 = get_pressure_level_index(self.p_i, 700) i5 = get_pressure_level_index(self.p_i, 500) theta850 = mpcalc.equivalent_potential_temperature(850 * units('hPa'), self.t_i[i8], self.td_i[i5]) theta500 = mpcalc.equivalent_potential_temperature(500 * units('hPa'), self.t_i[i5], self.td_i[i5]) thetadiff = theta850 - theta500 k = self.t_i[i8] - self.t_i[i5] + self.td_i[i8] - (self.t_i[i7] - self.td_i[i7]) a = ((self.t_i[i8] - self.t_i[i5]) - (self.t_i[i8] - self.td_i[i5]) - (self.t_i[i7] - self.td_i[i7]) - (self.t_i[i5] - self.td_i[i5])) sw = c_sweat(np.array(self.t_i[i8].magnitude), np.array(self.td_i[i8].magnitude), np.array(self.t_i[i5].magnitude), np.array(self.u_i[i8].magnitude), np.array(self.v_i[i8].magnitude), np.array(self.u_i[i5].magnitude), np.array(self.v_i[i5].magnitude)) si = showalter_index(self.t_i[i8], self.td_i[i8], self.t_i[i5]) li = lifted_index(self.t_i[0], self.td_i[0], self.p_i[0], self.t_i[i5]) srh_pos, srh_neg, srh_tot = mpcalc.storm_relative_helicity(self.u_i, self.v_i, self.alt, 1000 * units('m')) sbcape, sbcin = mpcalc.surface_based_cape_cin(self.p_i, self.t_i, self.td_i) shr6km = mpcalc.bulk_shear(self.p_i, self.u_i, self.v_i, heights=self.alt, depth=6000 * units('m')) wshr6km = mpcalc.wind_speed(*shr6km) sigtor = mpcalc.significant_tornado(sbcape, delta_height(self.p_i[0], lcl_p), srh_tot, wshr6km)[0] # Plotting self.ax.set_ylim(1050, 100) self.ax.set_xlim(-40, 50) self.plot(self.p_i, self.t_i, 'r', linewidth=1) self.plot(self.p_i[:self.dp_idx], self.td_i[:self.dp_idx], 'g', linewidth=1) self.plot_barbs(self.p_i[:index_p100], self.u_i[:index_p100] * 1.94, self.v_i[:index_p100] * 1.94) self.plot(lcl_p, lcl_t, 'ko', markerfacecolor='black') self.plot(self.p_i, prof, 'k', linewidth=2) if cin.magnitude < 0: chi = -1 * cin.magnitude self.shade_cin(self.p_i, self.t_i, prof) elif cin.magnitude > 0: chi = cin.magnitude self.shade_cin(self.p_i, self.t_i, prof) else: chi = 0. self.shade_cape(self.p_i, self.t_i, prof) self.plot_dry_adiabats(linewidth=0.5) self.plot_moist_adiabats(linewidth=0.5) self.plot_mixing_lines(linewidth=0.5) plt.title('Skew-T Plot \nStation: {} Time: {}'.format(self.st, self.time.strftime('%Y.%m.%d %H:%M')), fontsize=14, loc='left') # Add hodograph ax = self._fig.add_axes([0.95, 0.71, 0.17, 0.17]) h = Hodograph(ax, component_range=50) h.add_grid(increment=20) h.plot_colormapped(self.u_i[:index_p100], self.v_i[:index_p100], self.alt[:index_p100], linewidth=1.2) # Annotate parameters # Annotate names namelist = ['CAPE', 'CIN', 'MUCAPE', 'PWAT', 'K', 'A', 'SWEAT', 'LCL', 'LFC', 'EL', 'SI', 'LI', 'T850-500', 'θse850-500', 'SRH', 'STP'] xcor = -50 ycor = -90 spacing = -9 for nm in namelist: ax.text(xcor, ycor, '{}: '.format(nm), fontsize=10) ycor += spacing # Annotate values varlist = [cape, chi, mucape, pwat, k, a, sw, lcl_p, lfc_p, el_p, si, li, self.t_i[i8] - self.t_i[i5], thetadiff, srh_tot, sigtor] xcor = 10 ycor = -90 for v in varlist: if hasattr(v, 'magnitude'): v = v.magnitude ax.text(xcor, ycor, str(np.round_(v, 2)), fontsize=10) ycor += spacing # Annotate units unitlist = ['J/kg', 'J/kg', 'J/kg', 'mm', '°C', '°C', '', 'hPa', 'hPa', 'hPa', '°C', '°C', '°C', '°C'] xcor = 45 ycor = -90 for u in unitlist: ax.text(xcor, ycor, ' {}'.format(u), fontsize=10) ycor += spacing