python-awips/examples/notebooks/METAR_Station_Plot_with_MetPy.ipynb

319 lines
295 KiB
Text
Raw Normal View History

2018-10-04 17:25:20 -06:00
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Station Plot with Layout\n",
"========================\n",
"\n",
"Make a station plot, complete with sky cover and weather symbols, using a\n",
"station plot layout built into MetPy.\n",
"\n",
"The station plot itself is straightforward, but there is a bit of code to perform the\n",
"data-wrangling (hopefully that situation will improve in the future). Certainly, if you have\n",
"existing point data in a format you can work with trivially, the station plot will be simple.\n",
"\n",
"The `StationPlotLayout` class is used to standardize the plotting various parameters\n",
"(i.e. temperature), keeping track of the location, formatting, and even the units for use in\n",
"the station plot. This makes it easy (if using standardized names) to re-use a given layout\n",
"of a station plot.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The setup\n",
"---------\n",
"\n",
"First read in the data. We use `numpy.loadtxt` to read in the data and use a structured\n",
"`numpy.dtype` to allow different types for the various columns. This allows us to handle\n",
"the columns with string data.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from awips.dataaccess import DataAccessLayer\n",
"from dynamicserialize.dstypes.com.raytheon.uf.common.time import TimeRange\n",
"from datetime import datetime, timedelta\n",
"import numpy as np\n",
"\n",
"import cartopy.crs as ccrs\n",
"import cartopy.feature as cfeature\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"\n",
"from metpy.calc import wind_components\n",
"from metpy.cbook import get_test_data\n",
"from metpy.plots import (add_metpy_logo, simple_layout, StationPlot,\n",
" StationPlotLayout, wx_code_map)\n",
"from metpy.units import units\n",
"\n",
"def get_cloud_cover(code):\n",
" if 'OVC' in code:\n",
" return 1.0\n",
" elif 'BKN' in code:\n",
" return 6.0/8.0\n",
" elif 'SCT' in code:\n",
" return 4.0/8.0\n",
" elif 'FEW' in code:\n",
" return 2.0/8.0\n",
" else:\n",
" return 0\n",
"\n",
"# Pull out these specific stations (prepend K for AWIPS identifiers)\n",
"selected = ['PDX', 'OKC', 'ICT', 'GLD', 'MEM', 'BOS', 'MIA', 'MOB', 'ABQ', 'PHX', 'TTF',\n",
" 'ORD', 'BIL', 'BIS', 'CPR', 'LAX', 'ATL', 'MSP', 'SLC', 'DFW', 'NYC', 'PHL',\n",
" 'PIT', 'IND', 'OLY', 'SYR', 'LEX', 'CHS', 'TLH', 'HOU', 'GJT', 'LBB', 'LSV',\n",
" 'GRB', 'CLT', 'LNK', 'DSM', 'BOI', 'FSD', 'RAP', 'RIC', 'JAN', 'HSV', 'CRW',\n",
" 'SAT', 'BUY', '0CO', 'ZPC', 'VIH', 'BDG', 'MLF', 'ELY', 'WMC', 'OTH', 'CAR',\n",
" 'LMT', 'RDM', 'PDT', 'SEA', 'UIL', 'EPH', 'PUW', 'COE', 'MLP', 'PIH', 'IDA', \n",
" 'MSO', 'ACV', 'HLN', 'BIL', 'OLF', 'RUT', 'PSM', 'JAX', 'TPA', 'SHV', 'MSY',\n",
" 'ELP', 'RNO', 'FAT', 'SFO', 'NYL', 'BRO', 'MRF', 'DRT', 'FAR', 'BDE', 'DLH',\n",
" 'HOT', 'LBF', 'FLG', 'CLE', 'UNV']\n",
"selected = ['K{0}'.format(id) for id in selected]\n",
"data_arr = []"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# EDEX Request\n",
"edexServer = \"edex-cloud.unidata.ucar.edu\"\n",
"DataAccessLayer.changeEDEXHost(edexServer)\n",
"request = DataAccessLayer.newDataRequest(\"obs\")\n",
"availableProducts = DataAccessLayer.getAvailableParameters(request)\n",
"\n",
"single_value_params = [\"timeObs\", \"stationName\", \"longitude\", \"latitude\", \n",
" \"temperature\", \"dewpoint\", \"windDir\",\n",
" \"windSpeed\", \"seaLevelPress\"]\n",
"multi_value_params = [\"presWeather\", \"skyCover\", \"skyLayerBase\"]\n",
"pres_weather, sky_cov, sky_layer_base = [],[],[]\n",
"params = single_value_params + multi_value_params\n",
"obs = dict({params: [] for params in params})\n",
"\n",
"request.setParameters(*(params))\n",
"request.setLocationNames(*(selected))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we use the Python-AWIPS class **TimeRange** to prepare a beginning and end time span for requesting observations (the last hour):"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# Time range\n",
"lastHourDateTime = datetime.utcnow() - timedelta(hours = 1)\n",
"start = lastHourDateTime.strftime('%Y-%m-%d %H')\n",
"beginRange = datetime.strptime( start + \":00:00\", \"%Y-%m-%d %H:%M:%S\")\n",
"endRange = datetime.strptime( start + \":59:59\", \"%Y-%m-%d %H:%M:%S\")\n",
"timerange = TimeRange(beginRange, endRange)\n",
"\n",
"response = DataAccessLayer.getGeometryData(request,timerange)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"station_names = []\n",
"for ob in response:\n",
" avail_params = [x.decode('utf-8') for x in ob.getParameters()]\n",
" if \"presWeather\" in avail_params:\n",
" pres_weather.append(ob.getString(\"presWeather\"))\n",
" elif \"skyCover\" in avail_params and \"skyLayerBase\" in avail_params:\n",
" sky_cov.append(ob.getString(\"skyCover\"))\n",
" sky_layer_base.append(ob.getNumber(\"skyLayerBase\"))\n",
" else:\n",
" # If we already have a record for this stationName, skip\n",
" if ob.getString('stationName') not in station_names:\n",
" station_names.append(ob.getString('stationName'))\n",
" for param in single_value_params: \n",
" if param in avail_params:\n",
" if param == 'timeObs':\n",
" obs[param].append(datetime.fromtimestamp(ob.getNumber(param)/1000.0))\n",
" else:\n",
" try:\n",
" obs[param].append(ob.getNumber(param))\n",
" except TypeError:\n",
" obs[param].append(ob.getString(param))\n",
" else:\n",
" obs[param].append(None)\n",
" \n",
" obs['presWeather'].append(pres_weather);\n",
" obs['skyCover'].append(sky_cov);\n",
" obs['skyLayerBase'].append(sky_layer_base);\n",
" pres_weather = []\n",
" sky_cov = []\n",
" sky_layer_base = []"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next grab the simple variables out of the data we have (attaching correct units), and\n",
"put them into a dictionary that we will hand the plotting function later:\n",
"\n",
"- Get wind components from speed and direction\n",
"- Convert cloud fraction values to integer codes [0 - 8]\n",
"- Map METAR weather codes to WMO codes for weather symbols"
]
},
{
"cell_type": "code",
2018-10-05 17:09:43 -06:00
"execution_count": 9,
2018-10-04 17:25:20 -06:00
"metadata": {},
"outputs": [],
"source": [
"data = dict()\n",
"data['stid'] = np.array(obs['stationName'])\n",
"data['latitude'] = np.array(obs['latitude'])\n",
"data['longitude'] = np.array(obs['longitude'])\n",
"data['air_temperature'] = np.array(obs['temperature'], dtype=float)* units.degC\n",
"data['dew_point_temperature'] = np.array(obs['dewpoint'], dtype=float)* units.degC\n",
"data['air_pressure_at_sea_level'] = np.array(obs['seaLevelPress'])* units('mbar')\n",
"\n",
"direction = np.array(obs['windDir'])\n",
"direction[direction == -9999.0] = 'nan'\n",
"\n",
"u, v = wind_components(np.array(obs['windSpeed']) * units('knots'),\n",
" direction * units.degree)\n",
"data['eastward_wind'], data['northward_wind'] = u, v\n",
"data['cloud_coverage'] = [int(get_cloud_cover(x)*8) for x in obs['skyCover']]\n",
"data['present_weather'] = obs['presWeather']"
]
},
2018-10-05 17:09:43 -06:00
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['K0CO', 'KMLF', 'KCPR', 'KLNK', 'KFSD', 'KPIT', 'KMRF', 'KCLE', 'KFAR', 'KOLY', 'KBDG', 'KTTF', 'KGRB', 'KBDE', 'KOTH', 'KUNV', 'KBIS', 'KMSP', 'KLAX', 'KNYC', 'KELP', 'KSAT', 'KORD', 'KPHX', 'KEPH', 'KGJT', 'KGLD', 'KICT', 'KDFW', 'KATL', 'KCLT', 'KRAP', 'KACV', 'KABQ', 'KSEA', 'KLBF', 'KOKC', 'KBIL', 'KDRT', 'KVIH', 'KELY', 'KFAT', 'KHSV', 'KLBB', 'KPDX', 'KMSO', 'KUIL', 'KTLH', 'KPIH', 'KMIA', 'KMLP', 'KMSY', 'KTPA', 'KHLN', 'KHOT', 'KHOU', 'KIDA', 'KPUW', 'KLMT', 'KOLF', 'KPDT', 'KBOI', 'KPHL', 'KCAR', 'KCRW', 'KDSM', 'KJAN', 'KLEX', 'KIND', 'KMEM', 'KBOS', 'KBRO', 'KSYR', 'KRIC', 'KBUY', 'KSLC', 'KCOE', 'KRNO', 'KRUT', 'KNYL', 'KPSM', 'KRDM', 'KFLG', 'KWMC', 'KCHS', 'KJAX', 'KMOB', 'KSFO', 'KSHV', 'KLSV']\n"
]
}
],
"source": [
"print(obs['stationName'])"
]
},
2018-10-04 17:25:20 -06:00
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## MetPy Surface Obs Plot"
]
},
{
"cell_type": "code",
2018-10-05 17:09:43 -06:00
"execution_count": 12,
2018-10-04 17:25:20 -06:00
"metadata": {},
"outputs": [
{
"data": {
2018-10-05 17:09:43 -06:00
"image/png": "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
2018-10-04 17:25:20 -06:00
"text/plain": [
"<Figure size 1440x720 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"proj = ccrs.LambertConformal(central_longitude=-95, central_latitude=35,\n",
" standard_parallels=[35])\n",
2018-10-05 17:09:43 -06:00
"\n",
"# Change the DPI of the figure\n",
2018-10-04 17:25:20 -06:00
"plt.rcParams['savefig.dpi'] = 255\n",
"\n",
2018-10-05 17:09:43 -06:00
"# Winds, temps, dewpoint, station id\n",
"custom_layout = StationPlotLayout()\n",
"custom_layout.add_barb('eastward_wind', 'northward_wind', units='knots')\n",
"custom_layout.add_value('NW', 'air_temperature', fmt='.0f', units='degF', color='darkred')\n",
"custom_layout.add_value('SW', 'dew_point_temperature', fmt='.0f', units='degF', color='darkgreen')\n",
"custom_layout.add_value('E', 'precipitation', fmt='0.1f', units='inch', color='blue')\n",
2018-10-04 17:25:20 -06:00
"\n",
2018-10-05 17:09:43 -06:00
"# Create the figure\n",
2018-10-04 17:25:20 -06:00
"fig = plt.figure(figsize=(20, 10))\n",
"add_metpy_logo(fig, 1080, 290, size='large')\n",
"ax = fig.add_subplot(1, 1, 1, projection=proj)\n",
2018-10-05 17:09:43 -06:00
"\n",
"# Add various map elements\n",
2018-10-04 17:25:20 -06:00
"ax.add_feature(cfeature.LAND)\n",
"ax.add_feature(cfeature.OCEAN)\n",
"ax.add_feature(cfeature.LAKES)\n",
"ax.add_feature(cfeature.COASTLINE)\n",
"ax.add_feature(cfeature.STATES)\n",
"ax.add_feature(cfeature.BORDERS, linewidth=2)\n",
2018-10-05 17:09:43 -06:00
"\n",
2018-10-04 17:25:20 -06:00
"# Set plot bounds\n",
"ax.set_extent((-118, -73, 23, 50))\n",
"ax.set_title(str(ob.getDataTime()) + \" | METAR | \" + edexServer)\n",
"\n",
"stationplot = StationPlot(ax, data['longitude'], data['latitude'], clip_on=True,\n",
" transform=ccrs.PlateCarree(), fontsize=10)\n",
"stationplot.plot_text((2, 0), data['stid'])\n",
2018-10-05 17:09:43 -06:00
"custom_layout.plot(stationplot, data)\n",
"plt.show()"
2018-10-04 17:25:20 -06:00
]
}
],
"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.6.6"
}
},
"nbformat": 4,
"nbformat_minor": 1
}