GMTB Workflow Documentation
maps.py
Go to the documentation of this file.
1 ## \file maps.py
2 #\defgroup maps maps
3 #\ingroup graphics
4 import os,sys,shutil,math
5 import matplotlib as mpl
6 mpl.use('Agg')
7 from mpl_toolkits.basemap import Basemap, cm
8 import numpy as np
9 import matplotlib.pyplot as plt
10 
11 ##\ingroup maps
12 def polar_stere(bounds, **kwargs):
13 #def polar_stere(lon_w, lon_e, lat_s, lat_n, **kwargs):
14  '''Returns a Basemap object (NPS/SPS) focused in a region.
15 
16  lon_w, lon_e, lat_s, lat_n -- Graphic limits in geographical coordinates.
17  W and S directions are negative.
18  **kwargs -- Aditional arguments for Basemap object.
19 
20  '''
21  lon_w,lon_e,lat_s,lat_n = bounds
22  lon_0 = lon_w + (lon_e - lon_w) / 2.
23  ref = lat_s if abs(lat_s) > abs(lat_n) else lat_n
24  lat_0 = math.copysign(90., ref)
25  proj = 'npstere' if lat_0 > 0 else 'spstere'
26  prj = Basemap(projection=proj, lon_0=lon_0, lat_0=lat_0,
27  boundinglat=0, resolution='c')
28  #prj = pyproj.Proj(proj='stere', lon_0=lon_0, lat_0=lat_0)
29  lons = [lon_w, lon_e, lon_w, lon_e, lon_0, lon_0]
30  lats = [lat_s, lat_s, lat_n, lat_n, lat_s, lat_n]
31  x, y = prj(lons, lats)
32  ll_lon, ll_lat = prj(min(x), min(y), inverse=True)
33  ur_lon, ur_lat = prj(max(x), max(y), inverse=True)
34  return Basemap(projection='stere', lat_0=lat_0, lon_0=lon_0,
35  llcrnrlon=ll_lon, llcrnrlat=ll_lat,
36  urcrnrlon=ur_lon, urcrnrlat=ur_lat, **kwargs)
37 
38 ##\ingroup maps
40  ''' Class that generates all of the generic parts of a map figure.'''
41  def __init__(self):
42  ''' Load the parameters dictionary that defines plotting parameters for various variables.
43  '''
44  self.params = self.set_fig_params()
45 
46  def set_fig_params(self):
47  '''Definition of parameters for plotting standard maps.
48  The top level variable name must match the "shortName" used in the grib file.
49  Each variable has a
50  subdictionary for various height levels to define either int/min/max values,
51  or contour values directly. Other options that the scripts use:
52  cbar: user-specified color tables
53  scale: a scale factor (should start with an operator (*,/,+,-)
54  unit: a string for unit if the one in the grib file is inappropriate
55  varname: option for using a variable name different than that in the grib file
56  '''
57  params = {
58  'gh': {'200': {'int': 100, 'min': 100, 'max': 10000},
59  '500': {'int': 100, 'min': 4600, 'max': 6200},
60  '700': {'int': 50, 'min': 100, 'max': 2500},
61  '850': {'int': 50, 'min': 750, 'max': 1750},
62  'cbar': 'rainbow',
63  'unit': 'gpm'},
64 # 'unit': 'dam',
65 # 'scale': '/10'},
66 
67  't': {'200': {'int': 5, 'min': -85, 'max': -30},
68  '500': {'int': 5, 'min': -50, 'max': 15},
69  '700': {'int': 5, 'min': -40, 'max': 30},
70  '850': {'int': 5, 'min': -40, 'max': 40},
71  'any': {'int': 5, 'min': -75, 'max': 75},
72  'unit': 'C',
73  'scale': '-273.15'},
74  'prmsl': {'0': {'int': 10, 'min': 930, 'max': 1070},
75  'unit': 'hPa',
76  'cbar': 'rainbow',
77  'scale': '/100.',
78  'varname': 'Mean sea level pressure'},
79 
80  '2t': {'2': {'int': 5, 'min': -50, 'max': 50},
81  'unit': 'C',
82  'scale': '-273.15',
83  'varname': '2m temperature'},
84 
85  '2d': {'2': {'int': 5, 'min': -50, 'max': 50},
86  'unit': 'C',
87  'scale': '-273.15',
88  'varname': '2m dewpoint temperature',
89  'cbar': 'terrain_r'},
90 
91  'absv':{'500': {'int': 5e-5, 'min': -7.e-4, 'max': 7.e-4},
92 #zhang 'scale':'-7.292e-5*2*np.sin(self.lat*np.pi/160.)',
93 #zhang 'varname': 'Relative Vorticity',
94 # 'scale':'-((1.459e-4)*np.sin(self.lat*(np.pi)/180.))',
95 # 'varname': 'Relative Vorticity',
96  'cbar': plt.cm.RdBu_r},
97 
98  'w':{'700': {'int': 0.6, 'min': -6, 'max': 6},
99  'cbar': plt.cm.RdBu_r},
100 
101  'wind':{'200': {'int': 10, 'min': 0, 'max': 90},
102  '500': {'int': 10, 'min': 0, 'max': 90},
103  '700': {'int': 10, 'min': 0, 'max': 90},
104  '850': {'int': 10, 'min': 0, 'max': 90},
105  'any': {'int': 5, 'min': 5, 'max': 250}},
106  '10u':{'10': {'int': 3, 'min': -30, 'max': 30},
107  'cbar': plt.cm.RdBu_r},
108  '10v':{'10': {'int': 3, 'min': -30, 'max': 30},
109  'cbar': plt.cm.RdBu_r},
110 
111  'r' :{'850': {'int': 5, 'min': 0, 'max': 105},
112  'cbar': plt.cm.gist_earth_r},
113  'ncpcp':{'0': {'contours': [0,1,2.5,5,7.5,10,15,20,30,40,50,70,100,150,200,250,300,400,500,600]},
114  'cbar':cm.s3pcpn},
115  'acpcp':{'0': {'contours': [0,0.001,0.01,0.1,0.5,1,2.5,5,7.5,10,12.5,15,17.5,20,22.5,25,27.5,30,35,40,45,50,55,60,65,70,80,90,100,150,200]},
116  'cbar':cm.s3pcpn},
117  'tp':{'0': {'contours': [0,0.1,0.5,1,2.5,5,7.5,10,15,20,30,40,50,70,100,150,200,250,300,400,500,600]},
118  'cbar':cm.s3pcpn},
119  'q': {'any': {'int': 2, 'min': 0 , 'max': 24 },
120  'scale': '*1000',
121  'unit': 'g/kg',
122  'cbar':'terrain_r'},
123  'btmp': {'0': {'int': 5, 'min': -100 , 'max': 10 },
124  'scale': '-273.15',
125  'unit': 'C',
126  'cbar':'gist_ncar_r'},
127  'pwat':{'0': {'int': 5 ,'min':5 ,'max': 100},
128 # 'scale': '*1000',
129 # 'unit': 'g m**-2',
130  'cbar':'rainbow'},
131  'prate': {'0': {'int': 0.2, 'min': 0.2, 'max': 3.6},
132  'scale': '*1000',
133  'unit': 'g m**-2 s**-1',
134  'cbar':'jet'},
135  'cprat': {'0': {'int': 0.2, 'min': 0.2, 'max': 3.6},
136  'scale': '*1000',
137  'unit': 'g m**-2 s**-1',
138  'cbar':'jet'},
139 
140  'ULWRF': {'0': {'default': True},
141  'varname': 'ULWRF'}
142  }
143  return params
144 
145 
146  def draw_map(self):
147  ''' Make figure'''
148  lon=self.lon
149  lat=self.lat
150  fp=self.params
151  m=self.m
152  print "here"
153  plt.figure(figsize=(15,12))
154  print "plotted"
155 
156  # Draw political boundaries
157  m.drawcoastlines()
158  m.drawstates()
159  m.drawcountries()
160  m.drawmapboundary()
161 # m.drawlsmask(ocean_color='0.8',land_color='white')
162 
163 
164 
165  # Draw lat/lon grid
166  parallels = np.arange(-90.,90,10.)
167  m.drawparallels(parallels,labels=[1,0,0,0],fontsize=10)
168  meridians = np.arange(0.,360.,20.)
169  m.drawmeridians(meridians,labels=[0,0,0,1],fontsize=10)
170 
171  def save_figure(self,outdir):
172  '''save figures'''
173  fp=self.params
174  field = self.field
175  fp_field=fp.get(self.myvar, None)
176 
177  var = self.myvar
178  lev = str(field['level'])
179  atime = field.analDate.strftime('%Y%m%d%H')
180  grid = self.area_flag
181 
182  if self.fhr is None:
183 #zhang fcsthr=field['forecastTime']
184  fcsthr=field['endStep']
185  else:
186  fcsthr=self.fhr
187 
188  vert_unit = self.coord_str(str(field['typeOfLevel']),title=False)
189 
190  # Output file name definition
191  filename=''.join([var,lev,vert_unit,'_',grid,'_',atime,'_f','{:02d}'.format(fcsthr),'.png'])
192 
193  # Check to see if output location exists, if not, make it.
194  if not os.path.exists(outdir):
195  os.makedirs(outdir)
196 
197  plt.savefig('/'.join([outdir, filename]))
198  plt.close()
199 
200 
201 
202  def fill_field(self):
203  '''Color-fill the field of the figure being plotted'''
204  field=self.field
205  m=self.m
206  fp=self.params
207  x, y = m(self.lon,self.lat)
208 
209  # Choose either a specific level definition from the dictionary for contour info, or
210  # a generic set of parameters used for "any" level.
211  check_lev = fp[self.myvar].get(str(self.level),None)
212  if check_lev is not None:
213  var_lev = str(self.level)
214  else:
215  var_lev = 'any'
216 
217  # Get contour information from the dictionary. Should supply either min/max/int values, or
218  # a specific set of contour levels.
219  min = fp[self.myvar][var_lev].get('min',None)
220  max = fp[self.myvar][var_lev].get('max',None)
221  int = fp[self.myvar][var_lev].get('int',None)
222 
223  # If min is not defined, try to get contours
224  if min is None: levs = fp[self.myvar][var_lev].get('contours',None)
225 
226 
227  # Use "jet" colormap by default
228  colormap = fp[self.myvar].get('cbar',plt.cm.jet)
229 
230  data = None
231 # if self.myvar == 'wind':
232 # u=self.winds[0].values
233 # v=self.winds[1].values
234 #CH data=np.sqrt(u.dot(v))
235 # data=np.sqrt(u**2+v**2)
236 
237 
238  # Determine how to apply the scaline factor based on the first item in the string
239  scale = fp[self.myvar].get('scale',None)
240  data = self.scale_field(scale)
241 
242  if min is not None:
243  clevs = np.arange(min,max,int)
244  elif levs is not None:
245  clevs = levs
246  else:
247  clevs = None
248 
249  # Plot on default levels if none are specified in fp.
250  if clevs == None:
251  cs = m.contourf(x,y,data,cmap=colormap)
252  if self.line:
253  csl= m.contour(x,y,data,colors='k')
254  plt.clabel(csl, fontsize=10, inline=1, fmt= '%4.0f')
255  else:
256  cs = m.contourf(x,y,data,clevs,cmap=colormap)
257  if self.line:
258  csl= m.contour(x,y,data,clevs,colors='k')
259  plt.clabel(csl, fontsize=10, inline=1, fmt= '%4.0f')
260 # cbar=plt.colorbar(cs,orientation='vertical',shrink=0.8)
261  cbar = plt.colorbar(cs,orientation='horizontal',extend='both')
262 
263  def scale_field(self,scale=None):
264  ''' scale field values'''
265  field=self.field
266  if scale is not None:
267  if scale[0] == '/':
268  data=np.true_divide(field.values,float(scale[1:]))
269  elif scale[0] == '-':
270  data=np.subtract(field.values,eval(scale[1:]))
271  elif scale[0] == '*':
272  data=np.multiply(field.values,float(scale[1:]))
273  else:
274  data=field.values
275  return data
276 
277  def contour_field(self,var,field):
278  '''Draw field contours. Does not have to be the main field being processed. Mostly plotting height.'''
279  m = self.m
280  fp = self.params
281 
282  x, y = m(self.lon,self.lat)
283 
284  check_lev = fp[self.myvar].get(str(self.level),None)
285  if check_lev is not None:
286  var_lev = str(self.level)
287  else:
288  var_lev = 'any'
289 
290  # Get contour information from the dictionary. Should supply either min/max/int values, or
291  # a specific set of contour levels.
292  min = fp[self.myvar][var_lev].get('min',None)
293  max = fp[self.myvar][var_lev].get('max',None)
294  int = fp[self.myvar][var_lev].get('int',None)
295 
296  # If min is not defined, try to get contours
297  if min is None: levs = fp[self.myvar][var_lev].get('contours',None)
298 
299  scale = fp[var].get('scale',None)
300  data = self.scale_field(scale)
301 
302  if min is not None:
303  clevs = np.arange(min,max,int)
304  elif levs is not None:
305  clevs = levs
306  else:
307  clevs = None
308  # Plot on default levels if none are specified in fp.
309  if clevs == None:
310  cc = m.contour(x,y,data,colors='k')
311  else:
312  cc = m.contour(x,y,data,clevs,colors='k')
313 
314  plt.clabel(cc, fontsize=10, inline=1, fmt= '%4.0f')
315 
316 
317  def wind_field(self):
318  '''Plot the wind field in barbs. Masks are applied here to thin
319  the quantity of barbs plotted for clarity'''
320 
321  m=self.m
322  fp=self.params
323  u=self.winds[0].values
324  v=self.winds[1].values
325 
326  # Mask for winds. Different grids require slightly different masking
327  maskarray = np.ones(u.shape)
328  if self.area_flag == 'G3':
329  maskarray[1::7,1::14] = 0
330  elif self.area_flag == 'G104':
331  maskarray[1::3,1::6] = 0
332  else:
333  maskarray[1::20,1::40] = 0
334 
335 
336  mu = np.ma.masked_array(u, mask=maskarray)
337  mv = np.ma.masked_array(v, mask=maskarray)
338  x, y = m(self.lon,self.lat)
339  barb_control = dict(height=0.6, width=0.3, emptybarb=0.07)
340  self.barbs = m.barbs(x, y, mu, mv, barbcolor='k',pivot='middle',sizes=barb_control, linewidth=0.4, length=5)
341 
342  def coord_str(self,coord,title=True):
343  '''Determine how to label the vertical coordinate based on the contents of the grib file, or other
344  information.'''
345  field=self.field
346  if coord == 'isobaricInhPa':
347  vert_unit = str(field['pressureUnits'])
348  elif coord == 'hybrid':
349  if title:
350  vert_unit = "$\sigma$"
351  else:
352  vert_unit = "sigma"
353  elif coord == 'surface':
354  vert_unit = 'm'
355  elif coord == 'nominalTop':
356  vert_unit = 'Top'
357  else:
358  # vert_unit = str(field.get('unitsOfFirstFixedSurface',False))
359  vert_unit ='m'
360  return vert_unit
361 
362  def plot_title(self):
363  ''' configure plot title'''
364  field=self.field
365  fp=self.params
366  date = str(field['dataDate'])
367  myvar = fp[self.myvar].get('varname',str(field['name']))
368  atime = field.analDate.strftime('Analysis: %Y%m%d %H UTC')
369  if field.validDate:
370  vtime = field.validDate.strftime('Valid: %Y%m%d %H UTC')
371  else:
372  vtime = ''
373 
374  level = str(self.level)
375  coord = str(field['typeOfLevel'])
376  vert_unit = self.coord_str(coord)
377 
378  var_unit = fp[self.myvar].get('unit',field['units'])
379 
380  maptype = 'shaded'
381  if self.fhr is not None:
382  fcsthr= self.fhr
383  else:
384  try:
385 #zhang fcsthr=str(field['forecastTime']) #forecastTime=startStep
386  fcsthr=str(field['stepRange'])
387  except:
388  fcsthr=''
389 
390  if self.myvar == 'wind':
391  title_str=str('wind magnitude (%s, %s)'%(var_unit,maptype))
392  else:
393  title_str=str('%s (%s, %s)'%(myvar,var_unit,maptype))
394  #cycle=str(field['analysisTime'])
395  plt.title('%s \nFcst Hr: %s' % (atime, fcsthr) , loc='left')
396  plt.title('%s %s' % (level,vert_unit), position=(0.5, 1.04), fontsize=18)
397  if self.height:
398  plt.title('%s \n Height (gpm), contoured' % (title_str)
399  , loc = 'right')
400  else:
401  plt.title('%s' % title_str, loc = 'right')
402  plt.xlabel('%s' % (vtime), fontsize=18, labelpad=40)
403 
404  def display_map(self):
405  plt.show()
406 
407  def run(self):
408  self.draw_map()
409  self.fill_field()
410  if self.height is not None: self.contour_field('gh',self.height)
411  if self.winds is not None: self.wind_field()
412  self.plot_title()
413 
414 
416  ''' Class that defines the map projection for the data based on verification grids provided.'''
417  def __init__(self,field,date,myvar,level,area_flag='glob',ncep_grid=None, line=False,
418  resolution=None, winds=None, plot_height=None, def_maps=None, fhr=None):
419  super(make_basemap,self).__init__()
420 
421  self.field=field
422  self.lat, self.lon = field.latlons()
423  self.myvar=myvar
424  self.level=level
425  self.def_maps=def_maps
426  self.winds = winds
427  self.height = plot_height
428  self.line = line
429  self.area_flag = area_flag
430  self.fhr = fhr
431 
432  def def_bm(self):
433  ''' Defines several common maps used for verification in a Python dict'''
434  basemaps = {
435  'glob': {'resolution': 'c','projection':'cyl','lat_ts':10,'llcrnrlon':0,'urcrnrlon':357.5,'llcrnrlat':-90,'urcrnrlat':90},
436  'G3': {'resolution': 'c','projection':'cyl','lat_ts':10,'llcrnrlon':0,'urcrnrlon':357.5,'llcrnrlat':-90,'urcrnrlat':90},
437  'nh_mill': {'resolution': 'c','projection':'mill','lat_ts':10,'llcrnrlon':0,'urcrnrlon':357.5,'llcrnrlat': 0,'urcrnrlat':90},
438  'sh_mill': {'resolution': 'c','projection':'mill','lat_ts':10,'llcrnrlon':0,'urcrnrlon':357.5,'llcrnrlat':-90,'urcrnrlat': 0},
439  'trop_mill': {'resolution': 'c','projection':'mill','lat_ts':10,'llcrnrlon':0,'urcrnrlon':357.5,'llcrnrlat':-23,'urcrnrlat':23},
440  'nps': {'resolution': 'c','projection':'npstere','boundinglat':-0.268,'lon_0':-105},
441  'sps': {'resolution': 'c','projection':'spstere','boundinglat': -60,'lon_0': 105},
442  'G218': {'resolution': 'c','projection':'lcc', 'lat_1':25, 'lat_2':25, 'lon_0':265,'rsphere':6371200,'llcrnrlon':226.514, 'llcrnrlat':12.190,'urcrnrlon':-49.420 , 'urcrnrlat':57.328},
443  'G104': [-147, -64, 10.8, 89.9],
444 
445  }
446  return basemaps
447 
448  def set_bm(self):
449  ''' Sets the Basemap object for creating the figure.'''
450  base_map=self.def_bm()
451  map_def=base_map.get(self.area_flag,None)
452  print map_def
453  if map_def is None: map_def=base_map.get('glob')
454 
455  if self.area_flag != 'G104':
456  self.m=Basemap(**map_def)
457  else:
458  self.m=polar_stere(map_def,resolution='c')
459 
460 
def set_fig_params(self)
Definition: maps.py:46
def draw_map(self)
Definition: maps.py:146
def __init__(self)
Definition: maps.py:41
def save_figure(self, outdir)
Definition: maps.py:171
def run(self)
Definition: maps.py:407
def fill_field(self)
Definition: maps.py:202
def coord_str
Definition: maps.py:342
def plot_title(self)
Definition: maps.py:362
def scale_field
Definition: maps.py:263
def wind_field(self)
Definition: maps.py:317
def set_bm(self)
Definition: maps.py:448
def contour_field(self, var, field)
Definition: maps.py:277
def polar_stere(bounds, kwargs)
Definition: maps.py:12
def def_bm(self)
Definition: maps.py:432
def display_map(self)
Definition: maps.py:404