python-awips markdown examples added to ghpages AWIPS user manual

This commit is contained in:
Michael James 2017-08-14 11:14:48 -06:00 committed by mjames-upc
parent ccd81477c3
commit 26f3d9db30
35 changed files with 2280 additions and 17 deletions

BIN
docs/images/cartopy_2_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
docs/images/cartopy_4_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
docs/images/map_11_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/images/map_13_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
docs/images/map_15_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
docs/images/map_18_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

BIN
docs/images/map_4_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/images/map_6_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/images/map_8_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/images/output_0_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
docs/images/output_11_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/images/output_13_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
docs/images/output_14_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
docs/images/output_15_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
docs/images/output_18_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

BIN
docs/images/output_1_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
docs/images/output_3_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
docs/images/output_3_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
docs/images/output_4_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/images/output_5_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
docs/images/output_6_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
docs/images/output_7_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,000 KiB

BIN
docs/images/output_8_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/images/output_9_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

View file

@ -42,7 +42,7 @@ Unidata supports two visualization frameworks for rendering data: [CAVE](install
|----------------|-----------------------:|
| Linux x86_64 | [installEDEX.sh <i class="fa fa-download"></i>](http://www.unidata.ucar.edu/software/awips2/installEDEX.sh) |
[Read full EDEX install instructions...](install/install-edex.html)
[Read full EDEX install instructions...](install/install-edex)
---

View file

@ -0,0 +1,75 @@
The simplest example of requesting and plotting AWIPS gridded data with Matplotlib and Cartopy.
```python
from awips.dataaccess import DataAccessLayer
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
%matplotlib inline
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest()
request.setDatatype("grid")
request.setLocationNames("RAP13")
request.setParameters("T")
request.setLevels("2.0FHAG")
cycles = DataAccessLayer.getAvailableTimes(request, True)
times = DataAccessLayer.getAvailableTimes(request)
fcstRun = DataAccessLayer.getForecastRun(cycles[-1], times)
response = DataAccessLayer.getGridData(request, [fcstRun[0]])
grid = response[0]
data = grid.getRawData()
lons, lats = grid.getLatLonCoords()
bbox = [lons.min(), lons.max(), lats.min(), lats.max()]
def make_map(bbox, projection=ccrs.PlateCarree()):
fig, ax = plt.subplots(figsize=(16, 9),
subplot_kw=dict(projection=projection))
ax.set_extent(bbox)
ax.coastlines(resolution='50m')
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
return fig, ax
```
### with pcolormesh
```python
cmap = plt.get_cmap('rainbow')
fig, ax = make_map(bbox=bbox)
cs = ax.pcolormesh(lons, lats, data, cmap=cmap)
cbar = fig.colorbar(cs, shrink=0.7, orientation='horizontal')
cbar.set_label(str(grid.getLocationName()) +" " \
+ str(grid.getLevel()) + " " \
+ str(grid.getParameter()) \
+ " (" + str(grid.getUnit()) + ") " \
+ "valid " + str(grid.getDataTime().getRefTime()))
```
![png](../images/cartopy_2_1.png)
### with contourf
```python
fig2, ax2 = make_map(bbox=bbox)
cs2 = ax2.contourf(lons, lats, data, 80, cmap=cmap,
vmin=data.min(), vmax=data.max())
cbar2 = fig2.colorbar(cs2, shrink=0.7, orientation='horizontal')
cbar2.set_label(str(grid.getLocationName()) +" " \
+ str(grid.getLevel()) + " " \
+ str(grid.getParameter()) \
+ " (" + str(grid.getUnit()) + ") " \
+ "valid " + str(grid.getDataTime().getRefTime()))
```
![png](../images/cartopy_4_0.png)

View file

@ -0,0 +1,373 @@
The python-awips package provides access to the entire AWIPS Maps Database for use in Python GIS applications. Map objects are returned as <a href="http://toblerity.org/shapely/manual.html">Shapely</a> geometries (*Polygon*, *Point*, *MultiLineString*, etc.) and can be easily plotted by Matplotlib, Cartopy, MetPy, and other packages.
Each map database table has a geometry field called `the_geom`, which can be used to spatially select map resources for any column of type geometry,
## Notes
* This notebook requires: **python-awips, numpy, matplotplib, cartopy, shapely**
* Use datatype **maps** and **addIdentifier('table', &lt;postgres maps schema&gt;)** to define the map table:
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest('maps')
request.addIdentifier('table', 'mapdata.county')
* Use **request.setLocationNames()** and **request.addIdentifier()** to spatially filter a map resource. In the example below, WFO ID **BOU** (Boulder, Colorado) is used to query counties within the BOU county watch area (CWA)
request.addIdentifier('geomField', 'the_geom')
request.addIdentifier('inLocation', 'true')
request.addIdentifier('locationField', 'cwa')
request.setLocationNames('BOU')
request.addIdentifier('cwa', 'BOU')
See the <a href="http://unidata.github.io/awips2/python/maps-database/#mapdatacwa">Maps Database Reference Page</a> for available database tables, column names, and types.
> Note the geometry definition of `the_geom` for each data type, which can be **Point**, **MultiPolygon**, or **MultiLineString**.
## Setup
```python
from __future__ import print_function
from awips.dataaccess import DataAccessLayer
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy as np
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
from cartopy.feature import ShapelyFeature,NaturalEarthFeature
from shapely.geometry import Polygon
from shapely.ops import cascaded_union
# Standard map plot
def make_map(bbox, projection=ccrs.PlateCarree()):
fig, ax = plt.subplots(figsize=(12,12),
subplot_kw=dict(projection=projection))
ax.set_extent(bbox)
ax.coastlines(resolution='50m')
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
return fig, ax
# Server, Data Request Type, and Database Table
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest('maps')
request.addIdentifier('table', 'mapdata.county')
```
## Request County Boundaries for a WFO
* Use **request.setParameters()** to define fields to be returned by the request.
```python
# Define a WFO ID for location
# tie this ID to the mapdata.county column "cwa" for filtering
request.setLocationNames('BOU')
request.addIdentifier('cwa', 'BOU')
# enable location filtering (inLocation)
# locationField is tied to the above cwa definition (BOU)
request.addIdentifier('geomField', 'the_geom')
request.addIdentifier('inLocation', 'true')
request.addIdentifier('locationField', 'cwa')
# This is essentially the same as "'"select count(*) from mapdata.cwa where cwa='BOU';" (=1)
# Get response and create dict of county geometries
response = DataAccessLayer.getGeometryData(request, [])
counties = np.array([])
for ob in response:
counties = np.append(counties,ob.getGeometry())
print("Using " + str(len(counties)) + " county MultiPolygons")
%matplotlib inline
# All WFO counties merged to a single Polygon
merged_counties = cascaded_union(counties)
envelope = merged_counties.buffer(2)
boundaries=[merged_counties]
# Get bounds of this merged Polygon to use as buffered map extent
bounds = merged_counties.bounds
bbox=[bounds[0]-1,bounds[2]+1,bounds[1]-1.5,bounds[3]+1.5]
fig, ax = make_map(bbox=bbox)
# Plot political/state boundaries handled by Cartopy
political_boundaries = NaturalEarthFeature(category='cultural',
name='admin_0_boundary_lines_land',
scale='50m', facecolor='none')
states = NaturalEarthFeature(category='cultural',
name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(political_boundaries, linestyle='-', edgecolor='black')
ax.add_feature(states, linestyle='-', edgecolor='black',linewidth=2)
# Plot CWA counties
for i, geom in enumerate(counties):
cbounds = Polygon(geom)
intersection = cbounds.intersection
geoms = (intersection(geom)
for geom in counties
if cbounds.intersects(geom))
shape_feature = ShapelyFeature(geoms,ccrs.PlateCarree(),
facecolor='none', linestyle="-",edgecolor='#86989B')
ax.add_feature(shape_feature)
```
Using 25 county MultiPolygons
![png](../images/map_4_1.png)
## Create a merged CWA with cascaded_union
```python
# Plot CWA envelope
for i, geom in enumerate(boundaries):
gbounds = Polygon(geom)
intersection = gbounds.intersection
geoms = (intersection(geom)
for geom in boundaries
if gbounds.intersects(geom))
shape_feature = ShapelyFeature(geoms,ccrs.PlateCarree(),
facecolor='none', linestyle="-",linewidth=3.,edgecolor='#cc5000')
ax.add_feature(shape_feature)
fig
```
![png](../images/map_6_0.png)
## WFO boundary spatial filter for interstates
Using the previously-defined **envelope=merged_counties.buffer(2)** in **newDataRequest()** to request geometries which fall inside the buffered boundary.
```python
request = DataAccessLayer.newDataRequest('maps', envelope=envelope)
request.addIdentifier('table', 'mapdata.interstate')
request.addIdentifier('geomField', 'the_geom')
request.addIdentifier('locationField', 'hwy_type')
request.addIdentifier('hwy_type', 'I') # I (interstate), U (US highway), or S (state highway)
request.setParameters('name')
interstates = DataAccessLayer.getGeometryData(request, [])
print("Using " + str(len(interstates)) + " interstate MultiLineStrings")
# Plot interstates
for ob in interstates:
shape_feature = ShapelyFeature(ob.getGeometry(),ccrs.PlateCarree(),
facecolor='none', linestyle="-",edgecolor='orange')
ax.add_feature(shape_feature)
fig
```
Using 223 interstate MultiLineStrings
![png](../images/map_8_1.png)
> Road type from `select distinct(hwy_type) from mapdata.interstate;`
>
> I - Interstates
> U - US Highways
> S - State Highways
## Nearby cities
Request the city table and filter by population and progressive disclosure level:
**Warning**: the `prog_disc` field is not entirely understood and values appear to change significantly depending on WFO site.
```python
request = DataAccessLayer.newDataRequest('maps', envelope=envelope)
request.addIdentifier('table', 'mapdata.city')
request.addIdentifier('geomField', 'the_geom')
request.setParameters('name','population','prog_disc')
cities = DataAccessLayer.getGeometryData(request, [])
print("Found " + str(len(cities)) + " city Points")
```
Found 1201 city Points
```python
citylist = []
cityname = []
# For BOU, progressive disclosure values above 50 and pop above 5000 looks good
for ob in cities:
if ((ob.getNumber("prog_disc")>50) and int(ob.getString("population")) > 5000):
citylist.append(ob.getGeometry())
cityname.append(ob.getString("name"))
print("Using " + str(len(cityname)) + " city Points")
# Plot city markers
ax.scatter([point.x for point in citylist],
[point.y for point in citylist],
transform=ccrs.Geodetic(),marker="+",facecolor='black')
# Plot city names
for i, txt in enumerate(cityname):
ax.annotate(txt, (citylist[i].x,citylist[i].y),
xytext=(3,3), textcoords="offset points")
fig
```
Using 57 city Points
![png](../images/map_11_1.png)
## Lakes
```python
request = DataAccessLayer.newDataRequest('maps', envelope=envelope)
request.addIdentifier('table', 'mapdata.lake')
request.addIdentifier('geomField', 'the_geom')
request.setParameters('name')
# Get lake geometries
response = DataAccessLayer.getGeometryData(request, [])
lakes = np.array([])
for ob in response:
lakes = np.append(lakes,ob.getGeometry())
print("Using " + str(len(lakes)) + " lake MultiPolygons")
# Plot lakes
for i, geom in enumerate(lakes):
cbounds = Polygon(geom)
intersection = cbounds.intersection
geoms = (intersection(geom)
for geom in lakes
if cbounds.intersects(geom))
shape_feature = ShapelyFeature(geoms,ccrs.PlateCarree(),
facecolor='blue', linestyle="-",edgecolor='#20B2AA')
ax.add_feature(shape_feature)
fig
```
Using 208 lake MultiPolygons
![png](../images/map_13_1.png)
## Major Rivers
```python
request = DataAccessLayer.newDataRequest('maps', envelope=envelope)
request.addIdentifier('table', 'mapdata.majorrivers')
request.addIdentifier('geomField', 'the_geom')
request.setParameters('pname')
rivers = DataAccessLayer.getGeometryData(request, [])
print("Using " + str(len(rivers)) + " river MultiLineStrings")
# Plot rivers
for ob in rivers:
shape_feature = ShapelyFeature(ob.getGeometry(),ccrs.PlateCarree(),
facecolor='none', linestyle=":",edgecolor='#20B2AA')
ax.add_feature(shape_feature)
fig
```
Using 758 river MultiLineStrings
![png](../images/map_15_1.png)
## Topography
Spatial envelopes are required for topo requests, which can become slow to download and render for large (CONUS) maps.
```python
import numpy.ma as ma
request = DataAccessLayer.newDataRequest()
request.setDatatype("topo")
request.addIdentifier("group", "/")
request.addIdentifier("dataset", "full")
request.setEnvelope(envelope)
gridData = DataAccessLayer.getGridData(request)
print(gridData)
print("Number of grid records: " + str(len(gridData)))
print("Sample grid data shape:\n" + str(gridData[0].getRawData().shape) + "\n")
print("Sample grid data:\n" + str(gridData[0].getRawData()) + "\n")
```
[<awips.dataaccess.PyGridData.PyGridData object at 0x1174adf50>]
Number of grid records: 1
Sample grid data shape:
(778, 1058)
Sample grid data:
[[ 1694. 1693. 1688. ..., 757. 761. 762.]
[ 1701. 1701. 1701. ..., 758. 760. 762.]
[ 1703. 1703. 1703. ..., 760. 761. 762.]
...,
[ 1767. 1741. 1706. ..., 769. 762. 768.]
[ 1767. 1746. 1716. ..., 775. 765. 761.]
[ 1781. 1753. 1730. ..., 766. 762. 759.]]
```python
grid=gridData[0]
topo=ma.masked_invalid(grid.getRawData())
lons, lats = grid.getLatLonCoords()
print(topo.min())
print(topo.max())
# Plot topography
cs = ax.contourf(lons, lats, topo, 80, cmap=plt.get_cmap('terrain'),alpha=0.1)
cbar = fig.colorbar(cs, extend='both', shrink=0.5, orientation='horizontal')
cbar.set_label("topography height in meters")
fig
```
623.0
4328.0
![png](../images/map_18_1.png)

View file

@ -1,4 +1,3 @@
ok
## mapdata.airport

View file

@ -0,0 +1,234 @@
The EDEX modelsounding plugin creates 64-level vertical profiles from GFS and ETA (NAM) BUFR products distirubted over NOAAport. Paramters which are requestable are **pressure**, **temperature**, **specHum**, **uComp**, **vComp**, **omega**, **cldCvr**.
```python
from awips.dataaccess import DataAccessLayer
import matplotlib.tri as mtri
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from math import exp, log
import numpy as np
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest()
request.setDatatype("modelsounding")
forecastModel = "GFS"
request.addIdentifier("reportType", forecastModel)
request.setParameters("pressure","temperature","specHum","uComp","vComp","omega","cldCvr")
locations = DataAccessLayer.getAvailableLocationNames(request)
locations.sort()
list(locations)
```
['CHE',
'CRL',
'EAX',
'HSI',
'KDSM',
'KFOE',
'KFRM',
'KFSD',
'KGRI',
'KLNK',
'KMCI',
'KMCW',
'KMHE',
'KMHK',
'KMKC',
'KOFK',
'KOMA',
'KRSL',
'KSLN',
'KSTJ',
'KSUX',
'KTOP',
'KYKN',
'OAX',
'P#8',
'P#9',
'P#A',
'P#G',
'P#I',
'RDD',
'WSC']
```python
request.setLocationNames("KMCI")
cycles = DataAccessLayer.getAvailableTimes(request, True)
times = DataAccessLayer.getAvailableTimes(request)
try:
fcstRun = DataAccessLayer.getForecastRun(cycles[-1], times)
list(fcstRun)
response = DataAccessLayer.getGeometryData(request,[fcstRun[0]])
except:
print('No times available')
exit
```
```python
tmp,prs,sh = np.array([]),np.array([]),np.array([])
uc,vc,om,cld = np.array([]),np.array([]),np.array([]),np.array([])
for ob in response:
tmp = np.append(tmp,ob.getNumber("temperature"))
prs = np.append(prs,ob.getNumber("pressure"))
sh = np.append(sh,ob.getNumber("specHum"))
uc = np.append(uc,ob.getNumber("uComp"))
vc = np.append(vc,ob.getNumber("vComp"))
om = np.append(om,ob.getNumber("omega"))
cld = np.append(cld,ob.getNumber("cldCvr"))
print("parms = " + str(ob.getParameters()))
print("site = " + str(ob.getLocationName()))
print("geom = " + str(ob.getGeometry()))
print("datetime = " + str(ob.getDataTime()))
print("reftime = " + str(ob.getDataTime().getRefTime()))
print("fcstHour = " + str(ob.getDataTime().getFcstTime()))
print("period = " + str(ob.getDataTime().getValidPeriod()))
sounding_title = forecastModel + " " + str(ob.getLocationName()) + "("+ str(ob.getGeometry())+")" + str(ob.getDataTime())
```
parms = ['uComp', 'cldCvr', 'temperature', 'vComp', 'pressure', 'omega', 'specHum']
site = KMCI
geom = POINT (-94.72000122070312 39.31999969482422)
datetime = 1970-01-18 04:45:50.400000 (0)
reftime = Jan 18 70 04:45:50 GMT
fcstHour = 0
period = (Jan 18 70 04:45:50 , Jan 18 70 04:45:50 )
## Create data arrays and calculate dewpoint from spec. humidity
```python
from metpy.calc import get_wind_components, lcl, dry_lapse, parcel_profile, dewpoint
from metpy.calc import get_wind_speed,get_wind_dir, thermo, vapor_pressure
from metpy.plots import SkewT, Hodograph
from metpy.units import units, concatenate
# we can use units.* here...
t = (tmp-273.15) * units.degC
p = prs/100 * units.mbar
u,v = uc*1.94384,vc*1.94384 # m/s to knots
spd = get_wind_speed(u, v) * units.knots
dir = get_wind_dir(u, v) * units.deg
```
## Dewpoint from Specific Humidity
Because the modelsounding plugin does not return dewpoint values, we must calculate the profile ourselves. Here are three examples of dewpoint calculated from specific humidity, including a manual calculation following NCEP AWIPS/NSHARP.
### 1) metpy calculated mixing ratio and vapor pressure
```python
from metpy.calc import get_wind_components, lcl, dry_lapse, parcel_profile, dewpoint
from metpy.calc import get_wind_speed,get_wind_dir, thermo, vapor_pressure
from metpy.plots import SkewT, Hodograph
from metpy.units import units, concatenate
# we can use units.* here...
t = (tmp-273.15) * units.degC
p = prs/100 * units.mbar
u,v = uc*1.94384,vc*1.94384 # m/s to knots
spd = get_wind_speed(u, v) * units.knots
dir = get_wind_dir(u, v) * units.deg
```
```python
rmix = (sh/(1-sh)) *1000 * units('g/kg')
e = vapor_pressure(p, rmix)
td = dewpoint(e)
```
/Users/mj/miniconda2/lib/python2.7/site-packages/MetPy-0.3.0+34.gcf954c5-py2.7.egg/metpy/calc/thermo.py:371: RuntimeWarning: divide by zero encountered in log
val = np.log(e / sat_pressure_0c)
/Users/mj/miniconda2/lib/python2.7/site-packages/pint/quantity.py:1236: RuntimeWarning: divide by zero encountered in log
out = uf(*mobjs)
/Users/mj/miniconda2/lib/python2.7/site-packages/pint/quantity.py:693: RuntimeWarning: invalid value encountered in true_divide
magnitude = magnitude_op(self._magnitude, other_magnitude)
### 2) metpy calculated assuming spec. humidity = mixing ratio
```python
td2 = dewpoint(vapor_pressure(p, sh))
```
### 3) NCEP AWIPS soundingrequest plugin
based on GEMPAK/NSHARP, from https://github.com/Unidata/awips2-ncep/blob/unidata_16.2.2/edex/gov.noaa.nws.ncep.edex.plugin.soundingrequest/src/gov/noaa/nws/ncep/edex/plugin/soundingrequest/handler/MergeSounding.java#L1783
```python
# new arrays
ntmp = tmp
# where p=pressure(pa), T=temp(C), T0=reference temp(273.16)
rh = 0.263*prs*sh / (np.exp(17.67*ntmp/(ntmp+273.15-29.65)))
vaps = 6.112 * np.exp((17.67 * ntmp) / (ntmp + 243.5))
vapr = rh * vaps / 100
dwpc = np.array(243.5 * (np.log(6.112) - np.log(vapr)) / (np.log(vapr) - np.log(6.112) - 17.67)) * units.degC
```
/Users/mj/miniconda2/lib/python2.7/site-packages/ipykernel/__main__.py:8: RuntimeWarning: divide by zero encountered in log
/Users/mj/miniconda2/lib/python2.7/site-packages/ipykernel/__main__.py:8: RuntimeWarning: invalid value encountered in divide
## Plot with MetPy
```python
%matplotlib inline
plt.rcParams['figure.figsize'] = (12, 14)
# Create a skewT plot
skew = SkewT()
# Plot the data
skew.plot(p, t, 'r', linewidth=2)
skew.plot(p, td, 'b', linewidth=2)
skew.plot(p, td2, 'y')
skew.plot(p, dwpc, 'g', linewidth=2)
skew.plot_barbs(p, u, v)
skew.ax.set_ylim(1000, 100)
skew.ax.set_xlim(-40, 60)
plt.title(sounding_title)
# Calculate LCL height and plot as black dot
l = lcl(p[0], t[0], td[0])
lcl_temp = dry_lapse(concatenate((p[0], l)), t[0])[-1].to('degC')
skew.plot(l, lcl_temp, 'ko', markerfacecolor='black')
# An example of a slanted line at constant T -- in this case the 0 isotherm
l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
# Draw hodograph
ax_hod = inset_axes(skew.ax, '40%', '40%', loc=2)
h = Hodograph(ax_hod, component_range=get_wind_speed(u, v).max())
h.add_grid(increment=20)
h.plot_colormapped(u, v, spd)
# Show the plot
plt.show()
```
![png](../images/output_14_1.png)

View file

@ -0,0 +1,275 @@
Shown here are plots for Base Reflectivity (N0Q, 94) and Base Velocity (N0U, 99) using AWIPS data rendered with Matplotlib, Cartopy, and MetPy. This example improves upon existing Level 3 Python rendering by doing the following:
* Display scaled and labeled colorbar below each figure.
* Plot radar radial images as coordinate maps in Cartopy and label with lat/lon.
* 8 bit Z and V colormap and data scaling added to MetPy from operational AWIPS.
* Level 3 data are retrieved from the [Unidata EDEX Cloud server](http://unidata.github.io/awips2/docs/install/install-cave.html#how-to-run-cave) (`edex-cloud.unidata.ucar.edu`)
* Raw HDF5 byte data are converted to product values and scaled according to (page 3-34 https://www.roc.noaa.gov/wsr88d/PublicDocs/ICDS/2620001U.pdf)
The threshold level fields are used to describe (up to) 256 levels as follows:
halfword 31 contains the minimum data value in m/s*10 (or dBZ*10)
halfword 32 contains the increment in m/s*10 (or dBZ*10)
halfword 33 contains the number of levels (0 - 255)
According to the [ICD for the Product Specification](https://www.roc.noaa.gov/WSR88D/PublicDocs/NewTechnology/B17_2620003W_draft.pdf), *"the 256 data levels of the digital product cover a range of reflectivity between -32.0 to +94.5 dBZ, in increments of 0.5 dBZ. Level codes 0 and 1 correspond to 'Below Threshold' and 'Range Folded', respectively, while level codes 2 through 255 correspond to the reflectivity data itself"*.
So it's really 254 color values between -32 and +94.5 dBZ.
The ICD lists 16 specific color levels and directs 256-level reflectivity products to use corresponding colors, leaving it the rendering application to scale and blend between the 16 color values, and to make decisions about discrete color changes, apparently.
![](http://i.imgur.com/cqphoe3.png)
For AWIPS, the National Weather Service uses a mostly-blended color scale with a discrete jump to red at reflectivity values of 50 dBZ:
![](http://i.imgur.com/o18gmio.png)
50 dBZ corresponds to the 16-level color *light red* (**FF6060**). Note that `FF6060` is not used in the NWS AWIPS color scale, instead RGB value is given as `255,0,0` (hex code **FF0000**). 60 dBZ is not quite exactly where white starts, but it makes sense that it would. Obviously the AWIPS D2D authors took some liberties with their 256-level rendering, not adhering strictly to "dark red" for dBZ values between 60-65 (white was for 70 dBZ and above on the 16-level colormap). For this exercise we will assume 50 dBZ should be red and 60 dBZ white, and 75 dBZ cyan.
### Setup
> pip install python-awips matplotlib cartopy metpy
### Python Script
Download this script as a [Jupyter Notebook](http://nbviewer.jupyter.org/github/Unidata/python-awips/blob/master/examples/notebooks/NEXRAD_Level_3_Plot_with_Matplotlib.ipynb).
```python
from awips.dataaccess import DataAccessLayer
from awips import ThriftClient, RadarCommon
from dynamicserialize.dstypes.com.raytheon.uf.common.time import TimeRange
from dynamicserialize.dstypes.com.raytheon.uf.common.dataplugin.radar.request import GetRadarDataRecordRequest
from datetime import datetime
from datetime import timedelta
import matplotlib.pyplot as plt
import numpy as np
from numpy import ma
from metpy.plots import ctables
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
# set EDEX server and radar site definitions
site = 'kmux'
DataAccessLayer.changeEDEXHost('edex-cloud.unidata.ucar.edu')
request = DataAccessLayer.newDataRequest()
request.setDatatype('radar')
request.setLocationNames(site)
# Get latest time for site
datatimes = DataAccessLayer.getAvailableTimes(request)
dateTimeStr = str(datatimes[-1])
dateTimeStr = "2017-02-02 03:53:03"
buffer = 60 # seconds
dateTime = datetime.strptime(dateTimeStr, '%Y-%m-%d %H:%M:%S')
# Build timerange +/- buffer
beginRange = dateTime - timedelta(0, buffer)
endRange = dateTime + timedelta(0, buffer)
timerange = TimeRange(beginRange, endRange)
# GetRadarDataRecordRequest to query site with timerange
client = ThriftClient.ThriftClient('edex-cloud.unidata.ucar.edu')
request = GetRadarDataRecordRequest()
request.setTimeRange(timerange)
request.setRadarId(site)
# Map config
def make_map(bbox, projection=ccrs.PlateCarree()):
fig, ax = plt.subplots(figsize=(12, 12),
subplot_kw=dict(projection=projection))
ax.set_extent(bbox)
ax.coastlines(resolution='50m')
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
return fig, ax
# ctable defines the colortable, beginning value, data increment
# * For N0Q the scale is -20 to +75 dBZ in increments of 0.5 dBZ
# * For N0U the scale is -100 to +100 kts in increments of 1 kt
nexrad = {}
nexrad["N0Q"] = {
'id': 94,
'unit':'dBZ',
'name':'0.5 deg Base Reflectivity',
'ctable': ['NWSStormClearReflectivity',-20., 0.5],
'res': 1000.,
'elev': '0.5'
}
nexrad["N0U"] = {
'id': 99,
'unit':'kts',
'name':'0.5 deg Base Velocity',
'ctable': ['NWS8bitVel',-100.,1.],
'res': 250.,
'elev': '0.5'
}
grids = []
for code in nexrad:
request.setProductCode(nexrad[code]['id'])
request.setPrimaryElevationAngle(nexrad[code]['elev'])
response = client.sendRequest(request)
if response.getData():
for record in response.getData():
# Get record hdf5 data
idra = record.getHdf5Data()
rdat,azdat,depVals,threshVals = RadarCommon.get_hdf5_data(idra)
dim = rdat.getDimension()
lat,lon = float(record.getLatitude()),float(record.getLongitude())
radials,rangeGates = rdat.getSizes()
# Convert raw byte to pixel value
rawValue=np.array(rdat.getByteData())
array = []
for rec in rawValue:
if rec<0:
rec+=256
array.append(rec)
if azdat:
azVals = azdat.getFloatData()
az = np.array(RadarCommon.encode_radial(azVals))
dattyp = RadarCommon.get_data_type(azdat)
az = np.append(az,az[-1])
header = RadarCommon.get_header(record, format, rangeGates, radials, azdat, 'description')
rng = np.linspace(0, rangeGates, rangeGates + 1)
# Convert az/range to a lat/lon
from pyproj import Geod
g = Geod(ellps='clrk66')
center_lat = np.ones([len(az),len(rng)])*lat
center_lon = np.ones([len(az),len(rng)])*lon
az2D = np.ones_like(center_lat)*az[:,None]
rng2D = np.ones_like(center_lat)*np.transpose(rng[:,None])*nexrad[code]['res']
lons,lats,back=g.fwd(center_lon,center_lat,az2D,rng2D)
bbox = [lons.min(), lons.max(), lats.min(), lats.max()]
# Create 2d array
multiArray = np.reshape(array, (-1, rangeGates))
data = ma.array(multiArray)
# threshVals[0:2] contains halfwords 31,32,33 (min value, increment, num levels)
data = ma.array(threshVals[0]/10. + (multiArray)*threshVals[1]/10.)
if nexrad[code]['unit'] == 'kts':
data[data<-63] = ma.masked
data *= 1.94384 # Convert to knots
else:
data[data<=((threshVals[0]/10.)+threshVals[1]/10.)] = ma.masked
# Save our requested grids so we can render them multiple times
product = {
"code": code,
"bbox": bbox,
"lats": lats,
"lons": lons,
"data": data
}
grids.append(product)
print("Processed "+str(len(grids))+" grids.")
```
Processed 2 grids.
## Plot N0Q and N0U with Cartopy
```python
for rec in grids:
code = rec["code"]
bbox = rec["bbox"]
lats = rec["lats"]
lons = rec["lons"]
data = rec["data"]
# Create figure
%matplotlib inline
fig, ax = make_map(bbox=bbox)
# Colortable filename, beginning value, increment
ctable = nexrad[code]['ctable'][0]
beg = nexrad[code]['ctable'][1]
inc = nexrad[code]['ctable'][2]
norm, cmap = ctables.registry.get_with_steps(ctable, beg, inc)
cs = ax.pcolormesh(lons, lats, data, norm=norm, cmap=cmap)
ax.set_aspect('equal', 'datalim')
cbar = plt.colorbar(cs, extend='both', shrink=0.75, orientation='horizontal')
cbar.set_label(site.upper()+" "+ str(nexrad[code]['res']/1000.) +"km " \
+nexrad[code]['name']+" ("+code+") " \
+nexrad[code]['unit']+" " \
+str(record.getDataTime()))
# Zoom to within +-2 deg of center
ax.set_xlim(lon-2., lon+2.)
ax.set_ylim(lat-2., lat+2.)
plt.show()
```
![png](../images/output_3_0.png)
![png](../images/output_3_1.png)
compare with the same product scan rendered in AWIPS CAVE (slightly different projections and still some color mapping differences, most noticeable in ground clutter).
![](http://i.imgur.com/q7zPRod.gif)
# Two-panel plot, zoomed in
```python
fig, axes = plt.subplots(ncols=2,figsize=(12,9),
subplot_kw=dict(projection=ccrs.PlateCarree()))
i=0
for rec,ax in zip(grids, axes):
code = rec["code"]
bbox = rec["bbox"]
lats = rec["lats"]
lons = rec["lons"]
data = rec["data"]
# Create figure
ax.set_extent(bbox)
ax.coastlines(resolution='50m')
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
if i>0: gl.ylabels_left = False # hide right-pane left axis label
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
# Colortable filename, beginning value, increment
colorvals=nexrad[code]['ctable']
ctable = nexrad[code]['ctable'][0]
beg = nexrad[code]['ctable'][1]
inc = nexrad[code]['ctable'][2]
norm, cmap = ctables.registry.get_with_steps(ctable, beg, inc)
cs = ax.pcolormesh(lons, lats, data, norm=norm, cmap=cmap)
ax.set_aspect('equal', 'datalim')
cbar = fig.colorbar(cs, orientation='horizontal', ax=ax)
cbar.set_label(site.upper()+" "+code+" "+nexrad[code]['unit']+" "+str(record.getDataTime()))
plt.tight_layout()
# Zoom
ax.set_xlim(lon-.1, lon+.1)
ax.set_ylim(lat-.1, lat+.1)
i+=1
```
![png](../images/output_6_0.png)
and again compared to CAVE
![](http://i.imgur.com/YSr7sKB.png)

View file

@ -21,21 +21,738 @@ The [python-awips](https://python-awips.readthedocs.io/en/latest/) package provi
## Example
The simplest example requesting the RAP40 surface temperature grid from a remote EDEX server, saved to 2-dimensional Numpy arrays named `data`, `lons`, and `lats`.
This examples covers the callable methods of the Python AWIPS DAF when working with gridded data. We start with a connection to an EDEX server, then query data types, then grid names, parameters, levels, and other information. Finally the gridded data is plotted for its domain using Matplotlib and Cartopy.
from awips.dataaccess import DataAccessLayer
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest()
dataTypes = DataAccessLayer.getSupportedDatatypes()
request.setDatatype("grid")
request.addLocationNames("RAP40")
request.setParameters("T")
request.setLevels("0.0SFC")
cycles = DataAccessLayer.getAvailableTimes(request, True)
times = DataAccessLayer.getAvailableTimes(request)
response = DataAccessLayer.getGridData(request, times[-1])
for grid in response:
data = grid.getRawData()
lons, lats = grid.getLatLonCoords()
### DataAccessLayer.changeEDEXHost()
After DataAccessLayer is imported from the package `awips.dataaccess`, the first step is to define the EDEX data server hostname (`edex-cloud.unidata.ucar.edu` for these examples)
```python
from awips.dataaccess import DataAccessLayer
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
```
### DataAccessLayer.getSupportedDatatypes()
getSupportedDatatypes() returns a list of available data types offered by the EDEX server defined above.
```python
dataTypes = DataAccessLayer.getSupportedDatatypes()
list(dataTypes)
```
['acars',
'airep',
'binlightning',
'bufrmosavn',
'bufrmoseta',
'bufrmosgfs',
'bufrmoshpc',
'bufrmoslamp',
'bufrmosmrf',
'bufrua',
'climate',
'common_obs_spatial',
'ffmp',
'gfe',
'grid',
'hydro',
'ldadmesonet',
'maps',
'modelsounding',
'obs',
'pirep',
'practicewarning',
'profiler',
'radar',
'radar_spatial',
'satellite',
'sfcobs',
'topo',
'warning']
### DataAccessLayer.newDataRequest()
Now create a new data request, and set the data type to **grid** and "locationName" to **RAP40** with **setDataType()** and **setLocationNames()**
```python
request = DataAccessLayer.newDataRequest()
request.setDatatype("grid")
```
### DataAccessLayer.getAvailableLocationNames()
With datatype set to "grid", we can query all available grid names with **getAvailableLocationNames()**
```python
available_grids = DataAccessLayer.getAvailableLocationNames(request)
available_grids.sort()
list(available_grids)
```
['CMC',
'EKDMOS',
'EKDMOS-AK',
'ESTOFS',
'ETSS',
'FFG-ALR',
'FFG-FWR',
'FFG-KRF',
'FFG-MSR',
'FFG-ORN',
'FFG-PTR',
'FFG-RHA',
'FFG-RSA',
'FFG-STR',
'FFG-TAR',
'FFG-TIR',
'FFG-TUA',
'FNMOC-FAROP',
'FNMOC-NCODA',
'FNMOC-WW3',
'FNMOC-WW3-Europe',
'GFS',
'GFS20',
'GFSLAMP5',
'GribModel:9:151:172',
'HFR-EAST_6KM',
'HFR-EAST_PR_6KM',
'HFR-US_EAST_DELAWARE_1KM',
'HFR-US_EAST_FLORIDA_2KM',
'HFR-US_EAST_NORTH_2KM',
'HFR-US_EAST_SOUTH_2KM',
'HFR-US_EAST_VIRGINIA_1KM',
'HFR-US_HAWAII_1KM',
'HFR-US_HAWAII_2KM',
'HFR-US_HAWAII_6KM',
'HFR-US_WEST_500M',
'HFR-US_WEST_CENCAL_2KM',
'HFR-US_WEST_LOSANGELES_1KM',
'HFR-US_WEST_LOSOSOS_1KM',
'HFR-US_WEST_NORTH_2KM',
'HFR-US_WEST_SANFRAN_1KM',
'HFR-US_WEST_SOCAL_2KM',
'HFR-US_WEST_WASHINGTON_1KM',
'HFR-WEST_6KM',
'HPCGuide',
'HPCqpf',
'HPCqpfNDFD',
'HRRR',
'LAMP2p5',
'MOSGuide',
'MPE-Local-ALR',
'MPE-Local-FWR',
'MPE-Local-MSR',
'MPE-Local-ORN',
'MPE-Local-RHA',
'MPE-Local-SJU',
'MPE-Local-STR',
'MPE-Local-TAR',
'MPE-Local-TIR',
'MPE-Mosaic-ALR',
'MPE-Mosaic-FWR',
'MPE-Mosaic-MSR',
'MPE-Mosaic-ORN',
'MPE-Mosaic-RHA',
'MPE-Mosaic-SJU',
'MPE-Mosaic-TAR',
'MPE-Mosaic-TIR',
'NAM12',
'NAM40',
'NAVGEM',
'NCWF',
'NDFD',
'NOHRSC-SNOW',
'NamDNG',
'PROB3HR',
'QPE-ALR',
'QPE-Auto-TUA',
'QPE-FWR',
'QPE-KRF',
'QPE-MSR',
'QPE-Manual-KRF',
'QPE-ORN',
'QPE-RFC-PTR',
'QPE-RFC-RSA',
'QPE-RFC-STR',
'QPE-TIR',
'QPE-TUA',
'QPE-XNAV-ALR',
'QPE-XNAV-FWR',
'QPE-XNAV-KRF',
'QPE-XNAV-MSR',
'QPE-XNAV-ORN',
'QPE-XNAV-SJU',
'QPE-XNAV-TAR',
'QPE-XNAV-TIR',
'QPE-XNAV-TUA',
'RAP13',
'RAP20',
'RAP40',
'RFCqpf',
'RTMA',
'RTOFS-Now-WestAtl',
'RTOFS-Now-WestConus',
'RTOFS-WestAtl',
'RTOFS-WestConus',
'SeaIce',
'TPCWindProb',
'UKMET-MODEL1',
'URMA25',
'WaveWatch']
### Set grid name with `setLocationNames()`
```python
request.setLocationNames("RAP13")
```
# List Available Parameters for a Grid
### DataAccessLayer.getAvailableParameters()
After datatype and model name (locationName) are set, you can query all available parameters with **getAvailableParameters()**
```python
availableParms = DataAccessLayer.getAvailableParameters(request)
availableParms.sort()
list(availableParms)
```
['0to5',
'2xTP6hr',
'AV',
'Along',
'AppT',
'BLI',
'BlkMag',
'BlkShr',
'CAPE',
'CFRZR',
'CICEP',
'CIn',
'CP',
'CP1hr',
'CPr',
'CPrD',
'CRAIN',
'CSNOW',
'CURU',
'CXR',
'CapeStk',
'Corf',
'CorfF',
'CorfFM',
'CorfM',
'CritT1',
'DIABi',
'DivF',
'DivFn',
'DivFs',
'DpD',
'DpDt',
'DpT',
'Dpress',
'DthDt',
'EHI',
'EHI01',
'EHIi',
'EPT',
'EPTA',
'EPTC',
'EPTGrd',
'EPTGrdM',
'EPTs',
'EPVg',
'EPVs',
'EPVt1',
'EPVt2',
'FVecs',
'FeatMot',
'FnVecs',
'FsVecs',
'Fzra1',
'Fzra2',
'GH',
'GHxSM',
'GHxSM2',
'Gust',
'HI',
'HI1',
'HI3',
'HI4',
'HIdx',
'HPBL',
'Heli',
'Into',
'KI',
'L-I',
'LIsfc2x',
'LgSP1hr',
'MAdv',
'MCon',
'MCon2',
'MMSP',
'MSFDi',
'MSFi',
'MSFmi',
'MSG',
'MTV',
'Mix1',
'Mix2',
'Mmag',
'MnT',
'MpV',
'MxT',
'NBE',
'NetIO',
'OmDiff',
'P',
'PAdv',
'PBE',
'PFrnt',
'PGrd',
'PGrd1',
'PGrdM',
'PIVA',
'PR',
'PTvA',
'PTyp',
'PVV',
'PW',
'PW2',
'PoT',
'PoTA',
'QPV1',
'QPV2',
'QPV3',
'QPV4',
'REFC',
'RH',
'RH_001_bin',
'RH_002_bin',
'RM5',
'RMGH2',
'RRtype',
'RV',
'Rain1',
'Rain2',
'Rain3',
'Ro',
'SH',
'SHx',
'SLI',
'SNW',
'SNWA',
'SRMm',
'SRMmM',
'SSi',
'Shear',
'ShrMag',
'SnD',
'Snow1',
'Snow2',
'Snow3',
'SnowT',
'St-Pr',
'St-Pr1hr',
'StrTP',
'StrmMot',
'T',
'TAdv',
'TGrd',
'TGrdM',
'TP',
'TP12hr',
'TP168hr',
'TP1hr',
'TP24hr',
'TP36hr',
'TP3hr',
'TP48hr',
'TP6hr',
'TP72hr',
'TPrun',
'TPx12x6',
'TPx1x3',
'TQIND',
'TV',
'TW',
'T_001_bin',
'Tdef',
'Tdend',
'ThGrd',
'TmDpD',
'Tmax',
'Tmin',
'TotQi',
'Tstk',
'TwMax',
'TwMin',
'Twstk',
'TxSM',
'USTM',
'VAdv',
'VAdvAdvection',
'VSTM',
'Vis',
'WD',
'WEASD',
'WEASD1hr',
'WGS',
'Wind',
'WndChl',
'ageoVC',
'ageoW',
'ageoWM',
'cCape',
'cCin',
'cTOT',
'capeToLvl',
'dCape',
'dGH12',
'dP',
'dP1hr',
'dP3hr',
'dP6hr',
'dPW1hr',
'dPW3hr',
'dPW6hr',
'dT',
'dVAdv',
'dZ',
'defV',
'del2gH',
'df',
'fGen',
'fnD',
'fsD',
'gamma',
'gammaE',
'geoVort',
'geoW',
'geoWM',
'mixRat',
'msl-P',
'muCape',
'pV',
'pVeq',
'qDiv',
'qVec',
'qnVec',
'qsVec',
'shWlt',
'snoRatCrocus',
'snoRatEMCSREF',
'snoRatSPC',
'snoRatSPCdeep',
'snoRatSPCsurface',
'swtIdx',
'tTOT',
'tWind',
'tWindU',
'tWindV',
'uFX',
'uW',
'vSmthW',
'vTOT',
'vW',
'vertCirc',
'wDiv',
'wSp',
'wSp_001_bin',
'wSp_002_bin',
'wSp_003_bin',
'wSp_004_bin',
'zAGL']
### setParameters()
set the request parameter
```python
request.setParameters("T")
```
## List Available Levels for Parameter
Using **DataAccessLayer.getAvailableLevels()**
```python
availableLevels = DataAccessLayer.getAvailableLevels(request)
for level in availableLevels:
print(level)
```
0.0SFC
350.0MB
475.0MB
225.0MB
120.0_150.0BL
900.0MB
125.0MB
450.0MB
575.0MB
325.0MB
100.0MB
1000.0MB
60.0_90.0BL
275.0MB
1.0PV
950.0MB
150.0MB
1.5PV
700.0MB
825.0MB
150.0_180.0BL
250.0MB
375.0MB
1000.0_500.0MB
800.0MB
925.0MB
2.0PV
0.5PV
0.0TROP
750.0MB
500.0MB
625.0MB
400.0MB
0.0FHAG
2.0FHAG
875.0MB
175.0MB
850.0MB
600.0MB
725.0MB
975.0MB
550.0MB
675.0MB
425.0MB
200.0MB
0.0_30.0BL
30.0_60.0BL
650.0MB
525.0MB
300.0MB
90.0_120.0BL
775.0MB
0.0TILT
0.5TILT
340.0_350.0K
290.0_300.0K
700.0_600.0MB
700.0_300.0MB
320.0Ke
800.0_750.0MB
60.0TILT
5.3TILT
1000.0_900.0MB
340.0K
255.0K
255.0_265.0K
25.0TILT
1000.0_850.0MB
850.0_250.0MB
280.0_290.0Ke
320.0_330.0K
310.0_320.0Ke
310.0Ke
330.0K
900.0_800.0MB
550.0_500.0MB
2.4TILT
50.0TILT
35.0TILT
12.0TILT
300.0_310.0K
0.9TILT
320.0K
400.0_350.0MB
750.0_700.0MB
345.0K
250.0_260.0K
300.0Ke
290.0Ke
950.0_900.0MB
275.0_285.0Ke
335.0Ke
295.0_305.0Ke
275.0_285.0K
600.0_550.0MB
310.0K
335.0K
700.0_500.0MB
325.0_335.0K
300.0K
0.0MAXOMEGA
315.0_325.0K
325.0K
340.0Ke
300.0_250.0MB
1.5TILT
335.0_345.0K
315.0K
3.4TILT
330.0Ke
500.0_400.0MB
305.0K
285.0_295.0Ke
14.0TILT
325.0_335.0Ke
850.0_800.0MB
295.0Ke
305.0Ke
265.0_275.0K
700.0_650.0MB
450.0_400.0MB
1.8TILT
330.0_340.0K
800.0_700.0MB
850.0_300.0MB
6.0TILT
900.0_850.0MB
320.0_330.0Ke
8.7TILT
650.0_600.0MB
600.0_400.0MB
55.0TILT
270.0_280.0Ke
30.0TILT
310.0_320.0K
1000.0_950.0MB
250.0_200.0MB
400.0_300.0MB
500.0_100.0MB
285.0Ke
290.0K
305.0_315.0K
285.0_295.0K
925.0_850.0MB
275.0Ke
300.0_200.0MB
260.0_270.0K
315.0_325.0Ke
600.0_500.0MB
16.7TILT
280.0K
500.0_250.0MB
40.0TILT
400.0_200.0MB
300.0_310.0Ke
270.0_280.0K
1000.0_700.0MB
45.0TILT
850.0_500.0MB
295.0K
4.3TILT
295.0_305.0K
330.0_340.0Ke
270.0K
280.0_290.0K
925.0_700.0MB
260.0K
10.0TILT
325.0Ke
285.0K
290.0_300.0Ke
7.5TILT
280.0Ke
500.0_450.0MB
305.0_315.0Ke
250.0K
250.0_350.0K
270.0Ke
275.0K
315.0Ke
500.0_300.0MB
350.0_300.0MB
19.5TILT
850.0_700.0MB
350.0K
265.0K
0.0_0.0SFC
* **0.0SFC** is the Surface level
* **FHAG** stands for Fixed Height Above Ground (in meters)
* **NTAT** stands for Nominal Top of the ATmosphere
* **BL** stands for Boundary Layer, where **0.0_30.0BL** reads as *0-30 mb above ground level*
* **TROP** is the Tropopause level
### request.setLevels()
For this example we will use Surface Temperature
```python
request.setLevels("2.0FHAG")
```
### DataAccessLayer.getAvailableTimes()
* **getAvailableTimes(request, True)** will return an object of *run times* - formatted as `YYYY-MM-DD HH:MM:SS`
* **getAvailableTimes(request)** will return an object of all times - formatted as `YYYY-MM-DD HH:MM:SS (F:ff)`
* **getForecastRun(cycle, times)** will return a DataTime array for a single forecast cycle.
# Request a Grid
### DataAccessLayer.getGridData()
Now that we have our `request` and DataTime `fcstRun` arrays ready, it's time to request the data array from EDEX.
```python
cycles = DataAccessLayer.getAvailableTimes(request, True)
times = DataAccessLayer.getAvailableTimes(request)
fcstRun = DataAccessLayer.getForecastRun(cycles[-1], times)
response = DataAccessLayer.getGridData(request, [fcstRun[-1]])
```
```python
for grid in response:
data = grid.getRawData()
lons, lats = grid.getLatLonCoords()
print('Time :', str(grid.getDataTime()))
print('Model:', str(grid.getLocationName()))
print('Parm :', str(grid.getParameter()))
print('Unit :', str(grid.getUnit()))
print(data.shape)
print(data.min(), data.max())
```
('Time :', '2017-08-14 14:00:00 (21)')
('Model:', 'RAP13')
('Parm :', 'T')
('Unit :', 'K')
(337, 451)
(271.21939, 306.71939)

View file

@ -0,0 +1,249 @@
Satellite images are returned by Python AWIPS as grids, and can be rendered with Cartopy pcolormesh the same as gridded forecast models in other python-awips examples.
```python
%matplotlib inline
from awips.dataaccess import DataAccessLayer
import cartopy.crs as ccrs
import cartopy.feature as cfeat
import matplotlib.pyplot as plt
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import numpy as np
import datetime
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest()
request.setDatatype("satellite")
```
### Available Satellite Sectors and Products
```python
availableSectors = DataAccessLayer.getAvailableLocationNames(request)
availableSectors.sort()
print("\nAvailable sectors and products\n")
for sect in availableSectors:
request.setLocationNames(sect)
availableProducts = DataAccessLayer.getAvailableParameters(request)
availableProducts.sort()
print(sect + ":")
for prod in availableProducts:
print(" - "+prod)
```
Available sectors and products
* Alaska National:
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
- Percent of Normal TPW
- Rain fall rate
- Sounder Based Derived Precipitable Water (PW)
* Alaska Regional:
- Imager 11 micron IR
- Imager 3.9 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* East CONUS:
- Imager 11 micron IR
- Imager 13 micron (IR)
- Imager 3.9 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
- Low cloud base imagery
* GOES-East:
- Imager 11 micron IR
- Imager 13 micron IR
- Imager 3.5-4.0 micron IR (Fog)
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* GOES-East-West:
- Imager 11 micron IR
- Imager 13 micron IR
- Imager 3.5-4.0 micron IR (Fog)
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* GOES-Sounder:
- CAPE
- Sounder Based Derived Lifted Index (LI)
- Sounder Based Derived Precipitable Water (PW)
- Sounder Based Derived Surface Skin Temp (SFC Skin)
- Sounder Based Total Column Ozone
* GOES-West:
- Imager 11 micron IR
- Imager 13 micron IR
- Imager 3.5-4.0 micron IR (Fog)
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* Global:
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
* Hawaii National:
- Gridded Cloud Amount
- Gridded Cloud Top Pressure or Height
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
- Percent of Normal TPW
- Rain fall rate
- Sounder 11.03 micron imagery
- Sounder 14.06 micron imagery
- Sounder 3.98 micron imagery
- Sounder 4.45 micron imagery
- Sounder 6.51 micron imagery
- Sounder 7.02 micron imagery
- Sounder 7.43 micron imagery
- Sounder Based Derived Lifted Index (LI)
- Sounder Based Derived Precipitable Water (PW)
- Sounder Based Derived Surface Skin Temp (SFC Skin)
- Sounder Visible imagery
* Hawaii Regional:
- Imager 11 micron IR
- Imager 13 micron (IR)
- Imager 3.9 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* Mollweide:
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
* NEXRCOMP:
- DHR
- DVL
- EET
- HHC
- N0R
- N1P
- NTP
* NH Composite - Meteosat-GOES E-GOES W-GMS:
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* Northern Hemisphere Composite:
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* Puerto Rico National:
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
- Percent of Normal TPW
- Rain fall rate
- Sounder Based Derived Precipitable Water (PW)
* Puerto Rico Regional:
- Imager 11 micron IR
- Imager 13 micron (IR)
- Imager 3.9 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
* Supernational:
- Gridded Cloud Amount
- Gridded Cloud Top Pressure or Height
- Imager 11 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
- Percent of Normal TPW
- Rain fall rate
- Sounder Based Derived Lifted Index (LI)
- Sounder Based Derived Precipitable Water (PW)
- Sounder Based Derived Surface Skin Temp (SFC Skin)
* West CONUS:
- Imager 11 micron IR
- Imager 13 micron (IR)
- Imager 3.9 micron IR
- Imager 6.7-6.5 micron IR (WV)
- Imager Visible
- Low cloud base imagery
- Sounder 11.03 micron imagery
- Sounder 14.06 micron imagery
- Sounder 3.98 micron imagery
- Sounder 4.45 micron imagery
- Sounder 6.51 micron imagery
- Sounder 7.02 micron imagery
- Sounder 7.43 micron imagery
- Sounder Visible imagery
### Plot Global Water Vapor Composite
```python
request.setLocationNames("Global")
availableProducts = DataAccessLayer.getAvailableParameters(request)
availableProducts.sort()
request.setParameters(availableProducts[0])
utc = datetime.datetime.utcnow()
times = DataAccessLayer.getAvailableTimes(request)
hourdiff = utc - datetime.datetime.strptime(str(times[-1]),'%Y-%m-%d %H:%M:%S')
hours,days = hourdiff.seconds/3600,hourdiff.days
minute = str((hourdiff.seconds - (3600 * hours)) / 60)
offsetStr = ''
if hours > 0:
offsetStr += str(hours) + "hr "
offsetStr += str(minute) + "m ago"
if days > 1:
offsetStr = str(days) + " days ago"
print("Found "+ str(len(times)) +" available times")
print(" "+str(times[0]) + "\n to\n " + str(times[-1]))
print("Using "+str(times[-1]) + " ("+offsetStr+")")
```
> Found 96 available times
> 2017-01-23 00:00:00
> to
> 2017-02-03 21:00:00
> Using 2017-02-03 21:00:00 (2hr 3m ago)
```python
response = DataAccessLayer.getGridData(request, [times[-1]])
grid = response[0]
data = grid.getRawData()
lons,lats = grid.getLatLonCoords()
bbox = [lons.min(), lons.max(), lats.min(), lats.max()]
print("grid size " + str(data.shape))
print("grid extent " + str(list(bbox)))
```
> grid size (1024, 2048)
> grid extent [-179.91191, 179.99982, -89.977936, 89.890022]
```python
def make_map(bbox, projection=ccrs.PlateCarree()):
fig, ax = plt.subplots(figsize=(18,14),
subplot_kw=dict(projection=projection))
ax.set_extent(bbox)
ax.coastlines(resolution='50m')
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
return fig, ax
fig, ax = make_map(bbox=bbox)
# State boundaries
states = cfeat.NaturalEarthFeature(category='cultural',
name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states, linestyle=':')
cs = ax.pcolormesh(lons, lats, data, cmap='Greys_r')
cbar = fig.colorbar(cs, shrink=0.9, orientation='horizontal')
cbar.set_label(str(grid.getLocationName())+" " \
+str(grid.getParameter())+" " \
+str(grid.getDataTime().getRefTime()))
plt.tight_layout()
```
![png](../images/output_7_0.png)

View file

@ -0,0 +1,205 @@
Based on the MetPy example ["Station Plot with Layout"](http://metpy.readthedocs.org/en/latest/examples/generated/Station_Plot_with_Layout.html)
```python
import datetime
import pandas
import matplotlib.pyplot as plt
import numpy as np
import pprint
from awips.dataaccess import DataAccessLayer
from metpy.calc import get_wind_components
from metpy.cbook import get_test_data
from metpy.plots.wx_symbols import sky_cover, current_weather
from metpy.plots import StationPlot, StationPlotLayout, simple_layout
from metpy.units import units
def get_cloud_cover(code):
if 'OVC' in code:
return 1.0
elif 'BKN' in code:
return 6.0/8.0
elif 'SCT' in code:
return 4.0/8.0
elif 'FEW' in code:
return 2.0/8.0
else:
return 0
state_capital_wx_stations = {'Washington':'KOLM', 'Oregon':'KSLE', 'California':'KSAC',
'Nevada':'KCXP', 'Idaho':'KBOI', 'Montana':'KHLN',
'Utah':'KSLC', 'Arizona':'KDVT', 'New Mexico':'KSAF',
'Colorado':'KBKF', 'Wyoming':'KCYS', 'North Dakota':'KBIS',
'South Dakota':'KPIR', 'Nebraska':'KLNK', 'Kansas':'KTOP',
'Oklahoma':'KPWA', 'Texas':'KATT', 'Louisiana':'KBTR',
'Arkansas':'KLIT', 'Missouri':'KJEF', 'Iowa':'KDSM',
'Minnesota':'KSTP', 'Wisconsin':'KMSN', 'Illinois':'KSPI',
'Mississippi':'KHKS', 'Alabama':'KMGM', 'Nashville':'KBNA',
'Kentucky':'KFFT', 'Indiana':'KIND', 'Michigan':'KLAN',
'Ohio':'KCMH', 'Georgia':'KFTY', 'Florida':'KTLH',
'South Carolina':'KCUB', 'North Carolina':'KRDU',
'Virginia':'KRIC', 'West Virginia':'KCRW',
'Pennsylvania':'KCXY', 'New York':'KALB', 'Vermont':'KMPV',
'New Hampshire':'KCON', 'Maine':'KAUG', 'Massachusetts':'KBOS',
'Rhode Island':'KPVD', 'Connecticut':'KHFD', 'New Jersey':'KTTN',
'Delaware':'KDOV', 'Maryland':'KNAK'}
single_value_params = ["timeObs", "stationName", "longitude", "latitude",
"temperature", "dewpoint", "windDir",
"windSpeed", "seaLevelPress"]
multi_value_params = ["presWeather", "skyCover", "skyLayerBase"]
all_params = single_value_params + multi_value_params
obs_dict = dict({all_params: [] for all_params in all_params})
pres_weather = []
sky_cov = []
sky_layer_base = []
```
```python
from dynamicserialize.dstypes.com.raytheon.uf.common.time import TimeRange
from datetime import datetime, timedelta
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)
```
```python
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest()
request.setDatatype("obs")
request.setParameters(*(all_params))
request.setLocationNames(*(state_capital_wx_stations.values()))
```
```python
response = DataAccessLayer.getGeometryData(request,timerange)
for ob in response:
avail_params = ob.getParameters()
if "presWeather" in avail_params:
pres_weather.append(ob.getString("presWeather"))
elif "skyCover" in avail_params and "skyLayerBase" in avail_params:
sky_cov.append(ob.getString("skyCover"))
sky_layer_base.append(ob.getNumber("skyLayerBase"))
else:
for param in single_value_params:
if param in avail_params:
if param == 'timeObs':
obs_dict[param].append(datetime.fromtimestamp(ob.getNumber(param)/1000.0))
else:
try:
obs_dict[param].append(ob.getNumber(param))
except TypeError:
obs_dict[param].append(ob.getString(param))
else:
obs_dict[param].append(None)
obs_dict['presWeather'].append(pres_weather);
obs_dict['skyCover'].append(sky_cov);
obs_dict['skyLayerBase'].append(sky_layer_base);
pres_weather = []
sky_cov = []
sky_layer_base = []
```
We can now use pandas to retrieve desired subsets of our observations.
In this case, return the most recent observation for each station.
```python
df = pandas.DataFrame(data=obs_dict, columns=all_params)
#sort rows with the newest first
df = df.sort_values(by='timeObs', ascending=False)
#group rows by station
groups = df.groupby('stationName')
#create a new DataFrame for the most recent values
df_recent = pandas.DataFrame(columns=all_params)
#retrieve the first entry for each group, which will
#be the most recent observation
for rid, station in groups:
row = station.head(1)
df_recent = pandas.concat([df_recent, row])
```
Convert DataFrame to something metpy-readable by
attaching units and calculating derived values
```python
data = dict()
data['stid'] = np.array(df_recent["stationName"])
data['latitude'] = np.array(df_recent['latitude'])
data['longitude'] = np.array(df_recent['longitude'])
data['air_temperature'] = np.array(df_recent['temperature'], dtype=float)* units.degC
data['dew_point'] = np.array(df_recent['dewpoint'], dtype=float)* units.degC
data['slp'] = np.array(df_recent['seaLevelPress'])* units('mbar')
u, v = get_wind_components(np.array(df_recent['windSpeed']) * units('knots'),
np.array(df_recent['windDir']) * units.degree)
data['eastward_wind'], data['northward_wind'] = u, v
data['cloud_frac'] = [int(get_cloud_cover(x)*8) for x in df_recent['skyCover']]
```
```python
%matplotlib inline
import cartopy.crs as ccrs
import cartopy.feature as feat
from matplotlib import rcParams
rcParams['savefig.dpi'] = 100
proj = ccrs.LambertConformal(central_longitude=-95, central_latitude=35,
standard_parallels=[35])
state_boundaries = feat.NaturalEarthFeature(category='cultural',
name='admin_1_states_provinces_lines',
scale='110m', facecolor='none')
# Create the figure
fig = plt.figure(figsize=(20, 15))
ax = fig.add_subplot(1, 1, 1, projection=proj)
# Add map elements
ax.add_feature(feat.LAND, zorder=-1)
ax.add_feature(feat.OCEAN, zorder=-1)
ax.add_feature(feat.LAKES, zorder=-1)
ax.coastlines(resolution='110m', zorder=2, color='black')
ax.add_feature(state_boundaries)
ax.add_feature(feat.BORDERS, linewidth='2', edgecolor='black')
ax.set_extent((-120, -70, 20, 50))
# Start the station plot by specifying the axes to draw on, as well as the
# lon/lat of the stations (with transform). We also set the fontsize to 12 pt.
stationplot = StationPlot(ax, data['longitude'], data['latitude'],
transform=ccrs.PlateCarree(), fontsize=12)
# The layout knows where everything should go, and things are standardized using
# the names of variables. So the layout pulls arrays out of `data` and plots them
# using `stationplot`.
simple_layout.plot(stationplot, data)
# Plot the temperature and dew point to the upper and lower left, respectively, of
# the center point. Each one uses a different color.
stationplot.plot_parameter('NW', np.array(data['air_temperature']), color='red')
stationplot.plot_parameter('SW', np.array(data['dew_point']), color='darkgreen')
# A more complex example uses a custom formatter to control how the sea-level pressure
# values are plotted. This uses the standard trailing 3-digits of the pressure value
# in tenths of millibars.
stationplot.plot_parameter('NE', np.array(data['slp']),
formatter=lambda v: format(10 * v, '.0f')[-3:])
# Plot the cloud cover symbols in the center location. This uses the codes made above and
# uses the `sky_cover` mapper to convert these values to font codes for the
# weather symbol font.
stationplot.plot_symbol('C', data['cloud_frac'], sky_cover)
# Also plot the actual text of the station id. Instead of cardinal directions,
# plot further out by specifying a location of 2 increments in x and 0 in y.
stationplot.plot_text((2, 0), np.array(obs_dict["stationName"]))
plt.title("Most Recent Observations for State Capitals")
```
![png](../images/output_9_1.png)

View file

@ -0,0 +1,136 @@
The following script takes you through the steps of retrieving an Upper Air vertical profile from an AWIPS EDEX server and plotting a Skew-T/Log-P chart with Matplotlib and MetPy.
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.
```python
%matplotlib inline
from awips.dataaccess import DataAccessLayer
import matplotlib.tri as mtri
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import numpy as np
import math
from metpy.calc import get_wind_speed, get_wind_components, lcl, dry_lapse, parcel_profile
from metpy.plots import SkewT, Hodograph
from metpy.units import units, concatenate
# Set host
DataAccessLayer.changeEDEXHost("edex-cloud.unidata.ucar.edu")
request = DataAccessLayer.newDataRequest()
# Set data type
request.setDatatype("bufrua")
availableLocs = DataAccessLayer.getAvailableLocationNames(request)
availableLocs.sort()
# Set Mandatory and Significant Temperature level parameters
MAN_PARAMS = set(['prMan', 'htMan', 'tpMan', 'tdMan', 'wdMan', 'wsMan'])
SIGT_PARAMS = set(['prSigT', 'tpSigT', 'tdSigT'])
request.setParameters("wmoStaNum", "validTime", "rptType", "staElev", "numMand",
"numSigT", "numSigW", "numTrop", "numMwnd", "staName")
request.getParameters().extend(MAN_PARAMS)
request.getParameters().extend(SIGT_PARAMS)
# Set station ID (not name)
request.setLocationNames("72562") #KLBF
# Get all times
datatimes = DataAccessLayer.getAvailableTimes(request)
# Get most recent record
response = DataAccessLayer.getGeometryData(request,times=datatimes[-1].validPeriod)
# Initialize data arrays
tdMan,tpMan,prMan,wdMan,wsMan = np.array([]),np.array([]),np.array([]),np.array([]),np.array([])
prSig,tpSig,tdSig = np.array([]),np.array([]),np.array([])
manGeos = []
sigtGeos = []
# Build arrays
for ob in response:
if set(ob.getParameters()) & MAN_PARAMS:
manGeos.append(ob)
prMan = np.append(prMan,ob.getNumber("prMan"))
tpMan = np.append(tpMan,ob.getNumber("tpMan"))
tdMan = np.append(tdMan,ob.getNumber("tdMan"))
wdMan = np.append(wdMan,ob.getNumber("wdMan"))
wsMan = np.append(wsMan,ob.getNumber("wsMan"))
continue
if set(ob.getParameters()) & SIGT_PARAMS:
sigtGeos.append(ob)
prSig = np.append(prSig,ob.getNumber("prSigT"))
tpSig = np.append(tpSig,ob.getNumber("tpSigT"))
tdSig = np.append(tdSig,ob.getNumber("tdSigT"))
continue
# Sort mandatory levels (but not sigT levels) because of the 1000.MB interpolation inclusion
ps = prMan.argsort()[::-1]
wpres = prMan[ps]
direc = wdMan[ps]
spd = wsMan[ps]
tman = tpMan[ps]
dman = tdMan[ps]
# Flag missing data
prSig[prSig <= -9999] = np.nan
tpSig[tpSig <= -9999] = np.nan
tdSig[tdSig <= -9999] = np.nan
wpres[wpres <= -9999] = np.nan
tman[tman <= -9999] = np.nan
dman[dman <= -9999] = np.nan
direc[direc <= -9999] = np.nan
spd[spd <= -9999] = np.nan
# assign units
p = (prSig/100) * units.mbar
T = (tpSig-273.15) * units.degC
Td = (tdSig-273.15) * units.degC
wpres = (wpres/100) * units.mbar
tman = tman * units.degC
dman = dman * units.degC
u,v = get_wind_components(spd, np.deg2rad(direc))
# Create SkewT/LogP
plt.rcParams['figure.figsize'] = (8, 10)
skew = SkewT()
skew.plot(p, T, 'r', linewidth=2)
skew.plot(p, Td, 'g', linewidth=2)
skew.plot_barbs(wpres, u, v)
skew.ax.set_ylim(1000, 100)
skew.ax.set_xlim(-30, 30)
title_string = " T(F) Td "
title_string += " " + str(ob.getString("staName"))
title_string += " " + str(ob.getDataTime().getRefTime())
title_string += " (" + str(ob.getNumber("staElev")) + "m elev)"
title_string += "\n" + str(round(T[0].to('degF').item(),1))
title_string += " " + str(round(Td[0].to('degF').item(),1))
plt.title(title_string, loc='left')
# Calculate LCL height and plot as black dot
l = lcl(p[0], T[0], Td[0])
lcl_temp = dry_lapse(concatenate((p[0], l)), T[0])[-1].to('degC')
skew.plot(l, lcl_temp, 'ko', markerfacecolor='black')
# Calculate full parcel profile and add to plot as black line
prof = parcel_profile(p, T[0], Td[0]).to('degC')
skew.plot(p, prof, 'k', linewidth=2)
# An example of a slanted line at constant T -- in this case the 0 isotherm
l = skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
# Draw hodograph
ax_hod = inset_axes(skew.ax, '30%', '30%', loc=3)
h = Hodograph(ax_hod, component_range=max(wsMan))
h.add_grid(increment=20)
h.plot_colormapped(u, v, spd)
# Show the plot
plt.show()
```
![png](../images/output_1_0.png)