From 1b0b6d0700da716a4ee4b3889387dbb09ebf96f3 Mon Sep 17 00:00:00 2001 From: "John R. Leeman" Date: Tue, 7 Jan 2020 13:23:21 -0600 Subject: [PATCH 1/2] Remove time estimates --- ...dvanced StationPlots with Mesonet Data.ipynb | 11 +++-------- .../Surface Data with Siphon and MetPy.ipynb | 17 +++++------------ 2 files changed, 8 insertions(+), 20 deletions(-) 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", "
\"METAR\"
\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/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", "
\"METAR\"
\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 } From 73c8790a82c8489213fa22a32c96c40ba7269c8a Mon Sep 17 00:00:00 2001 From: "John R. Leeman" Date: Wed, 8 Jan 2020 08:03:18 -0600 Subject: [PATCH 2/2] Add declarative surface plotting notebook and solutions. --- .../Declarative Surface Observations.ipynb | 515 ++++++++++++++++++ .../solutions/dec_basic_sfc_plot.py | 27 + .../Surface_Data/solutions/pd_time_series.py | 39 ++ 3 files changed, 581 insertions(+) create mode 100644 pages/workshop/Surface_Data/Declarative Surface Observations.ipynb create mode 100644 pages/workshop/Surface_Data/solutions/dec_basic_sfc_plot.py create mode 100644 pages/workshop/Surface_Data/solutions/pd_time_series.py 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\n", + "
\n", + "\n", + "

Working with Surface Observations in Siphon and MetPy

\n", + "

Unidata Python Workshop

\n", + "\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
\"METAR\"
\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/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