2020-09-09 20:02:35 +00:00
=============================
METAR Station Plot with MetPy
=============================
`Notebook <http://nbviewer.ipython.org/github/Unidata/python-awips/blob/master/examples/notebooks/METAR_Station_Plot_with_MetPy.ipynb>`_
2022-06-03 19:57:40 +00:00
Python-AWIPS Tutorial Notebook
--------------
Objectives
==========
- Use python-awips to connect to an edex server
- Define and filter data request for METAR surface obs
- Extract necessary data and reformat it for plotting
- Stylize and plot METAR station data using Cartopy, Matplotlib, and
MetPy
--------------
Table of Contents
-----------------
| `1
Imports <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#imports>`__\
| `2 Function:
get_cloud_cover() <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#function-get-cloud-cover>`__\
| `3 Initial
Setup <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#initial-setup>`__\
| `3.1 Initial EDEX
Connection <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#initial-edex-connection>`__\
| `3.2 Setting Connection Location
2022-06-03 20:13:15 +00:00
Names <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#setting-connection-location-names>`__\
2022-06-03 19:57:40 +00:00
| `4 Filter by
Time <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#filter-by-time>`__\
| `5 Use the
Data! <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#use-the-data>`__\
| `5.1 Get the
Data! <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#get-the-data>`__\
| `5.2 Extract all
Parameters <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#extract-all-parameters>`__\
| `5.3 Populate the Data
Dictionary <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#populate-the-data-dictionary>`__\
| `6 Plot the
Data! <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#plot-the-data>`__\
| `7 See
Also <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#see-also>`__\
| `7.1 Related
Notebooks <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#related-notebooks>`__\
| `7.2 Additional
Documentation <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html#additional-documentation>`__\
1 Imports
---------
The imports below are used throughout the notebook. Note the first two
imports are coming directly from python-awips and allow us to connect to
an EDEX server, and define a timrange used for filtering the data. The
subsequent imports are for data manipulation and visualization.
2020-09-09 20:02:35 +00:00
.. code:: ipython3
from awips.dataaccess import DataAccessLayer
from dynamicserialize.dstypes.com.raytheon.uf.common.time import TimeRange
from datetime import datetime, timedelta
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
from metpy.calc import wind_components
2022-06-03 19:57:40 +00:00
from metpy.plots import StationPlot, StationPlotLayout, sky_cover
2020-09-09 20:02:35 +00:00
from metpy.units import units
2022-06-03 19:57:40 +00:00
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
--------------
2 Function: get_cloud_cover()
-----------------------------
Returns the cloud fraction values as integer codes (0 through 8).
.. code:: ipython3
2020-09-09 20:02:35 +00:00
def get_cloud_cover(code):
if 'OVC' in code:
2022-06-03 19:57:40 +00:00
return 8
2020-09-09 20:02:35 +00:00
elif 'BKN' in code:
2022-06-03 19:57:40 +00:00
return 6
2020-09-09 20:02:35 +00:00
elif 'SCT' in code:
2022-06-03 19:57:40 +00:00
return 4
2020-09-09 20:02:35 +00:00
elif 'FEW' in code:
2022-06-03 19:57:40 +00:00
return 2
2020-09-09 20:02:35 +00:00
else:
return 0
2022-06-03 19:57:40 +00:00
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
--------------
3 Initial Setup
---------------
3.1 Initial EDEX Connection
~~~~~~~~~~~~~~~~~~~~~~~~~~~
First we establish a connection to Unidata’ s public EDEX server. With
that connection made, we can create a `new data request
object <http://unidata.github.io/python-awips/api/IDataRequest.html>`__
and set the data type to **obs**.
Then, because we’ re going to uses MetPy’ s
`StationPlot <https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.StationPlot.html>`__
and
`StationPlotLayout <https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.StationPlotLayout.html>`__
we need to define several parameters, and then set them on the data
request object.
2020-09-09 20:02:35 +00:00
.. code:: ipython3
# EDEX Request
edexServer = "edex-cloud.unidata.ucar.edu"
DataAccessLayer.changeEDEXHost(edexServer)
request = DataAccessLayer.newDataRequest("obs")
2022-06-03 19:57:40 +00:00
# define desired parameters
2020-09-09 20:02:35 +00:00
single_value_params = ["timeObs", "stationName", "longitude", "latitude",
"temperature", "dewpoint", "windDir",
2022-06-03 19:57:40 +00:00
"windSpeed"]
multi_value_params = ["skyCover"]
2020-09-09 20:02:35 +00:00
params = single_value_params + multi_value_params
2022-06-03 19:57:40 +00:00
# set all parameters on the request
2020-09-09 20:02:35 +00:00
request.setParameters(*(params))
2022-06-03 19:57:40 +00:00
3.2 Setting Connection Location Names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We are also going to define specific station IDs so that our plot is not
too cluttered.
.. code:: ipython3
# Define a list of station IDs to plot
selected = ['KPDX', 'KOKC', 'KICT', 'KGLD', 'KMEM', 'KBOS', 'KMIA', 'KMOB', 'KABQ', 'KPHX', 'KTTF',
'KORD', 'KBIL', 'KBIS', 'KCPR', 'KLAX', 'KATL', 'KMSP', 'KSLC', 'KDFW', 'KNYC', 'KPHL',
'KPIT', 'KIND', 'KOLY', 'KSYR', 'KLEX', 'KCHS', 'KTLH', 'KHOU', 'KGJT', 'KLBB', 'KLSV',
'KGRB', 'KCLT', 'KLNK', 'KDSM', 'KBOI', 'KFSD', 'KRAP', 'KRIC', 'KJAN', 'KHSV', 'KCRW',
'KSAT', 'KBUY', 'K0CO', 'KZPC', 'KVIH', 'KBDG', 'KMLF', 'KELY', 'KWMC', 'KOTH', 'KCAR',
'KLMT', 'KRDM', 'KPDT', 'KSEA', 'KUIL', 'KEPH', 'KPUW', 'KCOE', 'KMLP', 'KPIH', 'KIDA',
'KMSO', 'KACV', 'KHLN', 'KBIL', 'KOLF', 'KRUT', 'KPSM', 'KJAX', 'KTPA', 'KSHV', 'KMSY',
'KELP', 'KRNO', 'KFAT', 'KSFO', 'KNYL', 'KBRO', 'KMRF', 'KDRT', 'KFAR', 'KBDE', 'KDLH',
'KHOT', 'KLBF', 'KFLG', 'KCLE', 'KUNV']
# set the location names to the desired station IDs
2020-09-09 20:02:35 +00:00
request.setLocationNames(*(selected))
2022-06-03 19:57:40 +00:00
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
--------------
4 Filter by Time
----------------
Here we decide how much data we want to pull from EDEX. By default we’ ll
request 1 hour, but that value can easily be modified by `adjusting the
``timedelta(hours = 1)`` <https://docs.python.org/3/library/datetime.html#timedelta-objects>`__
in line ``2``. The more data we request, the longer this section will
take to run.
2020-09-09 20:02:35 +00:00
.. code:: ipython3
# Time range
lastHourDateTime = datetime.utcnow() - timedelta(hours = 1)
start = lastHourDateTime.strftime('%Y-%m-%d %H')
beginRange = datetime.strptime( start + ":00:00", "%Y-%m-%d %H:%M:%S")
endRange = datetime.strptime( start + ":59:59", "%Y-%m-%d %H:%M:%S")
timerange = TimeRange(beginRange, endRange)
2022-06-03 19:57:40 +00:00
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
--------------
5 Use the Data!
---------------
5.1 Get the Data!
~~~~~~~~~~~~~~~~~
Now that we have our ``request`` and TimeRange ``timerange`` objects
ready, we’ re ready to get the data array from EDEX.
.. code:: ipython3
# Get response
2020-09-09 20:02:35 +00:00
response = DataAccessLayer.getGeometryData(request,timerange)
2022-06-03 19:57:40 +00:00
5.2 Extract all Parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~
In this section we start gathering all the information we’ ll need to
properly display our data. First we create an empty dictionary and array
to keep track of all data and unique station IDs. We also create a
boolean to help us only grab the first entry for ``skyCover`` related to
a station id.
.. container:: alert-info
::
<b>Note:</b> The way the data responses are returned, we recieve many <code>skyCover</code> entries for each station ID, but we only want to keep track of the most recent one (first one returned).
After defining these variables, we are ready to start looping through
our response data. If the response is an entry of ``skyCover``, and this
is a new station id, then set the skyCover value in the obs dictionary.
If this is not a skyCover entry, then explicitly set the ``timeObs``
variable (because we have to manipulate it slightly), and dynamically
set all the remaining parameters.
2020-09-09 20:02:35 +00:00
.. code:: ipython3
2022-06-03 19:57:40 +00:00
# define a dictionary and array that will be populated from our for loop below
obs = dict({params: [] for params in params})
2020-09-09 20:02:35 +00:00
station_names = []
2022-06-03 19:57:40 +00:00
# only grab the first skyCover record related to a station
new_station_id = True
# cycle through all the data in the response
2020-09-09 20:02:35 +00:00
for ob in response:
avail_params = ob.getParameters()
2022-06-03 19:57:40 +00:00
# if it has cloud information and is the first entry for this station id
if "skyCover" in avail_params and new_station_id:
# store the associated cloud cover int for the skyCover string
obs['skyCover'].append(get_cloud_cover(ob.getString("skyCover")))
new_station_id = False
elif "stationName" in avail_params:
new_station_id=True
2020-09-09 20:02:35 +00:00
# If we already have a record for this stationName, skip
if ob.getString('stationName') not in station_names:
station_names.append(ob.getString('stationName'))
for param in single_value_params:
if param in avail_params:
if param == 'timeObs':
obs[param].append(datetime.fromtimestamp(ob.getNumber(param)/1000.0))
else:
try:
obs[param].append(ob.getNumber(param))
except TypeError:
obs[param].append(ob.getString(param))
else:
obs[param].append(None)
2022-06-03 19:57:40 +00:00
5.3 Populate the Data Dictionary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Next grab the variables out of the obs dictionary we just populated,
attach correct units, (calculate their components, in the instance of
wind) and put them into a new dictionary that we will hand the plotting
function later.
2020-09-09 20:02:35 +00:00
.. code:: ipython3
data = dict()
data['stid'] = np.array(obs['stationName'])
data['latitude'] = np.array(obs['latitude'])
data['longitude'] = np.array(obs['longitude'])
data['air_temperature'] = np.array(obs['temperature'], dtype=float)* units.degC
data['dew_point_temperature'] = np.array(obs['dewpoint'], dtype=float)* units.degC
direction = np.array(obs['windDir'])
direction[direction == -9999.0] = 'nan'
u, v = wind_components(np.array(obs['windSpeed']) * units('knots'),
direction * units.degree)
data['eastward_wind'], data['northward_wind'] = u, v
2022-06-03 19:57:40 +00:00
data['cloud_coverage'] = np.array(obs['skyCover'])
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
--------------
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
6 Plot the Data!
----------------
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
Now we have all the data we need to create our plot! First we’ ll assign
a projection and create our figure and axes.
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
Next, we use Cartopy to add common features (land, ocean, lakes,
borders, etc) to help give us a more contextual map of the United States
to plot the METAR stations on. We create and add a title for our figure
as well.
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
Additionally, we use `MetPy’ s
StationPlotLayout <https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.StationPlotLayout.html>`__
to instantiate a custom layout and define all the attributes we want
displayed. We need to then set the data dictionary (containing all of
our data values) on the custom layout so it knows what to draw.
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
Finally, we display the plot!
2020-09-09 20:02:35 +00:00
.. code:: ipython3
proj = ccrs.LambertConformal(central_longitude=-95, central_latitude=35,
standard_parallels=[35])
# Create the figure
fig = plt.figure(figsize=(20, 10))
ax = fig.add_subplot(1, 1, 1, projection=proj)
# Add various map elements
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.LAKES)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.STATES)
ax.add_feature(cfeature.BORDERS, linewidth=2)
# Set plot bounds
ax.set_extent((-118, -73, 23, 50))
ax.set_title(str(ob.getDataTime()) + " | METAR | " + edexServer)
2022-06-03 19:57:40 +00:00
# Winds, temps, dewpoint, station id
custom_layout = StationPlotLayout()
custom_layout.add_barb('eastward_wind', 'northward_wind', units='knots')
custom_layout.add_value('NW', 'air_temperature', fmt='.0f', units='degF', color='darkred')
custom_layout.add_value('SW', 'dew_point_temperature', fmt='.0f', units='degF', color='darkgreen')
custom_layout.add_symbol('C', 'cloud_coverage', sky_cover)
2020-09-09 20:02:35 +00:00
stationplot = StationPlot(ax, data['longitude'], data['latitude'], clip_on=True,
transform=ccrs.PlateCarree(), fontsize=10)
stationplot.plot_text((2, 0), data['stid'])
custom_layout.plot(stationplot, data)
2022-06-03 19:57:40 +00:00
2020-09-09 20:02:35 +00:00
plt.show()
2022-06-03 19:57:40 +00:00
.. image:: METAR_Station_Plot_with_MetPy_files/METAR_Station_Plot_with_MetPy_26_0.png
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
--------------
7 See Also
----------
- `Aviation Weather Center Static METAR Plots
Information <https://www.aviationweather.gov/metar/help?page=plot>`__
7.1 Related Notebooks
~~~~~~~~~~~~~~~~~~~~~
- `Grid Levels and
Parameters <http://unidata.github.io/python-awips/examples/generated/Grid_Levels_and_Parameters.html>`__
- `Colored Surface Temperature
Plot <http://unidata.github.io/python-awips/examples/generated/Colored_Surface_Temperature_Plot.html>`__
7.2 Additional Documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**python-awips:**
- `DataAccessLayer.changeEDEXHost() <http://unidata.github.io/python-awips/api/DataAccessLayer.html#awips.dataaccess.DataAccessLayer.changeEDEXHost>`__
- `DataAccessLayer.newDataRequest() <http://unidata.github.io/python-awips/api/DataAccessLayer.html#awips.dataaccess.DataAccessLayer.newDataRequest>`__
- `IDataRequest <http://unidata.github.io/python-awips/api/IDataRequest.html>`__
- `DataAccessLayer.getGeometryData <http://unidata.github.io/python-awips/api/PyGeometryData.html>`__
**datetime:**
- `datetime.datetime <https://docs.python.org/3/library/datetime.html#datetime-objects>`__
- `datetime.utcnow() <https://docs.python.org/3/library/datetime.html?#datetime.datetime.utcnow>`__
- `datetime.timedelta <https://docs.python.org/3/library/datetime.html#timedelta-objects>`__
- `datetime.strftime() and
datetime.strptime() <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior>`__
**numpy:**
- `np.array <https://numpy.org/doc/stable/reference/generated/numpy.array.html>`__
**cartopy:**
- `cartopy projection
list <https://scitools.org.uk/cartopy/docs/v0.14/crs/projections.html?#cartopy-projection-list>`__
- `cartopy feature
interface <https://scitools.org.uk/cartopy/docs/v0.14/matplotlib/feature_interface.html>`__
**matplotlib:**
- `matplotlib.pyplot() <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.html>`__
- `matplotlib.pyplot.figure() <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html>`__
- `matplotlib.pyplot.figure.add_subplot <https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.add_subplot>`__
- `ax.set_extent <https://matplotlib.org/stable/api/image_api.html?highlight=set_extent#matplotlib.image.AxesImage.set_extent>`__
- `ax.set_title <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_title.html>`__
**metpy:**
- `metpy.calc.wind_components <https://unidata.github.io/MetPy/latest/api/generated/metpy.calc.wind_components.html>`__
- `metpy.plots.StationPlot() <https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.StationPlot.html>`__
- `metpy.plots.StationPlotLayout() <https://unidata.github.io/MetPy/latest/api/generated/metpy.plots.StationPlotLayout.html>`__
- `metpy.units <https://unidata.github.io/MetPy/latest/api/generated/metpy.units.html>`__
`Top <https://unidata.github.io/python-awips/examples/generated/METAR_Station_Plot_with_MetPy.html>`__
2020-09-09 20:02:35 +00:00
2022-06-03 19:57:40 +00:00
--------------