Sounding_Plotter

Sounding Plotter

This should be run as a script from the command line - not as a notebook.

Download and plot the most recent sounding data for a specified site.

Provides a simple command line interface to specify a site. Using the current UTC time, the script calculates what the most recent sounding should be and requests it from the Wyoming archive using Siphon.

Do the needed imports

In [ ]:
import posixpath

import matplotlib.pyplot as plt
import metpy.calc as mpcalc
from metpy.plots import add_metpy_logo, add_timestamp, SkewT
from metpy.units import units
from siphon.simplewebservice.wyoming import WyomingUpperAir

This class encapsulates the code needed to upload an image to Google Drive

In [ ]:
class DriveUploader:
    def __init__(self, credsfile='mycreds.txt'):
        from pydrive.drive import GoogleDrive
        self.gdrive = GoogleDrive(self._get_auth(credsfile))

    def _get_auth(self, credsfile):
        from pydrive.auth import GoogleAuth
        gauth = GoogleAuth()

        # Try to load saved client credentials
        gauth.LoadCredentialsFile(credsfile)
        if gauth.credentials is None:
            # Authenticate if they're not there
            gauth.LocalWebserverAuth()
        elif gauth.access_token_expired:
            # Refresh them if expired
            gauth.Refresh()
        else:
            # Initialize the saved creds
            gauth.Authorize()
        # Save the current credentials to a file
        gauth.SaveCredentialsFile(credsfile)
        return gauth

    def _get_first_file_id(self, title, parent, **kwargs):
        query = "title='{}' and '{}' in parents".format(title, parent)
        for k, v in kwargs.items():
            query += " and {}='{}'".format(k, v)
        res = next(self.gdrive.ListFile({'q': query}))
        if res:
            return res[0]['id']
        return None

    def get_folder(self, path):
        parent = 'root'
        for part in path.split('/'):
            if not part:
                continue
            parent = self._get_first_file_id(part, parent,
                                             mimeType='application/vnd.google-apps.folder')
        return parent

    def create_or_get_file(self, path):
        pathname, filename = posixpath.split(path)
        folder = self.get_folder(pathname)
        create_file_args = {'parents': [{'kind': 'drive#fileLink', 'id': folder}]}

        file_id = self._get_first_file_id(filename, folder)
        if file_id is not None:
            create_file_args['id'] = file_id
        return self.gdrive.CreateFile(create_file_args)

    def upload_to(self, local_path, remote_path):
        f = self.create_or_get_file(remote_path)
        f.SetContentFile(local_path)
        f['title'] = posixpath.basename(remote_path)
        f.Upload()

This function takes care of actually generating a skewT from the DataFrame

In [ ]:
def plot_skewt(df):
    # We will pull the data out of the example dataset into individual variables
    # and assign 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
    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, 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, '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()

    return skew


def make_name(site, time):
    return '{site}_{dt:%Y%m%d_%H%M}.png'.format(site=site, dt=time)

This is where the command line script will actually enter, and handles parsing command line arguments and driving everything else.

In [ ]:
if __name__ == '__main__':
    import argparse
    from datetime import datetime, timedelta
    import tempfile

    # Set up argument parsing for the script. Provides one argument for the site, and another
    # that controls whether the plot should be shown or saved as an image.
    parser = argparse.ArgumentParser(description='Download sounding data and plot.')
    parser.add_argument('-s', '--site', help='Site to obtain data for', type=str,
                        default='DNR')
    parser.add_argument('--show', help='Whether to show the plot rather than save to disk',
                        action='store_true')
    parser.add_argument('-d', '--date', help='Date and time to request data for in YYYYMMDDHH.'
                        ' Defaults to most recent 00/12 hour.', type=str)
    parser.add_argument('-g', '--gdrive', help='Google Drive upload path', type=str)
    parser.add_argument('-f', '--filename', help='Image filename', type=str)
    args = parser.parse_args()

    if args.date:
        request_time = datetime.strptime(args.date, '%Y%m%d%H')
    else:
        # Figure out the most recent sounding, 00 or 12. Subtracting two hours
        # helps ensure that we choose a time with data available.
        now = datetime.utcnow() - timedelta(hours=2)
        request_time = now.replace(hour=(now.hour // 12) * 12, minute=0, second=0)

    # Request the data and plot
    df = WyomingUpperAir.request_data(request_time, args.site)
    skewt = plot_skewt(df)

    # Add the timestamp for the data to the plot
    add_timestamp(skewt.ax, request_time, y=1.02, x=0, ha='left', fontsize='large')
    skewt.ax.set_title(args.site)

    if args.show:
        plt.show()
    else:
        fname = args.filename if args.filename else make_name(args.site, request_time)
        if args.gdrive:
            uploader = DriveUploader()
            with tempfile.NamedTemporaryFile(suffix='.png') as f:
                skewt.ax.figure.savefig(f.name)
                uploader.upload_to(f.name, posixpath.join(args.gdrive, fname))
        else:
            skewt.ax.figure.savefig(make_name(args.site, request_time))