|
1
|
|
|
# Copyright (c) 2008-2016 MetPy Developers. |
|
2
|
|
|
# Distributed under the terms of the BSD 3-Clause License. |
|
3
|
|
|
# SPDX-License-Identifier: BSD-3-Clause |
|
4
|
|
|
""" |
|
5
|
|
|
Four Panel Map |
|
6
|
|
|
=============== |
|
7
|
|
|
|
|
8
|
|
|
By reading model output data from a netCDF file, we can create a four panel plot showing: |
|
9
|
|
|
|
|
10
|
|
|
* 300 hPa heights and winds |
|
11
|
|
|
* 500 hPa heights and absolute vorticity |
|
12
|
|
|
* Surface temperatures |
|
13
|
|
|
* Precipitable water |
|
14
|
|
|
""" |
|
15
|
|
|
|
|
16
|
|
|
########################################### |
|
17
|
|
|
|
|
18
|
|
|
import cartopy.crs as ccrs |
|
19
|
|
|
import cartopy.feature as cfeature |
|
20
|
|
|
import matplotlib.gridspec as gridspec |
|
21
|
|
|
import matplotlib.pyplot as plt |
|
22
|
|
|
import netCDF4 |
|
23
|
|
|
import numpy as np |
|
24
|
|
|
import scipy.ndimage as ndimage |
|
25
|
|
|
|
|
26
|
|
|
from metpy.cbook import get_test_data |
|
27
|
|
|
from metpy.units import units |
|
28
|
|
|
|
|
29
|
|
|
########################################### |
|
30
|
|
|
|
|
31
|
|
|
# Make state boundaries feature |
|
32
|
|
|
states_provinces = cfeature.NaturalEarthFeature(category='cultural', |
|
33
|
|
|
name='admin_1_states_provinces_lines', |
|
34
|
|
|
scale='50m', facecolor='none') |
|
35
|
|
|
|
|
36
|
|
|
# Make country borders feature |
|
37
|
|
|
country_borders = cfeature.NaturalEarthFeature(category='cultural', |
|
38
|
|
|
name='admin_0_countries', |
|
39
|
|
|
scale='50m', facecolor='none') |
|
40
|
|
|
|
|
41
|
|
|
crs = ccrs.LambertConformal(central_longitude=-100.0, central_latitude=45.0) |
|
42
|
|
|
|
|
43
|
|
|
########################################### |
|
44
|
|
|
|
|
45
|
|
|
|
|
46
|
|
|
# Function used to create the map subplots |
|
47
|
|
|
def plot_background(ax): |
|
48
|
|
|
ax.set_extent([235., 290., 20., 55.]) |
|
49
|
|
|
ax.coastlines('50m', edgecolor='black', linewidth=0.5) |
|
50
|
|
|
ax.add_feature(states_provinces, edgecolor='black', linewidth=0.5) |
|
51
|
|
|
ax.add_feature(country_borders, edgecolor='black', linewidth=0.5) |
|
52
|
|
|
return ax |
|
53
|
|
|
|
|
54
|
|
|
|
|
55
|
|
|
########################################### |
|
56
|
|
|
|
|
57
|
|
|
# Open the example netCDF data |
|
58
|
|
|
ds = netCDF4.Dataset(get_test_data('gfs_output.nc', False)) |
|
59
|
|
|
print(ds) |
|
60
|
|
|
|
|
61
|
|
|
########################################### |
|
62
|
|
|
|
|
63
|
|
|
# Convert number of hours since the reference time into an actual date |
|
64
|
|
|
time_vals = netCDF4.num2date(ds.variables['time'][:].squeeze(), ds.variables['time'].units) |
|
65
|
|
|
|
|
66
|
|
|
########################################### |
|
67
|
|
|
|
|
68
|
|
|
# Combine 1D latitude and longitudes into a 2D grid of locations |
|
69
|
|
|
lon_2d, lat_2d = np.meshgrid(ds.variables['lon'][:], ds.variables['lat'][:]) |
|
70
|
|
|
|
|
71
|
|
|
########################################### |
|
72
|
|
|
|
|
73
|
|
|
# Assign units |
|
74
|
|
|
vort_500 = ds.variables['vort_500'][0] * units(ds.variables['vort_500'].units) |
|
75
|
|
|
surface_temp = ds.variables['temp'][0] * units(ds.variables['temp'].units) |
|
76
|
|
|
precip_water = ds.variables['precip_water'][0] * units(ds.variables['precip_water'].units) |
|
77
|
|
|
winds_300 = ds.variables['winds_300'][0] * units(ds.variables['winds_300'].units) |
|
78
|
|
|
|
|
79
|
|
|
########################################### |
|
80
|
|
|
|
|
81
|
|
|
# Do unit conversions to what we wish to plot |
|
82
|
|
|
vort_500 = vort_500 * 1e5 |
|
83
|
|
|
surface_temp = surface_temp.to('degF') |
|
84
|
|
|
precip_water = precip_water.to('inches') |
|
85
|
|
|
winds_300 = winds_300.to('knots') |
|
86
|
|
|
|
|
87
|
|
|
########################################### |
|
88
|
|
|
|
|
89
|
|
|
# Smooth the height data |
|
90
|
|
|
heights_300 = ndimage.gaussian_filter(ds.variables['heights_300'][0], sigma=1.5, order=0) |
|
91
|
|
|
heights_500 = ndimage.gaussian_filter(ds.variables['heights_500'][0], sigma=1.5, order=0) |
|
92
|
|
|
|
|
93
|
|
|
########################################### |
|
94
|
|
|
|
|
95
|
|
|
# Create the figure |
|
96
|
|
|
fig = plt.figure(figsize=(20, 15)) |
|
97
|
|
|
gs = gridspec.GridSpec(5, 2, height_ratios=[1, .05, 1, .05, 0], bottom=.05, top=.95, wspace=.1) |
|
98
|
|
|
|
|
99
|
|
|
# Upper left plot - 300-hPa winds and geopotential heights |
|
100
|
|
|
ax1 = plt.subplot(gs[0, 0], projection=crs) |
|
101
|
|
|
plot_background(ax1) |
|
102
|
|
|
cf1 = ax1.contourf(lon_2d, lat_2d, winds_300, cmap='cool', transform=ccrs.PlateCarree()) |
|
103
|
|
|
c1 = ax1.contour(lon_2d, lat_2d, heights_300, colors='black', linewidth=2, |
|
104
|
|
|
transform=ccrs.PlateCarree()) |
|
105
|
|
|
plt.clabel(c1, fontsize=10, inline=1, inline_spacing=1, fmt='%i', rightside_up=True) |
|
106
|
|
|
|
|
107
|
|
|
ax2 = plt.subplot(gs[1, 0]) |
|
108
|
|
|
cb1 = plt.colorbar(cf1, cax=ax2, orientation='horizontal') |
|
109
|
|
|
cb1.set_label('knots', size='x-large') |
|
110
|
|
|
ax1.set_title('300-hPa Wind Speeds and Heights', fontsize=16) |
|
111
|
|
|
|
|
112
|
|
|
# Upper right plot - 500mb absolute vorticity and geopotential heights |
|
113
|
|
|
ax3 = plt.subplot(gs[0, 1], projection=crs) |
|
114
|
|
|
plot_background(ax3) |
|
115
|
|
|
cf2 = ax3.contourf(lon_2d, lat_2d, vort_500, cmap='BrBG', transform=ccrs.PlateCarree(), |
|
116
|
|
|
zorder=0, norm=plt.Normalize(-32, 32), latlon=True) |
|
117
|
|
|
c2 = ax3.contour(lon_2d, lat_2d, heights_500, colors='k', lw=2, transform=ccrs.PlateCarree()) |
|
118
|
|
|
plt.clabel(c2, fontsize=10, inline=1, inline_spacing=1, fmt='%i', rightside_up=True) |
|
119
|
|
|
|
|
120
|
|
|
ax4 = plt.subplot(gs[1, 1]) |
|
121
|
|
|
cb2 = plt.colorbar(cf2, cax=ax4, orientation='horizontal') |
|
122
|
|
|
cb2.set_label(r'$10^{-5}$ s$^{-1}$', size='x-large') |
|
123
|
|
|
ax3.set_title('500-hPa Absolute Vorticity and Heights', fontsize=16) |
|
124
|
|
|
|
|
125
|
|
|
# Lower left plot - surface temperatures |
|
126
|
|
|
ax5 = plt.subplot(gs[2, 0], projection=crs) |
|
127
|
|
|
plot_background(ax5) |
|
128
|
|
|
cf3 = ax5.contourf(lon_2d, lat_2d, surface_temp, cmap='YlOrRd', |
|
129
|
|
|
transform=ccrs.PlateCarree(), zorder=0) |
|
130
|
|
|
|
|
131
|
|
|
ax6 = plt.subplot(gs[3, 0]) |
|
132
|
|
|
cb3 = plt.colorbar(cf3, cax=ax6, orientation='horizontal') |
|
133
|
|
|
cb3.set_label(u'\N{DEGREE FAHRENHEIT}', size='x-large') |
|
134
|
|
|
ax5.set_title('Surface Temperatures', fontsize=16) |
|
135
|
|
|
|
|
136
|
|
|
# Lower right plot - precipitable water entire atmosphere |
|
137
|
|
|
ax7 = plt.subplot(gs[2, 1], projection=crs) |
|
138
|
|
|
plot_background(ax7) |
|
139
|
|
|
cf4 = plt.contourf(lon_2d, lat_2d, precip_water, cmap='Greens', |
|
140
|
|
|
transform=ccrs.PlateCarree(), zorder=0) |
|
141
|
|
|
|
|
142
|
|
|
ax8 = plt.subplot(gs[3, 1]) |
|
143
|
|
|
cb4 = plt.colorbar(cf4, cax=ax8, orientation='horizontal') |
|
144
|
|
|
cb4.set_label('in.', size='x-large') |
|
145
|
|
|
ax7.set_title('Precipitable Water', fontsize=16) |
|
146
|
|
|
|
|
147
|
|
|
fig.suptitle('{0:%d %B %Y %H:%MZ}'.format(time_vals), fontsize=24) |
|
148
|
|
|
|
|
149
|
|
|
# Display the plot |
|
150
|
|
|
plt.show() |
|
151
|
|
|
|