diff --git a/pages/workshop/Surface_Data/Advanced StationPlots with Mesonet Data.ipynb b/pages/workshop/Surface_Data/Advanced StationPlots with Mesonet Data.ipynb
index e2469265..c0111545 100644
--- a/pages/workshop/Surface_Data/Advanced StationPlots with Mesonet Data.ipynb
+++ b/pages/workshop/Surface_Data/Advanced StationPlots with Mesonet Data.ipynb
@@ -21,11 +21,6 @@
"\n",
"
\n",
"\n",
- "## Overview:\n",
- "\n",
- "* **Teaching:** 30 minutes\n",
- "* **Exercises:** 35 minutes\n",
- "\n",
"### Questions\n",
"1. How do I read in complicated mesonet data with Pandas?\n",
"1. How do I merge multiple Pandas DataFrames?\n",
@@ -519,9 +514,9 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python [conda env:gallery]",
+ "display_name": "Python 3",
"language": "python",
- "name": "conda-env-gallery-py"
+ "name": "python3"
},
"language_info": {
"codemirror_mode": {
@@ -533,7 +528,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.3"
+ "version": "3.7.6"
}
},
"nbformat": 4,
diff --git a/pages/workshop/Surface_Data/Declarative Surface Observations.ipynb b/pages/workshop/Surface_Data/Declarative Surface Observations.ipynb
new file mode 100644
index 00000000..52f5ed18
--- /dev/null
+++ b/pages/workshop/Surface_Data/Declarative Surface Observations.ipynb
@@ -0,0 +1,515 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "\n",
+ "
\n",
+ "
![\"Unidata](\"https://raw.githubusercontent.com/Unidata/MetPy/master/src/metpy/plots/_static/unidata_150x150.png\")
\n",
+ "
\n",
+ "\n",
+ "
Working with Surface Observations in Siphon and MetPy
\n",
+ "
Unidata Python Workshop
\n",
+ "\n",
+ "
\n",
+ "
\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "\n",
+ "### Questions\n",
+ "1. What's the best way to get surface station data from a THREDDS data server?\n",
+ "1. What's the best way to make a station plot of data?\n",
+ "1. How can I request a time series of data for a single station?\n",
+ "\n",
+ "### Objectives\n",
+ "1. Getting METARs from THREDDS\n",
+ "1. Parse the Data\n",
+ "1. Making a Station Plot\n",
+ "1. Time Series Request and Plot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Getting METARs from THREDDS\n",
+ "## 1. Getting METARs from THREDDS\n",
+ "\n",
+ "We can get the current METARS from the THREDDS test server (it's not yet available on the main thredds.ucar.edu). Head over to https://thredds-test.unidata.ucar.edu/ and navigate to the NOAAport products and find the files."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from siphon.catalog import TDSCatalog"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cat = TDSCatalog('https://thredds-test.unidata.ucar.edu/thredds/catalog/noaaport/text/metar/catalog.xml')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ds = cat.datasets[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Ideally we would use remote_open, but because of a soon to be fixed bug we\n",
+ "# have to download locally and open that way.\n",
+ "# fobj = ds.remote_open()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ds.download()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Open the file and take a look - aren't you glad MetPy can parse this for you?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Top\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## 2. Parse the Data\n",
+ "MetPy can parse the METAR data for us into a dataframe that the declarative plotting inface can work with nicely."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from metpy.io import parse_metar_file"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df = parse_metar_file(ds.name)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's take a bit of a closer look at the columns that are available to use as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "list(df.columns)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Top\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## 3. Making a Station Plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from datetime import datetime, timedelta\n",
+ "\n",
+ "import cartopy.crs as ccrs\n",
+ "\n",
+ "from metpy.plots.declarative import *"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "obs = PlotObs()\n",
+ "obs.data = df\n",
+ "obs.time = datetime.utcnow()\n",
+ "obs.level = None\n",
+ "obs.fields = ['air_temperature']\n",
+ "obs.locations = ['NW']\n",
+ "obs.colors = ['tab:red']\n",
+ "obs.formats = [None]\n",
+ "obs.vector_field = ['eastward_wind', 'northward_wind']\n",
+ "obs.reduce_points = 0.5\n",
+ "\n",
+ "panel = MapPanel()\n",
+ "panel.area = 'ma'\n",
+ "panel.projection = ccrs.PlateCarree()\n",
+ "panel.layers = ['coastline', 'borders', 'states']\n",
+ "\n",
+ "panel.plots = [obs]\n",
+ "\n",
+ "pc = PanelContainer()\n",
+ "pc.size = (10, 10)\n",
+ "pc.panels = [panel]\n",
+ "\n",
+ "pc.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "
EXERCISE:\n",
+ "\n",
+ "Make a surface plot that plots:\n",
+ "\n",
+ "
\n",
+ " - Temperature in red, NW plot area
\n",
+ " - Dewpoint in green, SW plot area
\n",
+ " - Altimeter setting in black, NE plot area
\n",
+ " - Sky coverage in black, Center plot area
\n",
+ " - Set the title to something relevant
\n",
+ "
\n",
+ "\n",
+ "
BONUS: Format the altimeter setting in the \"traditional\" way (multiply by 10 and show only the last three digits of the integer value). i.e. 1014.56 becomes 145.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Make the observation plot\n",
+ "\n",
+ "# Make the map panel\n",
+ "\n",
+ "# Make the panel container\n",
+ "\n",
+ "# Show the plot\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " SOLUTION\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %load solutions/dec_basic_sfc_plot.py"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Top\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## 4. Time Series Request and Plot\n",
+ "Let's say we want the past days worth of data for a lat/lon point for the variables mean sea level pressure, air temperature, wind direction, and wind speed. We do not have time series plots in a declarative way yet, but these plots are relatively straight forward to create and it's another way we can query THREDDS to get data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "metar_cat_url = ('http://thredds.ucar.edu/thredds/catalog/'\n",
+ " 'irma/metar/catalog.xml?dataset=irma/metar/Metar_Station_Data_-_Irma_fc.cdmr')\n",
+ "\n",
+ "catalog = TDSCatalog(metar_cat_url)\n",
+ "\n",
+ "metar_dataset = catalog.datasets['Feature Collection']\n",
+ "\n",
+ "ncss = metar_dataset.subset()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define the time range we are interested in\n",
+ "end_time = datetime(2017, 9, 12, 0)\n",
+ "start_time = end_time - timedelta(days=2)\n",
+ "\n",
+ "# Build the query\n",
+ "query = ncss.query()\n",
+ "query.lonlat_point(-80.25, 25.8)\n",
+ "query.time_range(start_time, end_time)\n",
+ "query.variables('altimeter_setting', 'temperature', 'dewpoint',\n",
+ " 'wind_direction', 'wind_speed')\n",
+ "query.accept('csv')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get the data\n",
+ "data = ncss.get_data(query)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Make sure we got what we asked for\n",
+ "print(list(data.keys()))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Cleanup the Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df = pd.DataFrame(data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Parse the date time stamps\n",
+ "df['time'] = pd.to_datetime(df['time'].str.decode('utf-8'), infer_datetime_format=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Station names are bytes, we need to convert them to strings\n",
+ "df['station'] = df['station'].str.decode('utf-8')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Make a Time Series Plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ax = df.plot(x='time', y='wind_speed',\n",
+ " title=f\"{df['station'][0]} {df['time'][0]:%Y/%m/%d}\",\n",
+ " grid=True,\n",
+ " figsize=(10, 6))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from matplotlib.dates import DateFormatter, AutoDateLocator\n",
+ "# Improve on the default ticking\n",
+ "locator = AutoDateLocator()\n",
+ "hoursFmt = DateFormatter('%H')\n",
+ "ax.xaxis.set_major_locator(locator)\n",
+ "ax.xaxis.set_major_formatter(hoursFmt)\n",
+ "fig = ax.get_figure()\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "
EXERCISE:\n",
+ "
\n",
+ " - Pick a different location.
\n",
+ " - Plot temperature and dewpoint together on the same plot.
\n",
+ " - Use ax.set.xlabel and the corresponding y label to set sensible labels.
\n",
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Your code goes here\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %load solutions/pd_time_series.py"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Top\n",
+ "
"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "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.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/pages/workshop/Surface_Data/Surface Data with Siphon and MetPy.ipynb b/pages/workshop/Surface_Data/Surface Data with Siphon and MetPy.ipynb
index 8a025300..d6eed567 100644
--- a/pages/workshop/Surface_Data/Surface Data with Siphon and MetPy.ipynb
+++ b/pages/workshop/Surface_Data/Surface Data with Siphon and MetPy.ipynb
@@ -21,11 +21,6 @@
"\n",
"\n",
"\n",
- "## Overview:\n",
- "\n",
- "* **Teaching:** 20 minutes\n",
- "* **Exercises:** 20 minutes\n",
- "\n",
"### Questions\n",
"1. What's the best way to get surface station data from a THREDDS data server?\n",
"1. What's the best way to make a station plot of data?\n",
@@ -209,9 +204,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "scrolled": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
@@ -552,9 +545,9 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python [conda env:devel]",
+ "display_name": "Python 3",
"language": "python",
- "name": "conda-env-devel-py"
+ "name": "python3"
},
"language_info": {
"codemirror_mode": {
@@ -566,9 +559,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.3"
+ "version": "3.7.6"
}
},
"nbformat": 4,
- "nbformat_minor": 2
+ "nbformat_minor": 4
}
diff --git a/pages/workshop/Surface_Data/solutions/dec_basic_sfc_plot.py b/pages/workshop/Surface_Data/solutions/dec_basic_sfc_plot.py
new file mode 100644
index 00000000..ac63267f
--- /dev/null
+++ b/pages/workshop/Surface_Data/solutions/dec_basic_sfc_plot.py
@@ -0,0 +1,27 @@
+# Make the observation plot
+obs = PlotObs()
+obs.data = df
+obs.time = datetime.utcnow()
+obs.level = None
+obs.fields = ['air_temperature', 'dew_point_temperature', 'altimeter', 'cloud_coverage']
+obs.locations = ['NW', 'SW', 'NE', 'C']
+obs.colors = ['tab:red', 'tab:green', 'black', 'black']
+obs.formats = [None, None, lambda v: format(10 * v, '.0f')[-3:], 'sky_cover']
+obs.vector_field = ['eastward_wind', 'northward_wind']
+obs.reduce_points = 1
+
+# Make the map panel
+panel = MapPanel()
+panel.area = 'fl'
+panel.projection = ccrs.PlateCarree()
+panel.layers = ['coastline', 'borders', 'states']
+panel.plots = [obs]
+panel.title = f'Surface Observations {datetime.utcnow():%Y-%m-%d}'
+
+# Make the panel container
+pc = PanelContainer()
+pc.size = (10, 10)
+pc.panels = [panel]
+
+# Show the plot
+pc.show()
diff --git a/pages/workshop/Surface_Data/solutions/pd_time_series.py b/pages/workshop/Surface_Data/solutions/pd_time_series.py
new file mode 100644
index 00000000..97ce4e91
--- /dev/null
+++ b/pages/workshop/Surface_Data/solutions/pd_time_series.py
@@ -0,0 +1,39 @@
+# define the time range we are interested in
+end_time = datetime(2017, 9, 12, 0)
+start_time = end_time - timedelta(days=2)
+
+# build the query
+query = ncss.query()
+query.lonlat_point(-155.1, 19.7)
+query.time_range(start_time, end_time)
+query.variables('altimeter_setting', 'temperature', 'dewpoint',
+ 'wind_direction', 'wind_speed')
+query.accept('csv')
+
+data = ncss.get_data(query)
+
+df = pd.DataFrame(data)
+
+# Parse the date time stamps
+df['time'] = pd.to_datetime(df['time'].str.decode('utf-8'), infer_datetime_format=True)
+
+# Station names are bytes, we need to convert them to strings
+df['station'] = df['station'].str.decode('utf-8')
+
+# Make the plot
+ax = df.plot(x='time', y=['temperature', 'dewpoint'],
+ color=['tab:red', 'tab:green'],
+ grid=True,
+ figsize=(10,6),
+ fontsize=14)
+
+# Set good labels
+ax.set_xlabel('Time', fontsize=16)
+ax.set_ylabel('DegC', fontsize=16)
+ax.set_title(f"{df['station'][0]} {df['time'][0]:%Y/%m/%d}", fontsize=22)
+
+# Improve on the default ticking
+locator = AutoDateLocator()
+hoursFmt = DateFormatter('%H')
+ax.xaxis.set_major_locator(locator)
+ax.xaxis.set_major_formatter(hoursFmt)
\ No newline at end of file