{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", "\n", "
\n", "\"Unidata\n", "
\n", "\n", "

python-awips: Working with Upper Air Obs

\n", "

Unidata AMS 2021 Student Conference

\n", "\n", "
\n", "
\n", "\n", "---\n", "\n", "
\"Upper
\n", "\n", "\n", "### Focuses\n", "\n", "* Retreive upper air vertical profile data from EDEX server.\n", "* Use EDEX to get the pressure, temperature, dewpoint lines and wind profile data for the Upper Air observation.\n", "* Plot a Skew-T/Log-P plot with Hodograph using Matplotlib and Metpy.\n", "\n", "\n", "### Objectives\n", "\n", "1. [Define Data Request](#1.-Define-Data-Request)\n", "1. [Limit Results Based on Time](#2.-Limit-Results-Based-on-Time)\n", "1. [Get the Data!](#3.-Get-the-Data!)\n", "1. [Prepare the Data](#4.-Prepare-the-Data)\n", "1. [Draw the Plot](#5.-Draw-the-Plot)\n", "\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from awips.dataaccess import DataAccessLayer\n", "import matplotlib.tri as mtri\n", "import matplotlib.pyplot as plt\n", "from mpl_toolkits.axes_grid1.inset_locator import inset_axes\n", "import numpy as np\n", "import math\n", "from metpy.calc import wind_speed, wind_components, lcl, dry_lapse, parcel_profile\n", "from metpy.plots import SkewT, Hodograph\n", "from metpy.units import units, concatenate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Define Data Request\n", "\n", "If you read through the [python-awips: How to Access Data](https://nbviewer.jupyter.org/github/Unidata/pyaos-ams-2021/blob/master/notebooks/dataAccess/python-awips-HowToAccessData.ipynb) training, you will know that we need to set an EDEX url to access our server, and then we create a data request. In this example we use *bufrua* as the data type to define our request. We also set some *parameters* and the *location name*.\n", "The **bufrua** plugin returns separate objects for parameters at **mandatory levels** and at **significant temperature levels**. For the Skew-T/Log-P plot, significant temperature levels are used to plot the pressure, temperature, and dewpoint lines, while mandatory levels are used to plot the wind profile." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create an EDEX data request\n", "DataAccessLayer.changeEDEXHost(\"edex-cloud.unidata.ucar.edu\")\n", "request = DataAccessLayer.newDataRequest()\n", "request.setDatatype(\"bufrua\")\n", "\n", "# set parameters, including the mandatory and significant parameters\n", "MAN_PARAMS = set(['prMan', 'htMan', 'tpMan', 'tdMan', 'wdMan', 'wsMan'])\n", "SIGT_PARAMS = set(['prSigT', 'tpSigT', 'tdSigT'])\n", "request.setParameters(\"wmoStaNum\", \"validTime\", \"rptType\", \"staElev\", \"numMand\",\n", " \"numSigT\", \"numSigW\", \"numTrop\", \"numMwnd\", \"staName\")\n", "request.getParameters().extend(MAN_PARAMS)\n", "request.getParameters().extend(SIGT_PARAMS)\n", "\n", "# Set station ID (not name)\n", "request.setLocationNames(\"72562\") #KLBF\n", "\n", "# Take a look at our request\n", "print(request)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---\n", "\n", "## 2. Limit Results Based on Time\n", "\n", "There can be data from multiple time points, so let's limit to the most recent time." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get all times\n", "datatimes = DataAccessLayer.getAvailableTimes(request)\n", "\n", "# Get most recent record\n", "response = DataAccessLayer.getGeometryData(request,times=datatimes[-1].validPeriod)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---\n", "\n", "## 3. Get the Data!\n", "\n", "Now that we have the the data response, create arrays to store all the vertical profile data. Then, cycle through the objects in the response to populate all the data arrays." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize data arrays\n", "tdMan,tpMan,prMan,wdMan,wsMan = np.array([]),np.array([]),np.array([]),np.array([]),np.array([])\n", "prSig,tpSig,tdSig = np.array([]),np.array([]),np.array([])\n", "manGeos = []\n", "sigtGeos = []\n", "\n", "# Build arrays\n", "for ob in response:\n", " parm_array = ob.getParameters()\n", " if set(parm_array) & MAN_PARAMS:\n", " manGeos.append(ob)\n", " prMan = np.append(prMan,ob.getNumber(\"prMan\"))\n", " tpMan, tpUnit = np.append(tpMan,ob.getNumber(\"tpMan\")), ob.getUnit(\"tpMan\")\n", " tdMan, tdUnit = np.append(tdMan,ob.getNumber(\"tdMan\")), ob.getUnit(\"tdMan\")\n", " wdMan = np.append(wdMan,ob.getNumber(\"wdMan\"))\n", " wsMan, wsUnit = np.append(wsMan,ob.getNumber(\"wsMan\")), ob.getUnit(\"wsMan\")\n", " continue\n", " if set(parm_array) & SIGT_PARAMS:\n", " sigtGeos.append(ob)\n", " prSig = np.append(prSig,ob.getNumber(\"prSigT\"))\n", " tpSig = np.append(tpSig,ob.getNumber(\"tpSigT\"))\n", " tdSig = np.append(tdSig,ob.getNumber(\"tdSigT\"))\n", " continue" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---\n", "\n", "## 4. Prepare the Data\n", "\n", "Sort and filter the data to prepare it for plotting. Then assign units and convert them to degrees Celsius if necessary." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Sort mandatory levels (but not sigT levels) because of the 1000.MB interpolation inclusion\n", "ps = prMan.argsort()[::-1]\n", "wpres = prMan[ps]\n", "direc = wdMan[ps]\n", "spd = wsMan[ps]\n", "tman = tpMan[ps]\n", "dman = tdMan[ps]\n", "\n", "# Flag missing data\n", "prSig[prSig <= -9999] = np.nan\n", "tpSig[tpSig <= -9999] = np.nan\n", "tdSig[tdSig <= -9999] = np.nan\n", "wpres[wpres <= -9999] = np.nan\n", "tman[tman <= -9999] = np.nan\n", "dman[dman <= -9999] = np.nan\n", "direc[direc <= -9999] = np.nan\n", "spd[spd <= -9999] = np.nan\n", "\n", "# assign units\n", "p = (prSig/100) * units.mbar\n", "wpres = (wpres/100) * units.mbar\n", "u,v = wind_components(spd * units.knots, np.deg2rad(direc))\n", "\n", "if tpUnit == 'K':\n", " T = (tpSig-273.15) * units.degC\n", " Td = (tdSig-273.15) * units.degC\n", " tman = tman * units.degC\n", " dman = dman * units.degC" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---\n", "\n", "## 5. Draw the Plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create SkewT/LogP\n", "plt.rcParams['figure.figsize'] = (10, 12)\n", "skew = SkewT()\n", "skew.plot(p, T, 'r', linewidth=2)\n", "skew.plot(p, Td, 'g', linewidth=2)\n", "skew.plot_barbs(wpres, u, v)\n", "skew.ax.set_ylim(1000, 100)\n", "skew.ax.set_xlim(-60, 30)\n", "\n", "title_string = \" T(F) Td \" \n", "title_string += \" \" + str(ob.getString(\"staName\"))\n", "title_string += \" \" + str(ob.getDataTime().getRefTime())\n", "title_string += \" (\" + str(ob.getNumber(\"staElev\")) + \"m elev)\"\n", "title_string += \"\\n\" + str(round(T[0].to('degF').item(),1))\n", "title_string += \" \" + str(round(Td[0].to('degF').item(),1))\n", "plt.title(title_string, loc='left')\n", "\n", "# Calculate LCL height and plot as black dot\n", "lcl_pressure, lcl_temperature = lcl(p[0], T[0], Td[0])\n", "skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black')\n", "\n", "# Calculate full parcel profile and add to plot as black line\n", "prof = parcel_profile(p, T[0], Td[0]).to('degC')\n", "skew.plot(p, prof, 'k', linewidth=2)\n", "\n", "# An example of a slanted line at constant T -- in this case the 0 isotherm\n", "l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)\n", "\n", "# Draw hodograph\n", "ax_hod = inset_axes(skew.ax, '30%', '30%', loc=3)\n", "h = Hodograph(ax_hod, component_range=max(wsMan))\n", "h.add_grid(increment=20)\n", "h.plot_colormapped(u, v, spd)\n", "\n", "# Show the plot\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---\n", "\n", "## See also\n", "\n", "Documentation for:\n", "\n", "* [awips.DataAccessLayer](http://unidata.github.io/python-awips/api/DataAccessLayer.html)\n", "* [awips.PyGeometryData](http://unidata.github.io/python-awips/api/PyGeometryData.html)\n", "* [matplotlib.pyplot](https://matplotlib.org/3.3.3/api/_as_gen/matplotlib.pyplot.html)\n", "* [metpy.plots.SkewT](https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.SkewT.html)\n", "* [metpy.plots.Hodograph](https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.Hodograph.html)\n", "\n", "### Related Notebooks:\n", "\n", "* [python-awips: How to Access Data](https://nbviewer.jupyter.org/github/Unidata/pyaos-ams-2021/blob/master/notebooks/dataAccess/python-awips-HowToAccessData.ipynb)\n", "\n", "\n", "Top" ] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:pyaos-ams-2021]", "language": "python", "name": "conda-env-pyaos-ams-2021-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1" } }, "nbformat": 4, "nbformat_minor": 1 }