Exemple #1
0
def test_lcl_convergence():
    """Test LCL calculation convergence failure."""
    with pytest.raises(RuntimeError):
        lcl(1000. * units.mbar,
            30. * units.degC,
            20. * units.degC,
            max_iters=2)
Exemple #2
0
def parcelUAV(T, Td, p):
	'''
	Inputs: temperature, dewpoint, and pressure
	Returns: lcl pressure, lcl temperature, isbelowlcl flag, profile temp
	'''
	lclpres, lcltemp = mcalc.lcl(p[0] * units.mbar, 
        T[0] * units.degC, Td[0] * units.degC)
	print 'LCL Pressure: %5.2f %s' % (lclpres.magnitude, lclpres.units)
	print 'LCL Temperature: %5.2f %s' % (lcltemp.magnitude, lcltemp.units)

	# parcel profile
	# determine if there are points sampled above lcl
	ilcl = np.squeeze(np.where((p * units.mbar) <= lclpres))
	# if not, entire profile dry adiabatic
	if ilcl.size == 0:
	    prof = mcalc.dry_lapse(p * units.mbar, T[0] * units.degC).to('degC')
	    isbelowlcl = 1
	# if there are, need to concat dry & moist profile ascents
	else:
	    ilcl = ilcl[0]
	    prof_dry = mcalc.dry_lapse(p[:ilcl] * units.mbar,
	        T[0] * units.degC).to('degC')
	    prof_moist = mcalc.moist_lapse(p[ilcl:] * units.mbar,
	        prof_dry[-1]).to('degC')
	    prof = np.concatenate((prof_dry, prof_moist)) * units.degC
	    isbelowlcl = 0

	return lclpres, lcltemp, isbelowlcl, prof
Exemple #3
0
    def calculate_basic_thermo(self):

        #Enclose in try, except because not every sounding will have a converging parcel path or CAPE.
        try:

            #Precipitable Water
            self.pw = mc.precipitable_water(self.sounding["pres"],
                                            self.sounding["dewp"])

            #Lifting condensation level
            self.lcl_pres, self.lcl_temp = mc.lcl(self.sounding["pres"][0],
                                                  self.sounding["temp"][0],
                                                  self.sounding["dewp"][0])

            #Surface-based CAPE and CIN
            self.parcel_path = mc.parcel_profile(self.sounding["pres"],
                                                 self.sounding["temp"][0],
                                                 self.sounding["dewp"][0])
            self.sfc_cape, self.sfc_cin = mc.cape_cin(self.sounding["pres"],
                                                      self.sounding["temp"],
                                                      self.sounding["dewp"],
                                                      self.parcel_path)

        #Do this when parcel path fails to converge
        except Exception as e:
            print("WARNING: No LCL, CAPE, or PW stats because:\n{}.".format(e))
            self.parcel_path = numpy.nan
            self.pw = numpy.nan
            self.lcl_pres = numpy.nan
            self.lcl_temp = numpy.nan
            self.sfc_cape = numpy.nan
            self.sfc_cin = numpy.nan

        #Returning
        return
def showalter_index(pressure, temperature, dewpt):
    """Calculate Showalter Index from pressure temperature and 850 hPa lcl.

    Showalter Index derived from [Galway1956]_:
    SI = T500 - Tp500

    where:
    T500 is the measured temperature at 500 hPa
    Tp500 is the temperature of the lifted parcel at 500 hPa

    Parameters
    ----------

        pressure : `pint.Quantity`
            Atmospheric pressure level(s) of interest, in order from highest
            to lowest pressure

        temperature : `pint.Quantity`
            Parcel temperature for corresponding pressure

        dewpt (:class: `pint.Quantity`):
            Parcel dew point temperatures for corresponding pressure


     Returns
     -------

     `pint.Quantity`
        Showalter index in delta degrees celsius
    """

    # find the measured temperature and dew point temperature at 850 hPa.
    idx850 = np.where(pressure == 850 * units.hPa)
    T850 = temperature[idx850]
    Td850 = dewpt[idx850]

    # find the parcel profile temperature at 500 hPa.
    idx500 = np.where(pressure == 500 * units.hPa)
    Tp500 = temperature[idx500]

    # Calculate lcl at the 850 hPa level
    lcl_calc = mpcalc.lcl(850 * units.hPa, T850[0], Td850[0])
    lcl_calc = lcl_calc[0]

    # Define start and end heights for dry and moist lapse rate calculations
    p_strt = 1000 * units.hPa
    p_end = 500 * units.hPa

    # Calculate parcel temp when raised dry adiabatically from surface to lcl
    dl = mpcalc.dry_lapse(lcl_calc, temperature[0], p_strt)
    dl = (dl.magnitude - 273.15) * units.degC  # Change units to C

    # Calculate parcel temp when raised moist adiabatically from lcl to 500mb
    ml = mpcalc.moist_lapse(p_end, dl, lcl_calc)

    # Calculate the Showalter index
    shox = Tp500 - ml
    return shox
Exemple #5
0
def plot_sounding(date, station):
    p, T, Td, u, v, windspeed = get_sounding_data(date, station)

    lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
    lfc_pressure, lfc_temperature = mpcalc.lfc(p, T, Td)
    parcel_path = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')

    # Create a new figure. The dimensions here give a good aspect ratio
    fig = plt.figure(figsize=(8, 8))
    skew = SkewT(fig)

    # Plot the data
    temperature_line, = skew.plot(p, T, color='tab:red')
    dewpoint_line, = skew.plot(p, Td, color='blue')
    cursor = mplcursors.cursor([temperature_line, dewpoint_line])

    # Plot thermodynamic parameters and parcel path
    skew.plot(p, parcel_path, color='black')

    if lcl_pressure:
        skew.ax.axhline(lcl_pressure, color='black')

    if lfc_pressure:
        skew.ax.axhline(lfc_pressure, color='0.7')

    # Add the relevant special lines
    skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
    skew.plot_dry_adiabats()
    skew.plot_moist_adiabats()
    skew.plot_mixing_lines()

    # Shade areas representing CAPE and CIN
    skew.shade_cin(p, T, parcel_path)
    skew.shade_cape(p, T, parcel_path)

    # Add wind barbs
    skew.plot_barbs(p, u, v)

    # Add an axes to the plot
    ax_hod = inset_axes(skew.ax, '30%', '30%', loc=1, borderpad=3)

    # Plot the hodograph
    h = Hodograph(ax_hod, component_range=100.)

    # Grid the hodograph
    h.add_grid(increment=20)

    # Plot the data on the hodograph
    mask = (p >= 100 * units.mbar)
    h.plot_colormapped(u[mask], v[mask],
                       windspeed[mask])  # Plot a line colored by wind speed

    # Set some sensible axis limits
    skew.ax.set_ylim(1000, 100)
    skew.ax.set_xlim(-40, 60)

    return fig, skew
def get_skewt_vars(p, tc, tdc, pro):
    """This function processes the dataset values and returns a string element
    which can be used as a subtitle to replicate the styles of NCL Skew-T
    Diagrams.

    Args:

        p (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`):
            Pressure level input from dataset

        tc (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`):
            Temperature for parcel from dataset

        tdc (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`):
            Dew point temperature for parcel from dataset

        pro (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`):
            Parcel profile temperature converted to degC


    Returns:
        :class: 'str'
    """

    # CAPE
    cape = mpcalc.cape_cin(p, tc, tdc, pro)
    cape = cape[0].magnitude

    # Precipitable Water
    pwat = mpcalc.precipitable_water(p, tdc)
    pwat = (pwat.magnitude / 10) * units.cm  # Convert mm to cm
    pwat = pwat.magnitude

    # Pressure and temperature of lcl
    lcl = mpcalc.lcl(p[0], tc[0], tdc[0])
    plcl = lcl[0].magnitude
    tlcl = lcl[1].magnitude

    # Showalter index
    shox = showalter_index(p, tc, tdc)
    shox = shox[0].magnitude

    # Place calculated values in iterable list
    vals = [plcl, tlcl, shox, pwat, cape]
    vals = [round(num) for num in vals]

    # Define variable names for calculated values
    names = ['Plcl=', 'Tlcl[C]=', 'Shox=', 'Pwat[cm]=', 'Cape[J]=']

    # Combine the list of values with their corresponding labels
    lst = list(chain.from_iterable(zip(names, vals)))
    lst = map(str, lst)

    # Create one large string for later plotting use
    joined = ' '.join(lst)

    return joined
def 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
Exemple #8
0
def make_skewt():
    # Get the data
    date = request.args.get('date')
    time = request.args.get('time')
    region = request.args.get('region')
    station = request.args.get('station')
    date = datetime.strptime(date, '%Y%m%d')
    date = datetime(date.year, date.month, date.day, int(time))
    df = get_sounding_data(date, region, station)
    p = df['pressure'].values * units(df.units['pressure'])
    T = df['temperature'].values * units(df.units['temperature'])
    Td = df['dewpoint'].values * units(df.units['dewpoint'])
    u = df['u_wind'].values * units(df.units['u_wind'])
    v = df['v_wind'].values * units(df.units['v_wind'])

    # Make the Skew-T
    fig = plt.figure(figsize=(9, 9))
    add_metpy_logo(fig, 115, 100)
    skew = SkewT(fig, rotation=45)

    # Plot the data using normal plotting functions, in this case using
    # log scaling in Y, as dictated by the typical meteorological plot
    skew.plot(p, T, 'tab:red')
    skew.plot(p, Td, 'tab:green')
    skew.plot_barbs(p, u, v)
    skew.ax.set_ylim(1000, 100)
    skew.ax.set_xlim(-40, 60)

    # Calculate LCL height and plot as black dot
    lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
    skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black')

    # Calculate full parcel profile and add to plot as black line
    prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')
    skew.plot(p, prof, 'k', linewidth=2)

    # Shade areas of CAPE and CIN
    skew.shade_cin(p, T, prof)
    skew.shade_cape(p, T, prof)

    # An example of a slanted line at constant T -- in this case the 0
    # isotherm
    skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)

    # Add the relevant special lines
    skew.plot_dry_adiabats()
    skew.plot_moist_adiabats()
    skew.plot_mixing_lines()

    canvas = FigureCanvas(fig)
    img = BytesIO()
    fig.savefig(img)
    img.seek(0)
    return send_file(img, mimetype='image/png')
    def plot_skewt(self, station_data):
        """
        :param adjusted_data: receives the post processed dataframe
        :param valid:
        :return:
        """

        # We will pull the data out of the example dataset into individual variables
        # and assign units.

        p = station_data['pressure'].values * units.hPa
        T = station_data['Temperature_isobaric'].values * units.degC
        Td = station_data['Dewpoint'].replace(np.nan,
                                              0.0000001).values * units.degC
        u = station_data['u-component_of_wind_isobaric'].values * \
            units('meters / second').to('knots')
        v = station_data['v-component_of_wind_isobaric'].values * \
            units('meters / second').to('knots')

        # Create a new figure. The dimensions here give a good aspect ratio.
        fig = plt.figure(figsize=(12, 9))
        skew = SkewT(fig, rotation=45)

        # Plot the data using normal plotting functions, in this case using
        # log scaling in Y, as dictated by the typical meteorological plot
        skew.plot(p, T, 'r')
        skew.plot(p, Td, 'g')
        skew.plot_barbs(p, u, v)
        skew.ax.set_ylim(1020, 100)
        skew.ax.set_xlim(-40, 60)

        # Calculate LCL height and plot as black dot
        lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
        skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black')

        # Calculate full parcel profile and add to plot as black line
        prof = mpcalc.parcel_profile(p, T[0], Td[0])
        skew.plot(p, prof, 'k', linewidth=2)

        # An example of a slanted line at constant T -- in this case the 0
        # isotherm
        skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)

        # Add the relevant special lines
        skew.plot_dry_adiabats()
        skew.plot_moist_adiabats()
        skew.plot_mixing_lines()

        skew.shade_cape(p, T, prof)
        skew.shade_cin(p, T, prof)

        return skew
Exemple #10
0
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()
Exemple #11
0
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
Exemple #12
0
def plot_skewt(p, t, td, puv=None, u=None, v=None, title=None, outfile=None):

    # Create a new figure. The dimensions here give a good aspect ratio
    fig = plt.figure(figsize=(9, 9))
    skew = SkewT(fig, rotation=30)

    # Plot the data using normal plotting functions, in this case using
    # log scaling in Y, as dictated by the typical meteorological plot
    skew.plot(p, t, 'r', linewidth=2)
    skew.plot(p, td, 'g', linewidth=2)
    if u is not None and v is not None:
        skew.plot_barbs(puv, u, v)

    skew.ax.set_ylim(1000, 100)
    skew.ax.set_xlim(-40, 60)

    # Calculate the LCL
    lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], t[0], td[0])

    # Calculate the parcel profile.
    parcel_prof = mpcalc.parcel_profile(p, t[0], td[0]).to('degC')

    # Plot LCL temperature as black dot
    skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black')

    # Plot the parcel profile as a black line
    skew.plot(p, parcel_prof, 'k--', linewidth=1)

    # Shade areas of CAPE and CIN
    skew.shade_cin(p, t, parcel_prof)
    skew.shade_cape(p, t, parcel_prof)

    # Plot a zero degree isotherm
    #skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)

    # Add the relevant special lines
    skew.plot_dry_adiabats()
    skew.plot_moist_adiabats()
    skew.plot_mixing_lines()

    if title is not None:
        plt.title(title)

    # Show the plot
    #plt.show()
    if outfile is None: outfile = 'skewt.png'
    fig.savefig(outfile, format='png')
def LCL_metpy(pres, temp, dp):
    """
    Inputs are pressure (hPa), temperature (degC), and dew point (degC)
    """
    # pre-allocate
    len_time = len(pres.launch_time)
    lcl_p = [None] * len_time
    lcl_t = [None] * len_time
    lcl_ht = [None] * len_time
    pres_avg = np.zeros(len_time)
    T_avg = np.zeros(len_time)
    dp_avg = np.zeros(len_time)

    for i in range(len_time):

        # input: sonde mean-value from 60-210 m for pressure, temp, and dew point temp
        pres_avg[i] = pres[i, 5:20].mean(dim="alt", skipna=True)
        T_avg[i] = temp[i, 5:20].mean(dim="alt", skipna=True)
        dp_avg[i] = dp[i, 5:20].mean(dim="alt", skipna=True)

        # break loop and restart if NaN encountered
        if np.isnan(T_avg[i]) == True:
            continue

        # lcl_p and lcl_t are the values of pressure (hPa) and temperature (K) at LCL, respectively
        lcl_p[i], lcl_t[i] = (mpcalc.lcl(pres_avg[i] * units.hPa,
                                         T_avg[i] * units.degC,
                                         dp_avg[i] * units.degC,
                                         max_iters=200))

        lcl_p[i] = lcl_p[i].magnitude
        lcl_t[i] = lcl_t[i].magnitude

        # find height where PLCL- 10hPa < P < PLCL + 10hPa
        altitude = dp['alt']
        lcl_ht[i] = altitude.where((pres[i]<lcl_p[i]+10) & \
                      (pres[i]>lcl_p[i] - 10)).mean().values

        # convert None to NaN
        lcl_ht = np.array(lcl_ht, dtype=float)

    return lcl_ht
Exemple #14
0
def calculate_sounding_data(df,field_height,field_temp):
    ###################################### CALCULATION MAGIC #################################################
    # We will pull the data out of the latest soudning into individual variables and assign units.           #
    # This will Return a dictionary with all the vallculate values. Keys are:                                #
    # "pressure", "temperature", "dewpoint", "height", "windspeed","wind_dir"                               
    ###################################### CALCULATION MAGIC #################################################
    cache = settings.AppCache
    #Retrieves Sounding Details and returns them
    key='calculation'+str(field_height)+'_'+str(field_temp)
    #If soundings are cached and valid, returns the cache, otherwise retreive new sounding
    calc = cache.get(key)

    if calc is not None:
        logging.info("Calculation Cache Hit")
        print("Calculation Cahce Hit")
        return calc
    
    logging.info("Calculation Cache Miss")
    print("Calculation Cahce Miss")
    p = df['pressure'].values * units.hPa
    T = df['temperature'].values * units.degC
    Td = df['dewpoint'].values * units.degC
    alt = df['height'].values * units.ft
    wind_speed = df['speed'].values * units.knots
    wind_dir = df['direction'].values * units.degrees
    wet_bulb = mpcalc.wet_bulb_temperature(p,T,Td)
    field_pressure = mpcalc.height_to_pressure_std(field_height*units.ft)
    adiabat_line = mpcalc.dry_lapse(p,field_temp*units.degC,ref_pressure=mpcalc.height_to_pressure_std(field_height*units.ft))
    
    #Interpolate Missing Values using linear interpolation
    Td_linear = interp1d(alt.magnitude,Td.magnitude)
    T_linear = interp1d(alt.magnitude,T.magnitude)
    
    #Calculate the LCL Based on Max Temperature
    lcl_pressure, lcl_temperature = mpcalc.lcl(field_pressure,field_temp*units.degC ,Td_linear(field_height)*units.degC)
    #parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')
    calc = MeteoCast.UpperAtmData(df, p, T, Td,alt,wind_speed,wind_dir,wet_bulb,field_pressure,lcl_pressure,lcl_temperature,adiabat_line)
    cache.set(key, calc, expire=600,tag='Calculation Data ')
    return calc
u,v = get_wind_components(spd, direc)

# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(9, 9))
skew = SkewT(fig, rotation=45)

# Plot the data using normal plotting functions, in this case using
# log scaling in Y, as dictated by the typical meteorological plot
skew.plot(p, T, 'r')
skew.plot(p, Td, 'g')
skew.plot_barbs(p, u, v)
skew.ax.set_ylim(1000, 100)
skew.ax.set_xlim(-40, 60)

# Calculate LCL height and plot as black dot
l = lcl(p[0], C2K(T[0]), C2K(Td[0]))
skew.plot(l, K2C(dry_lapse(l, C2K(T[0]), p[0])), 'ko',
        markerfacecolor='black')

# Calculate full parcel profile and add to plot as black line
prof = K2C(parcel_profile(p, C2K(T[0]), C2K(Td[0])))
skew.plot(p, prof, 'k', linewidth=2)

# Example of coloring area between profiles
skew.ax.fill_betweenx(p, T, prof, where=T>=prof, facecolor='blue', alpha=0.4)
skew.ax.fill_betweenx(p, T, prof, where=T<prof, facecolor='red', alpha=0.4)

# An example of a slanted line at constant T -- in this case the 0
# isotherm
l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
Exemple #16
0
def EnergyMassPlot(pressure, temperature, dewpoint,
                  height, uwind, vwind, sphum=None, rh=None,
                  label='', size=(12,10), return_fig=False): 
    p=pressure
    Z=height
    T=temperature
    Td=dewpoint
    if isinstance(sphum,np.ndarray) and isinstance(rh,np.ndarray):
        q=sphum
        qs=q/rh
    else:
        q = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Td),p)
        qs= mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T),p)
    
    s = g*Z + Cp_d*T 
    sv= g*Z + Cp_d*mpcalc.virtual_temperature(T,q)
    h = s            + Lv*q
    hs= s            + Lv*qs
    
    parcel_Tprofile = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')
    CAPE,CIN = mpcalc.cape_cin(p,T,Td,parcel_Tprofile)
    ELp,ELT = mpcalc.el(p,T,Td)
    ELindex = np.argmin(np.abs(p - ELp))
    lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
    
    p_PWtop = max(200*units.mbar, min(p) +1*units.mbar)
    PW = mpcalc.precipitable_water(Td,p, top=p_PWtop)
    PWs = mpcalc.precipitable_water(T,p, top=p_PWtop)
    CRH = (PW/PWs).magnitude *100. 
    
    fig,ax=setup_fig(size=size,label=label)
    
    ax.plot(s  /1000, p, color='r', linewidth=1.5)  ### /1000 for kJ/kg
    ax.plot(sv /1000, p, color='r', linestyle='-.') 
    ax.plot(h  /1000, p, color='b', linewidth=1.5) 
    ax.plot(hs /1000, p, color='r', linewidth=1.5) 
    ### RH rulings between s and h lines: annotate near 800 hPa level
    annot_level = 800 #hPa
    idx = np.argmin(np.abs(p - annot_level *units.hPa))
    right_annot_loc = 380
    for iRH in np.arange(10,100,10):
        ax.plot( (s+ Lv*qs*iRH/100.)/1000, p, linewidth=0.5, linestyle=':', color='k')
        ax.annotate(str(iRH), xy=( (s[idx]+Lv*qs[idx]*iRH/100.)/1000, annot_level),                    
                    horizontalalignment='center',fontsize=6)
    ax.annotate('RH (%)', xy=(right_annot_loc, annot_level), fontsize=10)
    
    if  not np.isnan(CAPE.magnitude) and CAPE.magnitude >10:  
        parcelh  = h [0]        # for a layer mean: np.mean(h[idx1:idx2])
        parcelsv = sv[0]
        parcelp0 = p[0]

        # Undilute parcel
        ax.plot( (1,1)*parcelh/1000., (1,0)*parcelp0, linewidth=0.5, color='g')
        maxbindex = np.argmax(parcel_Tprofile - T)
        ax.annotate('CAPE='+str(int(CAPE.magnitude)), 
                    xy=(parcelh/1000., p[maxbindex]), color='g')

        # Plot LCL at saturation point, above the lifted sv of the surface parcel 
        lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
        ax.annotate('LCL', xy=(sv[0]/1000., lcl_pressure), fontsize=10, color='g', horizontalalignment='right')
        # Purple fill for negative buoyancy below LCL:
        ax.fill_betweenx(p, sv/1000., parcelsv/1000., where=p>lcl_pressure, facecolor='purple', alpha=0.4)

        # Positive moist convective buoyancy in green 
        # Above LCL:
        ax.fill_betweenx(p, hs/1000., parcelh/1000., where= parcelh>hs, facecolor='g', alpha=0.4)


        # Depict Entraining parcels
        # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z)
        # M=1 at ground without loss of generality
        entrainment_distance = 10000., 5000., 2000. 
        ax.annotate('entrain: 10,5,2 km',  xy=(parcelh/1000, 140), color='g', 
                    fontsize=8, horizontalalignment='right')
        ax.annotate('parcel h',            xy=(parcelh/1000, 120), color='g', 
                    fontsize=10, horizontalalignment='right')

        for ED in entrainment_distance: 
            eps = 1.0 / (ED*units.meter)
            M = np.exp(eps * (Z-Z[0]).to('m')).to('dimensionless')

            # dM is the mass contribution at each level, with 1 at the origin level. 
            M[0] = 0
            dM = np.gradient(M)

            # parcel mass is a  sum of all the dM's at each level
            # conserved linearly-mixed variables like h are weighted averages 
            hent = np.cumsum(dM*h) / np.cumsum(dM)

            ax.plot( hent[0:ELindex+3]/1000., p[0:ELindex+3], linewidth=0.5, color='g')
        ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h). 
    dZ = 100 *units.meter

    # depict displacements at sounding levels nearest these target levels
    targetlevels = [900,800,700,600,500,400,300,200]*units.hPa
    for ilev in targetlevels:
        idx = np.argmin(np.abs(p - ilev))

        # dT: Dry lapse rate dT/dz_dry is -g/Cp
        dT = (-g/Cp_d *dZ).to('kelvin')    
        Tdisp = T[idx].to('kelvin') + dT

        # dp: hydrostatic
        rho = (p[idx]/Rd/T[idx])
        dp = -rho*g*dZ

        # dhsat
        #qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T)     ,p)
        dqs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Tdisp) ,p[idx]+dp) -qs[idx]
        dhs = g*dZ + Cp_d*dT + Lv*dqs

        # Whiskers on the data plots
        ax.plot( (hs[idx]+dhs*(-1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r')  
        ax.plot( (s [idx]    *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r')  
        ax.plot( (h [idx]    *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='b')  

        # annotation to explain it 
        if ilev == 600*ilev.units:
            ax.plot(right_annot_loc*hs.units +dhs*(-1,1)/1000, p[idx]+dp*(-1,1), linewidth=3, color='r')  
            ax.annotate('+/- 100m', xy=(right_annot_loc,600), fontsize=8)
            ax.annotate('  internal', xy=(right_annot_loc,630), fontsize=8)
            ax.annotate('  waves', xy=(right_annot_loc,660), fontsize=8)


    ### Blue fill proportional to precipitable water, and blue annotation
    ax.fill_betweenx(p, s/1000., h/1000., where=h > s, facecolor='b', alpha=0.4)

    # Have to specify the top of the PW integral. 
    # I want whole atmosphere of course, but 200 hPa captures it all really. 
    #import metpy.calc as mpcalc
    p_PWtop = max(200*units.mbar, min(p) +1*units.mbar)
    PW = mpcalc.precipitable_water(Td,p, top=p_PWtop)
    PWs = mpcalc.precipitable_water(T,p, top=p_PWtop)
    CRH = (PW/PWs).magnitude *100. 

    # PW annotation arrow tip at 700 mb
    idx = np.argmin(np.abs(p - 700*p.units))
    centerblue = (s[idx]+h[idx])/2.0 /1000.

    ax.annotate('CWV='+str(round(PW.to('mm').magnitude, 1))+'mm',
                xy=(centerblue, 700), xytext=(285, 200), 
                color='blue', fontsize=15,
                arrowprops=dict(width = 1, edgecolor='blue', shrink=0.02),
                )
    ax.annotate('(' + str(round(CRH,1)) +'% of sat)',
                xy=(285, 230), color='blue', fontsize=12)



    ### Surface water values at 1C intervals, for eyeballing surface fluxes
    sT = np.trunc(T[0].to('degC'))
    sTint = int(sT.magnitude)

    for idT in [-2,0,2,4]:
        ssTint = sTint + idT # UNITLESS degC integers, for labels

        # Kelvin values for computations
        ssTC = ssTint * units.degC
        ssTK = ssTC.to('kelvin')
        ss = g*Z[0] + Cp_d*ssTK 
        hs = ss     + Lv*mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(ssTK) ,p[0])

        ax.annotate(str(ssTint), xy=(ss/1000., p[0]+0*p.units), 
                    verticalalignment='top', horizontalalignment='center',
                    color='red', fontsize=7)
        ax.annotate(str(ssTint), xy=(hs/1000., p[0]+0*p.units), 
                    verticalalignment='top', horizontalalignment='center',
                    color='red', fontsize=9)
        ax.annotate('\u00b0C water', xy=(right_annot_loc, p[0]), verticalalignment='top', 
                    fontsize=10, color='r')
    if return_fig:
        return ax,fig
    def plotter(self, skew):
        boundary = self.plotMeta["boundary"]
        linestyle = self.plotMeta["linestyle"]
        linestyle["dry_adiabats"]["t0"] = np.arange(-100, 300,
                                                    10) * units.celsius
        self.fs = 16
        ## basic vars
        P, T, Td, U, V = self.read()
        ## LCL
        LCL_parcel_idx = 0
        LCL_P, LCL_T = mcalc.lcl(P[LCL_parcel_idx], T[LCL_parcel_idx],
                                 Td[LCL_parcel_idx])

        ## shift profile
        profP = P
        profT = T
        profTd = Td
        ## calc
        # LFC_P, LFC_T = mcalc.lfc(P, T, Td)
        while (True):
            try:
                parcel_prof = mcalc.parcel_profile(profP, T[0],
                                                   Td[0]).to('degC')
                break
            except:
                profP = profP[1:]
                profT = profT[1:]
                profTd = profTd[1:]
            finally:
                if (len(profP) == 0):
                    break

        ##special lines
        skew.ax.set_xticks(
            np.arange(boundary["T"][0], boundary["T"][1], boundary["T"][2]))
        skew.plot_dry_adiabats(**linestyle["dry_adiabats"])
        skew.plot_moist_adiabats(**linestyle["moist_adiabats"])
        skew.plot_mixing_lines(p=[1000, 500] * units.hPa,
                               **linestyle["mixing_lines"])

        skew.ax.set_xlabel('[$\degree C$]', fontsize=self.fs)
        skew.ax.set_ylabel('[$hPa$]', fontsize=self.fs)

        skew.ax.set_xlim(-40, 40)
        skew.ax.set_ylim(boundary["P"][0], boundary["P"][1])
        for i in np.arange(boundary["T"][0], boundary["T"][1],
                           boundary["T"][2] * 2):
            plt.fill_between(range(i, i + boundary["T"][2] + 1),
                             boundary["P"][0],
                             boundary["P"][1],
                             color='yellow',
                             alpha=0.7)

        ## main data
        skew.plot(P, T, 'b')
        skew.plot(P, Td, 'r')
        ### wind bar
        # idx  = mcalc.resample_nn_1d(P.m, np.array([1000, 975, 950, 925, 900, 850, 800, 750, 700, 650, 600, 500]))
        # skew.plot_barbs(P[idx], U[idx], V[idx], plot_units = units('knots'), xloc=1.05)
        skip = self.plotMeta["wind"]["skip"]
        skew.plot_barbs(P[P.m >= 100][::skip],
                        U[P.m >= 100][::skip],
                        V[P.m >= 100][::skip],
                        plot_units=units('m/s'),
                        xloc=1.05)

        skew.plot(LCL_P, LCL_T, 'ko', markerfacecolor='black')
        skew.plot(profP, parcel_prof, 'k', linewidth=2)
Exemple #18
0
def scalardata(field, valid_time, targetdir=".", debug=False):
    # Get color map, levels, and netCDF variable name appropriate for requested variable (from fieldinfo module).
    info = fieldinfo[field]
    if debug:
        print("scalardata: found", field, "fieldinfo:", info)
    cmap = colors.ListedColormap(info['cmap'])
    levels = info['levels']
    fvar = info['fname'][0]

    # Get narr file and filename.
    ifile = get(valid_time, targetdir=targetdir, narrtype=info['filename'])

    if debug:
        print("About to open " + ifile)
    nc = xarray.open_dataset(ifile)
    # Tried to rename vars and dimensions so metpy.parse_cf() would not warn "Found latitude/longitude values, assuming latitude_longitude for projection grid_mapping variable"
    # It didn't help. Only commenting out the metpy.parse_cf() line helped.
    # It didn't help with MetpyDeprecationWarning: Multidimensional coordinate lat assigned for axis "y". This behavior has been deprecated and will be removed in v1.0 (only one-dimensional coordinates will be available for the "y" axis) either
    #nc = nc.rename_vars({"gridlat_221": "lat", "gridlon_221" : "lon"})
    #nc = nc.rename_dims({"gridx_221": "x", "gridy_221" : "y"})
    #nc = nc.metpy.parse_cf() # TODO: figure out why filled contour didn't have .metpy.parse_cf()

    if fvar not in nc.variables:
        print(fvar, "not in", ifile, '. Try', nc.var())
        sys.exit(1)

    # Define data array. Speed and shear derived differently.
    # Define 'long_name' attribute
    #
    if field[0:5] == "speed":
        u = nc[info['fname'][0]]
        v = nc[info['fname'][1]]
        data = u  # copy metadata/coordinates from u
        data.values = wind_speed(u, v)
        data.attrs['long_name'] = "wind speed"
    elif field[0:3] == 'shr' and '_' in field:
        du, dv = shear(field,
                       valid_time=valid_time,
                       targetdir=targetdir,
                       debug=debug)
        ws = wind_speed(du, dv)
        attrs = {
            'long_name': 'wind shear',
            'units': str(ws.units),
            'verttitle': du.attrs["verttitle"]
        }
        # Use .m magnitude because you can't transfer units of pint quantity to xarray numpy array (xarray.values)
        data = xarray.DataArray(data=ws.m,
                                dims=du.dims,
                                coords=du.coords,
                                name=field,
                                attrs=attrs)
    elif field == 'theta2':
        pres = nc[info['fname'][0]]
        temp = nc[info['fname'][1]]
        data = pres  # retain xarray metadata/coordinates
        theta = potential_temperature(pres, temp)
        data.values = theta
        data.attrs['units'] = str(theta.units)
        data.attrs['long_name'] = 'potential temperature'
    elif field == 'thetae2':
        pres = nc[info['fname'][0]]
        temp = nc[info['fname'][1]]
        dwpt = nc[info['fname'][2]]
        data = pres  # retain xarray metadata/coordinates
        thetae = equivalent_potential_temperature(pres, temp, dwpt)
        data.values = thetae
        data.attrs['units'] = str(thetae.units)
        data.attrs['long_name'] = 'equivalent potential temperature'
    elif field == 'scp' or field == 'stp' or field == 'tctp':
        cape = nc[info['fname'][0]]
        cin = nc[info['fname'][1]]
        ifile = get(valid_time, targetdir=targetdir, narrtype=narrFlx)
        ncFlx = xarray.open_dataset(ifile).metpy.parse_cf()
        srh = ncFlx[info['fname'][2]]
        shear_layer = info['fname'][3]
        bulk_shear = scalardata(shear_layer,
                                valid_time,
                                targetdir=targetdir,
                                debug=debug)
        lifted_condensation_level_height = scalardata('zlcl',
                                                      valid_time,
                                                      targetdir=targetdir,
                                                      debug=debug)

        if field == 'scp':
            # In SPC help, cin is positive in SCP formulation.
            cin_term = -40 / cin
            cin_term = cin_term.where(cin < -40, other=1)
            scp = supercell_composite(cape, srh,
                                      bulk_shear) * cin_term.metpy.unit_array
            attrs = {
                'units': str(scp.units),
                'long_name': 'supercell composite parameter'
            }
            data = xarray.DataArray(data=scp,
                                    dims=cape.dims,
                                    coords=cape.coords,
                                    name=field,
                                    attrs=attrs)
        if field == 'stp':
            cin_term = (200 + cin) / 150
            cin_term = cin_term.where(cin <= -50, other=1)
            cin_term = cin_term.where(cin >= -200, other=0)
            # CAPE, srh, bulk_shear, cin may be one vertical level, but LCL may be multiple heights.
            # xarray.broadcast() makes them all multiple heights with same shape, so significant_tornado doesn't
            # complain about expecting lat/lon 2 dimensions and getting 3 dimensions..
            (cape, lifted_condensation_level_height, srh, bulk_shear,
             cin_term) = xarray.broadcast(cape,
                                          lifted_condensation_level_height,
                                          srh, bulk_shear, cin_term)
            stp = significant_tornado(cape, lifted_condensation_level_height,
                                      srh,
                                      bulk_shear) * cin_term.metpy.unit_array
            attrs = {
                'units': str(stp.units),
                'long_name': 'significant tornado parameter',
                'verttitle':
                lifted_condensation_level_height.attrs['verttitle']
            }
            data = xarray.DataArray(data=stp,
                                    dims=cape.dims,
                                    coords=cape.coords,
                                    name=field,
                                    attrs=attrs)
        if field == 'tctp':
            tctp = srh / (40 * munits['m**2/s**2']) * bulk_shear / (
                12 * munits['m/s']) * (2000 - lifted_condensation_level_height
                                       ) / (1400 * munits.m)
            # But NARR storm relative helicity (srh) is 0-3 km AGL, while original TCTP expects 0-1 km AGL.
            # So the shear term is too large using the NARR srh. Normalize the srh term with a larger denominator.
            # In STP, srh is normalized by 150 m**2/s**2. Use that.
            tctp_0_3kmsrh = srh / (150 * munits['m**2/s**2']) * bulk_shear / (
                12 * munits['m/s']) * (2000 - lifted_condensation_level_height
                                       ) / (1400 * munits.m)
            attrs = {
                'units': 'dimensionless',
                'long_name': 'TC tornado parameter'
            }
            data = xarray.DataArray(data=tctp_0_3kmsrh,
                                    dims=cape.dims,
                                    coords=cape.coords,
                                    name=field,
                                    attrs=attrs)
    elif field == 'lcl':
        pres = nc[info['fname'][0]]
        temp = nc[info['fname'][1]]
        dwpt = nc[info['fname'][2]]
        LCL_pressure, LCL_temperature = lcl(pres.fillna(pres.mean()),
                                            temp.fillna(temp.mean()),
                                            dwpt.fillna(dwpt.mean()))
        # convert units to string or xarray.DataArray.metpy.unit_array dies with ttributeError: 'NoneType' object has no attribute 'evaluate'
        attrs = {
            "long_name": "lifted condensation level",
            "units": str(LCL_pressure.units),
            "from": "metpy.calc.lcl"
        }
        data = xarray.DataArray(data=LCL_pressure,
                                coords=pres.coords,
                                dims=pres.dims,
                                name='LCL',
                                attrs=attrs)
    elif field == 'zlcl':
        LCL_pressure = scalardata('lcl',
                                  valid_time,
                                  targetdir=targetdir,
                                  debug=debug)
        ifile = get(valid_time, targetdir=targetdir, narrtype=narr3D)
        nc3D = xarray.open_dataset(ifile).metpy.parse_cf()
        hgt3D = nc3D["HGT_221_ISBL"]
        data = pressure_to_height(LCL_pressure, hgt3D, targetdir=targetdir)
    else:
        data = nc[fvar]
    data = units(data, info, debug=debug)
    data = vertical(data, info, debug=debug)
    data = temporal(data, info, debug=debug)

    data.attrs['field'] = field
    data.attrs['ifile'] = os.path.realpath(ifile)
    data.attrs['levels'] = levels
    data.attrs['cmap'] = cmap

    if data.min() > levels[-1] or data.max() < levels[0]:
        print('levels', levels, 'out of range of data', data.min(), data.max())
        sys.exit(2)

    return data
##########################################################################
# Thermodynamic Calculations
# --------------------------
#
# Often times we will want to calculate some thermodynamic parameters of a
# sounding. The MetPy calc module has many such calculations already implemented!
#
# * **Lifting Condensation Level (LCL)** - The level at which an air parcel's
#   relative humidity becomes 100% when lifted along a dry adiabatic path.
# * **Parcel Path** - Path followed by a hypothetical parcel of air, beginning
#   at the surface temperature/pressure and rising dry adiabatically until
#   reaching the LCL, then rising moist adiabatically.

# Calculate the LCL
lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])

print(lcl_pressure, lcl_temperature)

# Calculate the parcel profile.
parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')

##########################################################################
#  Skew-T Plotting
# ------------------------
#
# Fiducial lines indicating dry adiabats, moist adiabats, and mixing ratio are
# useful when performing further analysis on the Skew-T diagram. Often the
# 0C isotherm is emphasized and areas of CAPE and CIN are shaded.

# Create a new figure. The dimensions here give a good aspect ratio
Exemple #20
0
def test_lcl():
    """Test LCL calculation."""
    lcl_pressure, lcl_temperature = lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC)
    assert_almost_equal(lcl_pressure, 864.761 * units.mbar, 2)
    assert_almost_equal(lcl_temperature, 17.676 * units.degC, 2)
Exemple #21
0
    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]
Exemple #22
0
##########################################################################
# Thermodynamic Calculations
# --------------------------
#
# Often times we will want to calculate some thermodynamic parameters of a
# sounding. The MetPy calc module has many such calculations already implemented!
#
# * **Lifting Condensation Level (LCL)** - The level at which an air parcel's
#   relative humidity becomes 100% when lifted along a dry adiabatic path.
# * **Parcel Path** - Path followed by a hypothetical parcel of air, beginning
#   at the surface temperature/pressure and rising dry adiabatically until
#   reaching the LCL, then rising moist adiabatially.

# Calculate the LCL
lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])

print(lcl_pressure, lcl_temperature)

# Calculate the parcel profile.
parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')

##########################################################################
# Basic Skew-T Plotting
# ---------------------
#
# The Skew-T (log-P) diagram is the standard way to view rawinsonde data. The
# y-axis is height in pressure coordinates and the x-axis is temperature. The
# y coordinates are plotted on a logarithmic scale and the x coordinate system
# is skewed. An explanation of skew-T interpretation is beyond the scope of this
# tutorial, but here we will plot one that can be used for analysis or
Exemple #23
0
def test_lcl_convergence():
    """Test LCL calculation convergence failure."""
    with pytest.raises(RuntimeError):
        lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC, max_iters=2)
Exemple #24
0
##########################################################################
# Thermodynamic Calculations
# --------------------------
#
# Often times we will want to calculate some thermodynamic parameters of a
# sounding. The MetPy calc module has many such calculations already implemented!
#
# * **Lifting Condensation Level (LCL)** - The level at which an air parcel's
#   relative humidity becomes 100% when lifted along a dry adiabatic path.
# * **Parcel Path** - Path followed by a hypothetical parcel of air, beginning
#   at the surface temperature/pressure and rising dry adiabatically until
#   reaching the LCL, then rising moist adiabatially.

# Calculate the LCL level
parcel_lcl = mpcalc.lcl(p[0], T[0], Td[0])

# Determine the LCL temperature by lifting a parcel from the surface
# pressure and temperature via a dry adiabatic process.
lcl_temperature = mpcalc.dry_lapse(concatenate((p[0], parcel_lcl)),
                                   T[0])[-1].to('degC')

print(parcel_lcl, lcl_temperature)

# Calculate the parcel profile.
parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')

##########################################################################
# Basic Skew-T Plotting
# ---------------------
#
Exemple #25
0
def draw_sta_skewT(p=None,
                   T=None,
                   Td=None,
                   wind_speed=None,
                   wind_dir=None,
                   u=None,
                   v=None,
                   fcst_info=None,
                   output_dir=None):
    fig = plt.figure(figsize=(9, 9))
    skew = SkewT(fig, rotation=45)

    plt.rcParams['font.sans-serif'] = ['SimHei']  # 步骤一(替换sans-serif字体)
    plt.rcParams['axes.unicode_minus'] = False  # 步骤二(解决坐标轴负数的负号显示问题)

    # Plot the data using normal plotting functions, in this case using
    # log scaling in Y, as dictated by the typical meteorological plot.
    skew.plot(p, T, 'r')
    skew.plot(p, Td, 'g')
    skew.plot_barbs(p, u, v)
    skew.ax.set_ylim(1000, 100)
    skew.ax.set_xlim(-40, 60)

    # Calculate LCL height and plot as black dot. Because `p`'s first value is
    # ~1000 mb and its last value is ~250 mb, the `0` index is selected for
    # `p`, `T`, and `Td` to lift the parcel from the surface. If `p` was inverted,
    # i.e. start from low value, 250 mb, to a high value, 1000 mb, the `-1` index
    # should be selected.
    lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
    skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black')

    # Calculate full parcel profile and add to plot as black line
    prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')
    skew.plot(p, prof, 'k', linewidth=2)

    # Shade areas of CAPE and CIN
    skew.shade_cin(p, T, prof)
    skew.shade_cape(p, T, prof)

    # An example of a slanted line at constant T -- in this case the 0
    # isotherm
    skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)

    # Add the relevant special lines
    skew.plot_dry_adiabats()
    skew.plot_moist_adiabats()
    skew.plot_mixing_lines()

    #forecast information
    bax = plt.axes([0.12, 0.88, .25, .07], facecolor='#FFFFFFCC')
    bax.axis('off')
    bax.axis([0, 10, 0, 10])

    initTime = pd.to_datetime(str(
        fcst_info['forecast_reference_time'].values)).replace(
            tzinfo=None).to_pydatetime()
    if (sys.platform[0:3] == 'lin'):
        locale.setlocale(locale.LC_CTYPE, 'zh_CN.utf8')
    if (sys.platform[0:3] == 'win'):
        locale.setlocale(locale.LC_CTYPE, 'chinese')
    plt.text(2.5, 7.5, '起报时间: ' + initTime.strftime("%Y年%m月%d日%H时"), size=11)
    plt.text(2.5,
             5.0,
             '[' + str(fcst_info.attrs['model']) + '] ' +
             str(int(fcst_info['forecast_period'].values[0])) + '小时预报探空',
             size=11)
    plt.text(2.5,
             2.5,
             '预报点: ' + str(fcst_info.attrs['points']['lon']) + ', ' +
             str(fcst_info.attrs['points']['lat']),
             size=11)
    plt.text(2.5, 0.5, 'www.nmc.cn', size=11)
    utl.add_logo_extra_in_axes(pos=[0.1, 0.88, .07, .07],
                               which='nmc',
                               size='Xlarge')

    # Show the plot
    if (output_dir != None):
        plt.savefig(
            output_dir + '时间剖面产品_起报时间_' +
            str(fcst_info['forecast_reference_time'].values)[0:13] + '_预报时效_' +
            str(int(fcst_info.attrs['forecast_period'].values)) + '.png',
            dpi=200,
            bbox_inches='tight')
    else:
        plt.show()
Exemple #26
0
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
Exemple #27
0
def calculate_stability_indicies(
    ds,
    temp_name='temperature',
    td_name='dewpoint_temperature',
    p_name='pressure',
    rh_name='relative_humidity',
    moving_ave_window=0,
):
    """
    Function for calculating stability indices from sounding data.

    Parameters
    ----------
    ds : ACT dataset
        The dataset to compute the stability indicies of. Must have
        temperature, dewpoint, and pressure in vertical coordinates.
    temp_name : str
        The name of the temperature field.
    td_name : str
        The name of the dewpoint field.
    p_name : str
        The name of the pressure field.
    rh_name : str
        The name of the relative humidity field.
    moving_ave_window : int
        Number of points to do a moving average on sounding data to reduce
        noise. This is useful if noise in the sounding is preventing parcel
        ascent.

    Returns
    -------
    ds : ACT dataset
        An ACT dataset with additional stability indicies added.

    """
    if not METPY_AVAILABLE:
        raise ImportError(
            'MetPy need to be installed on your system to ' + 'calculate stability indices'
        )

    t = ds[temp_name]
    td = ds[td_name]
    p = ds[p_name]
    rh = ds[rh_name]

    if not hasattr(t, 'units'):
        raise AttributeError('Temperature field must have units' + ' for ACT to discern!')

    if not hasattr(td, 'units'):
        raise AttributeError('Dewpoint field must have units' + ' for ACT to discern!')

    if not hasattr(p, 'units'):
        raise AttributeError('Pressure field must have units' + ' for ACT to discern!')
    if t.units == 'C':
        t_units = units.degC
    else:
        t_units = getattr(units, t.units)

    if td.units == 'C':
        td_units = units.degC
    else:
        td_units = getattr(units, td.units)

    p_units = getattr(units, p.units)
    rh_units = getattr(units, rh.units)

    # Sort all values by decreasing pressure
    t_sorted = np.array(t.values)
    td_sorted = np.array(td.values)
    p_sorted = np.array(p.values)
    rh_sorted = np.array(rh.values)
    ind_sort = np.argsort(p_sorted)
    t_sorted = t_sorted[ind_sort[-1:0:-1]]
    td_sorted = td_sorted[ind_sort[-1:0:-1]]
    p_sorted = p_sorted[ind_sort[-1:0:-1]]
    rh_sorted = rh_sorted[ind_sort[-1:0:-1]]

    if moving_ave_window > 0:
        t_sorted = np.convolve(t_sorted, np.ones((moving_ave_window,)) / moving_ave_window)
        td_sorted = np.convolve(td_sorted, np.ones((moving_ave_window,)) / moving_ave_window)
        p_sorted = np.convolve(p_sorted, np.ones((moving_ave_window,)) / moving_ave_window)
        rh_sorted = np.convolve(rh_sorted, np.ones((moving_ave_window,)) / moving_ave_window)

    t_sorted = t_sorted * t_units
    td_sorted = td_sorted * td_units
    p_sorted = p_sorted * p_units
    rh_sorted = rh_sorted * rh_units

    # Calculate mixing ratio
    mr = mpcalc.mixing_ratio_from_relative_humidity(p_sorted, t_sorted, rh_sorted)

    # Discussion of issue #361 use virtual temperature.
    vt = mpcalc.virtual_temperature(t_sorted, mr)

    t_profile = mpcalc.parcel_profile(p_sorted, t_sorted[0], td_sorted[0])

    # Calculate parcel trajectory
    ds['parcel_temperature'] = t_profile.magnitude
    ds['parcel_temperature'].attrs['units'] = t_profile.units

    # Calculate CAPE, CIN, LCL
    sbcape, sbcin = mpcalc.surface_based_cape_cin(p_sorted, vt, td_sorted)

    lcl = mpcalc.lcl(p_sorted[0], t_sorted[0], td_sorted[0])
    try:
        lfc = mpcalc.lfc(p_sorted[0], t_sorted[0], td_sorted[0])
    except IndexError:
        lfc = np.nan * p_sorted.units

    mucape, mucin = mpcalc.most_unstable_cape_cin(p_sorted, vt, td_sorted)

    where_500 = np.argmin(np.abs(p_sorted - 500 * units.hPa))
    li = t_sorted[where_500] - t_profile[where_500]

    ds['surface_based_cape'] = sbcape.magnitude
    ds['surface_based_cape'].attrs['units'] = 'J/kg'
    ds['surface_based_cape'].attrs['long_name'] = 'Surface-based CAPE'
    ds['surface_based_cin'] = sbcin.magnitude
    ds['surface_based_cin'].attrs['units'] = 'J/kg'
    ds['surface_based_cin'].attrs['long_name'] = 'Surface-based CIN'
    ds['most_unstable_cape'] = mucape.magnitude
    ds['most_unstable_cape'].attrs['units'] = 'J/kg'
    ds['most_unstable_cape'].attrs['long_name'] = 'Most unstable CAPE'
    ds['most_unstable_cin'] = mucin.magnitude
    ds['most_unstable_cin'].attrs['units'] = 'J/kg'
    ds['most_unstable_cin'].attrs['long_name'] = 'Most unstable CIN'
    ds['lifted_index'] = li.magnitude
    ds['lifted_index'].attrs['units'] = t_profile.units
    ds['lifted_index'].attrs['long_name'] = 'Lifted index'
    ds['level_of_free_convection'] = lfc.magnitude
    ds['level_of_free_convection'].attrs['units'] = lfc.units
    ds['level_of_free_convection'].attrs['long_name'] = 'Level of free convection'
    ds['lifted_condensation_level_temperature'] = lcl[1].magnitude
    ds['lifted_condensation_level_temperature'].attrs['units'] = lcl[1].units
    ds['lifted_condensation_level_temperature'].attrs[
        'long_name'
    ] = 'Lifted condensation level temperature'
    ds['lifted_condensation_level_pressure'] = lcl[0].magnitude
    ds['lifted_condensation_level_pressure'].attrs['units'] = lcl[0].units
    ds['lifted_condensation_level_pressure'].attrs[
        'long_name'
    ] = 'Lifted condensation level pressure'
    return ds
Exemple #28
0
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)
Exemple #29
0
def test_lcl():
    """Test LCL calculation."""
    l = lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC)
    assert_almost_equal(l, 864.761 * units.mbar, 2)
Exemple #30
0
def test_lcl():
    """Test LCL calculation."""
    lcl_pressure, lcl_temperature = lcl(1000. * units.mbar, 30. * units.degC, 20. * units.degC)
    assert_almost_equal(lcl_pressure, 864.761 * units.mbar, 2)
    assert_almost_equal(lcl_temperature, 17.676 * units.degC, 2)
Exemple #31
0
    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()
Exemple #32
0
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
Exemple #33
0
###########################################
# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(9, 9))
skew = SkewT(fig, rotation=45)

# Plot the data using normal plotting functions, in this case using
# log scaling in Y, as dictated by the typical meteorological plot
skew.plot(p, T, 'r')
skew.plot(p, Td, 'g')
skew.plot_barbs(p, u, v)
skew.ax.set_ylim(1000, 100)
skew.ax.set_xlim(-40, 60)

# Calculate LCL height and plot as black dot
l = mpcalc.lcl(p[0], T[0], Td[0])
lcl_temp = mpcalc.dry_lapse(concatenate((p[0], l)), T[0])[-1].to('degC')
skew.plot(l, lcl_temp, 'ko', markerfacecolor='black')

# Calculate full parcel profile and add to plot as black line
prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')
skew.plot(p, prof, 'k', linewidth=2)

# Example of coloring area between profiles
greater = T >= prof
skew.ax.fill_betweenx(p, T, prof, where=greater, facecolor='blue', alpha=0.4)
skew.ax.fill_betweenx(p, T, prof, where=~greater, facecolor='red', alpha=0.4)

# An example of a slanted line at constant T -- in this case the 0
# isotherm
l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
Exemple #34
0
def atmCalc(height, temp, humid):
    print("ATMCALC", height, temp, humid, file=sys.stderr)
    mtny = windh(MTNX, height, ratio=1,
                 yoffset=0)

    windx = XVALUES
    windy = windh(windx, height)

    temp_ = temp * units.degC
    initp = mc.height_to_pressure_std(windy[0] * units.meters)
    dewpt = mc.dewpoint_from_relative_humidity(temp_, humid / 100.)
    lcl_ = mc.lcl(initp, temp_, dewpt, max_iters=50, eps=1e-5)
    LCL = mc.pressure_to_height_std(lcl_[0])

    if (lcl_[0] > mc.height_to_pressure_std(max(windy) * units.meters)
            and LCL > windy[0] * units.meters * 1.000009):
        # add LCL to x
        xlcl = windh(LCL.to('meters').magnitude, height, inv=True)
        windx = np.sort(np.append(windx, xlcl))
        windy = windh(windx, height)

    pressures = mc.height_to_pressure_std(windy * units.meters)

    wvmr0 = mc.mixing_ratio_from_relative_humidity(initp, temp_, humid / 100.)

    # now calculate the air parcel temperatures and RH at each position
    if (lcl_[0] <= min(pressures)):
        T = mc.dry_lapse(pressures, temp_)
        RH = [
            mc.relative_humidity_from_mixing_ratio(
                wvmr0, t, p) for t, p in zip(
                T, pressures)]
    else:
        mini = np.argmin(pressures)
        p1 = pressures[:mini + 1]
        p2 = pressures[mini:]  # with an overlap
        p11 = p1[p1 >= lcl_[0] * .9999999]  # lower (with tol) with lcl
        p12 = p1[p1 < lcl_[0] * 1.000009]  # upper (with tol) with lcl
        T11 = mc.dry_lapse(p11, temp_)
        T12 = mc.moist_lapse(p12, lcl_[1])
        T1 = concatenate((T11[:-1], T12))
        T2 = mc.dry_lapse(p2, T1[-1])
        T = concatenate((T1, T2[1:]))
        wvmrtop = mc.saturation_mixing_ratio(pressures[mini], T[mini])
        RH=[]
        for i in range(len(pressures)):
            if pressures[i] > lcl_[0] and i <= mini:
                v=mc.relative_humidity_from_mixing_ratio(pressures[i], T[i], wvmr0)
            else:
                if i < mini:
                    v=1
                else:
                    v=mc.relative_humidity_from_mixing_ratio(pressures[i], T[i], wvmrtop)
            RH.append(v)
        
        #RH = [mc.relative_humidity_from_mixing_ratio(*tp, wvmr0) if tp[1] > lcl_[
            #0] and i <= mini else 1.0 if i < mini else
            #mc.relative_humidity_from_mixing_ratio(*tp, wvmrtop)
            #for i, tp in enumerate(zip(pressures, T))]

    RH = concatenate(RH)
    return windx, mtny, windy, lcl_, LCL, T.to("degC"), RH
Exemple #35
0
    def plot_skewt(self):
        """
        :param adjusted_data: receives the post processed dataframe
        :param valid:
        :return:
        """
        for area in self.airports:
            for airport in self.airports[area]:
                lon = self.airports[area][airport]['lon']
                lat = self.airports[area][airport]['lat']
                pressure_levels = self.levels * units.hPa
                tair = list(
                    self.create_profile_variable(self.tair, lat,
                                                 lon).values()) * units.degC
                dewp = list(
                    self.create_profile_variable(self.dewp, lat,
                                                 lon).values()) * units.degC
                u_wnd = list(self.create_profile_variable(self.u_wnd, lat, lon).values()) * \
                        units('meters / second').to('knots')
                v_wnd = list(self.create_profile_variable(self.v_wnd, lat, lon).values()) * \
                        units('meters / second').to('knots')

                # Create a new figure. The dimensions here give a good aspect ratio.
                fig = plt.figure(figsize=(12, 9))
                skew = SkewT(fig, rotation=45)

                # Plot the data using normal plotting functions, in this case using
                # log scaling in Y, as dictated by the typical meteorological plot
                skew.plot(pressure_levels, tair, 'r')
                skew.plot(pressure_levels, dewp, 'g')
                skew.plot_barbs(pressure_levels, u_wnd, v_wnd)
                skew.ax.set_ylim(1020, 100)
                skew.ax.set_xlim(-40, 60)

                # Calculate LCL height and plot as black dot
                lcl_pressure, lcl_temperature = mpcalc.lcl(
                    pressure_levels[0], tair[0], dewp[0])
                skew.plot(lcl_pressure,
                          lcl_temperature,
                          'ko',
                          markerfacecolor='black')

                # Calculate full parcel profile and add to plot as black line
                prof = mpcalc.parcel_profile(pressure_levels, tair[0], dewp[0])
                skew.plot(pressure_levels, prof, 'k', linewidth=2)

                # An example of a slanted line at constant T -- in this case the 0
                # isotherm
                skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)

                # Add the relevant special lines
                skew.plot_dry_adiabats()
                skew.plot_moist_adiabats()
                skew.plot_mixing_lines()

                skew.shade_cape(pressure_levels, tair, prof)
                skew.shade_cin(pressure_levels, tair, prof)
                plt.title(
                    f'Perfil vertical (GFS) de {airport} valido para {self.time_stamp}',
                    fontsize=16,
                    ha='center')
                sounding_output_path = f'{self.output_path}/{area}/{airport}'
                Path(sounding_output_path).mkdir(parents=True, exist_ok=True)
                plt.savefig(
                    f'{sounding_output_path}/sounding_{airport}_{self.time_step:02d}.jpg'
                )

        return skew
Exemple #36
0
skew.plot(p, Td, 'g')
skew.plot_barbs(p, u, v)
skew.ax.set_ylim(1000, 100)    # Y limits from 1000 in the botton to 100 millibars to the top
skew.ax.set_xlim(-40, 60)      # X limits

# Add the relevant special lines\n",
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()

plt.show()

# Calculate LCL height and plot as black dot\n",
from metpy.calc import lcl, dry_lapse
from metpy.units import units, concatenate
l = lcl(p[0], T[0], Td[0])     # we can calculate the lcl level from the starting p, T, Td
lcl_temp = dry_lapse(concatenate((p[0], l)), T[0])[-1].to('degC')  # Temperature of the lcl using the dry lapse
skew.plot(l, lcl_temp, 'ko', markerfacecolor='black') # plot the lcl level on the temperature with a black circle (ko) filled

# Calculate full parcel profile and add to plot as black line\n",
from metpy.calc import parcel_profile
prof = parcel_profile(p, T[0], Td[0]).to('degC')
skew.plot(p, prof, 'k', linewidth=2)

# Example of coloring area between profiles
skew.ax.fill_betweenx(p, T, prof, where=T>= prof, facecolor='blue', alpha=0.4)
skew.ax.fill_betweenx(p, T, prof, where=T< prof, facecolor='red', alpha=0.4)

# # An example of a slanted line at constant T -- in this case the 0 isotherm
level = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)   # highligh the 0 degree isoterm
######################################################
## Calculate thermodymanic and convective variables ##
######################################################

theta = np.array(
    mcalc.potential_temperature(pres * units.mbar,
                                (Tmean + 273.15) * units.kelvin))
Td = np.array(mcalc.dewpoint_rh(Tmean * units.degC, RHmean / 100.))
ws = np.array(
    mcalc.saturation_mixing_ratio(pres * units.mbar,
                                  (Tmean + 273.15) * units.kelvin))
w = np.multiply(RHmean / 100., ws * 1000.)

# check for RH
if isRH:
    lclpres, lcltemp = mcalc.lcl(pres[0] * units.mbar, Tmean[0] * units.degC,
                                 Td[0] * units.degC)
    print 'LCL Pressure: {}'.format(lclpres)
    print 'LCL Temperature: {}'.format(lcltemp)

    # parcel profile
    # determine if there are points sampled above lcl
    ilcl = np.squeeze(np.where((pres * units.mbar) <= lclpres))
    # if not, entire profile dry adiabatic
    if ilcl.size == 0:
        prof = mcalc.dry_lapse(pres * units.mbar,
                               Tmean[0] * units.degC).to('degC')
        isbelowlcl = 1
    # if there are, need to concat dry & moist profile ascents
    else:
        ilcl = ilcl[0]
        prof_dry = mcalc.dry_lapse(pres[:ilcl] * units.mbar,