def solar_irradiance(latitude, longitude, dt=None, optical_depth=0.118): ''' Calculate the solar irradiance for the specified time and location. latitude : scalar The latitude of the location on the Earth in degrees longitude : scalar The longitude of the location on the Earth in degrees dt : datetime.datetime instance The date and time for which the irradiance should be calculated. This defaults to the current date. optical_depth : scalar An overall optical depth value to assume for the atmosphere so that the effects of atmospheric absorption can be accounted for. Defaults to 0.118. Returns : scalar or array The solar irradiance in W / m^2 for each value in *hour*. ''' if dt is None: dt = datetime.utcnow() if not iterable(dt): dt = [dt] lat_rad = latitude * degree lon_rad = longitude * degree cos_zenith = _get_cos_zenith(lat_rad, lon_rad, dt) s = solar_constant(dt[0]) # Mask out values for cos < 0 mask = np.array(cos_zenith < 0.) if mask.any(): cos_zenith = masked_array(cos_zenith, mask=mask) return s * cos_zenith * np.exp(-optical_depth / cos_zenith)
def grid_data(ob_data, grid_x, grid_y, ob_x, ob_y, weight_func, params): ''' Calculates a value at each grid point based on the observed data in ob_data. ob_data : 1D array The observation data grid_x : 2D array The x locations of the grid points grid_y : 2D array The y locations of the grid points ob_x : 1D array The x locations of the observation points ob_y : 1D array The y locations of the observation points weight_func : callable Any function that returns weights for the observations given their distance from a grid point. params : any object or tuple of objects Appropriate parameters to pass to *weight_func* after the distances. Returns : 2D array The values for the grid points ''' if not iterable(params): params = (params, ) # grid_point_dists calculates a 3D array containing the distance for each # grid point to every observation. weights = weight_func(grid_point_dists(grid_x, grid_y, ob_x, ob_y), *params) total_weights = weights.sum(axis=2) final = (weights * ob_data).sum(axis=2) / total_weights final = np.ma.masked_array(final, mask=(total_weights == 0.)) return final
def grid_data(ob_data, grid_x, grid_y, ob_x, ob_y, weight_func, params): ''' Calculates a value at each grid point based on the observed data in ob_data. ob_data : 1D array The observation data grid_x : 2D array The x locations of the grid points grid_y : 2D array The y locations of the grid points ob_x : 1D array The x locations of the observation points ob_y : 1D array The y locations of the observation points weight_func : callable Any function that returns weights for the observations given their distance from a grid point. params : any object or tuple of objects Appropriate parameters to pass to *weight_func* after the distances. Returns : 2D array The values for the grid points ''' if not iterable(params): params = (params,) # grid_point_dists calculates a 3D array containing the distance for each # grid point to every observation. weights = weight_func(grid_point_dists(grid_x, grid_y, ob_x, ob_y), *params) total_weights = weights.sum(axis=2) final = (weights * ob_data).sum(axis=2) / total_weights final = np.ma.masked_array(final, mask=(total_weights==0.)) return final
def meteogram(data, fig=None, num_panels=3, time_range=None, layout=None, styles=None, limits=None, units=None, tz=UTC): ''' Plots a meteogram (collection of time series) for a data set. This is broken down into a series of panels (defaults to 3), each of which can plot multiple variables, with sensible defaults, but can also be specified using *layout*. *data* : numpy record array A numpy record array containing time series for individual variables in each field. *fig* : :class:`matplotlib.figure.Figure` instance or None. A matplotlib Figure on which to draw. If None, a new figure will be created. *num_panels* : int The number of panels to use in the plot. *time_range* : sequence, datetime.timedetla, or *None* If a sequence, the starting and ending times for the x-axis. If a :class:`datetime.timedelta` object, it represents the time span to plot, which will end with the last data point. It defaults to the last 24 hours of data. *layout* : dictionary A dictionary that maps panel numbers to lists of variables. If a panel is not found in the dictionary, a default (up to panel 5) will be used. *None* can be included in the list to denote that :func:`pyplot.twinx` should be called, and the remaining variables plotted. *styles* : dictionary A dictionary that maps variable names to dictionary of matplotlib style arguments. Also, the keyword `fill` can be included, to indicate that a filled plot should be used. Any variable not specified will use a default (if available). *limits* : dictionary A dictionary that maps variable names to plot limits. These limits are given by tuples with at least two items, which specify the start and end limits. Either can be *None* which implies using the automatically determined value. Optional third and fourth values can be given in the tuple, which is a list of tick values and labels, respectively. *units* : dictionary A dictionary that maps variable names to unit strings for axis labels. *tz* : datetime.tzinfo instance A :class:`datetime.tzinfo instance specifying the timezone to use for plotting the x-axis. See the docs for :module:`datetime` and :module:`pytz` for how to construct and use these objects. The default is UTC. Returns : list A list of the axes objects that were created. ''' if fig is None: fig = plt.figure() # Get the time variable time = data['datetime'] # Process time_range. major_ticker = AutoDateLocator(tz=tz) minor_ticker = NullLocator() major_formatter = DateFormatter('%H', tz=tz) minor_formatter = NullFormatter() if time_range is None: time_range = timedelta(hours=24) major_ticker = HourLocator(byhour=np.arange(0, 25, 3), tz=tz) minor_ticker = HourLocator(tz=tz) if not iterable(time_range): end = time[-1] start = end - time_range time_range = (start, end) #List of variables in each panel. None denotes that at that point, twinx #should be called and the remaining variables plotted on the other axis default_layout = { 0:['temperature', 'dewpoint'], 1:['wind gusts', 'wind speed', None, 'wind direction'], 2:['pressure'], 3:['rainfall'], 4:['solar radiation']} if layout is not None: default_layout.update(layout) layout = default_layout #Default styles for each variable default_styles = { 'relative humidity':dict(color='#255425', linestyle='--'), 'dewpoint':dict(facecolor='#265425', edgecolor='None', fill=True), 'temperature':dict(facecolor='#C14F53', edgecolor='None', fill=True), 'pressure':dict(facecolor='#895125', edgecolor='None', fill=True), 'dewpoint':dict(facecolor='#265425', edgecolor='None', fill=True), 'wind speed':dict(facecolor='#1C2386', edgecolor='None', fill=True), 'wind gusts':dict(facecolor='#8388FC', edgecolor='None', fill=True), 'wind direction':dict(markeredgecolor='#A9A64B', marker='D', linestyle='', markerfacecolor='None', markeredgewidth=1, markersize=3), 'rainfall':dict(facecolor='#37CD37', edgecolor='None', fill=True), 'solar radiation':dict(facecolor='#FF8529', edgecolor='None', fill=True), 'windchill':dict(color='#8388FC', linewidth=1.5), 'heat index':dict(color='#671A5C')} if styles is not None: default_styles.update(styles) styles = default_styles #Default data limits default_limits = { 'wind direction':(0, 360, np.arange(0,400,45,), ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']), 'wind speed':(0, None), 'wind gusts':(0, None), 'rainfall':(0, None), 'solar radiation':(0, 1000, np.arange(0,1050,200)) } if limits is not None: default_limits.update(limits) limits = default_limits #Set data units def_units = default_units.copy() if units is not None: def_units.update(units) units = def_units #Get the station name site = data['site'][0] #Get strings for the start and end times start = time_range[0].astimezone(tz).strftime('%H%M %d %b %Y') end = time_range[1].astimezone(tz).strftime('%H%M %d %b %Y %Z') axes = [] for panel in range(num_panels): if panel > 0: ax = fig.add_subplot(num_panels, 1, panel+1, sharex=ax) else: ax = fig.add_subplot(num_panels, 1, panel+1) ax.set_title('%s\n%s to %s' % (site, start, end)) panel_twinned = False var_min = [] var_max = [] for varname in layout[panel]: if varname is None: _rescale_yaxis(ax, var_min + var_max) ax = ax.twinx() panel_twinned = True var_min = [] var_max = [] continue # Get the linestyle for this variable style = styles.get(varname, dict()) #Get the variable from the data and plot var = data[varname] #Set special limits if necessary lims = limits.get(varname, (None, None)) #Store the max and min for auto scaling if var.max() is not np.ma.masked: var_max.append(var.max()) var_min.append(var.min()) if style.pop('fill', False): #Plot the filled area. Need latest Matplotlib for date support #with fill_betweeen lower = -500 if lims[0] is None else lims[0] ax.fill_between(time, lower, var, where=~var.mask, **style) #TODO: Matplotlib SVN has support for turning off the #automatic scaling of the y-axis. Can we use that somehow #to simplify our code?? _rescale_yaxis(ax, var_min + var_max) else: ax.plot(time, var, **style) #If then length > 2, then we have ticks and (maybe) labels if len(lims) > 2: other = lims[2:] lims = lims[:2] #Separate out the ticks and perhaps labels if len(other) == 1: ax.set_yticks(other[0]) else: ticks,labels = other ax.set_yticks(ticks) ax.set_yticklabels(labels) ax.set_ylim(*lims) # Set the label to the title-cased nice-version from the # field info with units, if given. if varname in units and units[varname]: unit_str = ' (%s)' % units[varname] if '^' in unit_str and '$' not in unit_str: unit_str = '$' + unit_str + '$' else: unit_str = '' descr = get_title(data, varname) ax.set_ylabel(descr.title() + unit_str) ax.xaxis.set_major_locator(major_ticker) ax.xaxis.set_major_formatter(major_formatter) ax.xaxis.set_minor_locator(minor_ticker) ax.xaxis.set_minor_formatter(minor_formatter) if not panel_twinned: ax.yaxis.set_ticks_position('both') for tick in ax.yaxis.get_major_ticks(): tick.label2On = True axes.append(ax) # Set the xlabel as appropriate depending on the timezone ax.set_xlabel('Hour (%s)' % time[-1].astimezone(tz).tzname()) ax.set_xlim(*time_range) return axes
def meteogram(data, fig=None, num_panels=3, time_range=None, layout=None, styles=None, limits=None, units=None, tz=UTC): ''' Plots a meteogram (collection of time series) for a data set. This is broken down into a series of panels (defaults to 3), each of which can plot multiple variables, with sensible defaults, but can also be specified using *layout*. *data* : numpy record array A numpy record array containing time series for individual variables in each field. *fig* : :class:`matplotlib.figure.Figure` instance or None. A matplotlib Figure on which to draw. If None, a new figure will be created. *num_panels* : int The number of panels to use in the plot. *time_range* : sequence, datetime.timedetla, or *None* If a sequence, the starting and ending times for the x-axis. If a :class:`datetime.timedelta` object, it represents the time span to plot, which will end with the last data point. It defaults to the last 24 hours of data. *layout* : dictionary A dictionary that maps panel numbers to lists of variables. If a panel is not found in the dictionary, a default (up to panel 5) will be used. *None* can be included in the list to denote that :func:`pyplot.twinx` should be called, and the remaining variables plotted. *styles* : dictionary A dictionary that maps variable names to dictionary of matplotlib style arguments. Also, the keyword `fill` can be included, to indicate that a filled plot should be used. Any variable not specified will use a default (if available). *limits* : dictionary A dictionary that maps variable names to plot limits. These limits are given by tuples with at least two items, which specify the start and end limits. Either can be *None* which implies using the automatically determined value. Optional third and fourth values can be given in the tuple, which is a list of tick values and labels, respectively. *units* : dictionary A dictionary that maps variable names to unit strings for axis labels. *tz* : datetime.tzinfo instance A :class:`datetime.tzinfo instance specifying the timezone to use for plotting the x-axis. See the docs for :module:`datetime` and :module:`pytz` for how to construct and use these objects. The default is UTC. Returns : list A list of the axes objects that were created. ''' if fig is None: fig = plt.figure() # Get the time variable time = data['datetime'] # Process time_range. major_ticker = AutoDateLocator(tz=tz) minor_ticker = NullLocator() major_formatter = DateFormatter('%H', tz=tz) minor_formatter = NullFormatter() if time_range is None: time_range = timedelta(hours=24) major_ticker = HourLocator(byhour=np.arange(0, 25, 3), tz=tz) minor_ticker = HourLocator(tz=tz) if not iterable(time_range): end = time[-1] start = end - time_range time_range = (start, end) #List of variables in each panel. None denotes that at that point, twinx #should be called and the remaining variables plotted on the other axis default_layout = { 0: ['temperature', 'dewpoint'], 1: ['wind gusts', 'wind speed', None, 'wind direction'], 2: ['pressure'], 3: ['rainfall'], 4: ['solar radiation'] } if layout is not None: default_layout.update(layout) layout = default_layout #Default styles for each variable default_styles = { 'relative humidity': dict(color='#255425', linestyle='--'), 'dewpoint': dict(facecolor='#265425', edgecolor='None', fill=True), 'temperature': dict(facecolor='#C14F53', edgecolor='None', fill=True), 'pressure': dict(facecolor='#895125', edgecolor='None', fill=True), 'dewpoint': dict(facecolor='#265425', edgecolor='None', fill=True), 'wind speed': dict(facecolor='#1C2386', edgecolor='None', fill=True), 'wind gusts': dict(facecolor='#8388FC', edgecolor='None', fill=True), 'wind direction': dict(markeredgecolor='#A9A64B', marker='D', linestyle='', markerfacecolor='None', markeredgewidth=1, markersize=3), 'rainfall': dict(facecolor='#37CD37', edgecolor='None', fill=True), 'solar radiation': dict(facecolor='#FF8529', edgecolor='None', fill=True), 'windchill': dict(color='#8388FC', linewidth=1.5), 'heat index': dict(color='#671A5C') } if styles is not None: default_styles.update(styles) styles = default_styles #Default data limits default_limits = { 'wind direction': (0, 360, np.arange( 0, 400, 45, ), ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']), 'wind speed': (0, None), 'wind gusts': (0, None), 'rainfall': (0, None), 'solar radiation': (0, 1000, np.arange(0, 1050, 200)) } if limits is not None: default_limits.update(limits) limits = default_limits #Set data units def_units = default_units.copy() if units is not None: def_units.update(units) units = def_units #Get the station name site = data['site'][0] #Get strings for the start and end times start = time_range[0].astimezone(tz).strftime('%H%M %d %b %Y') end = time_range[1].astimezone(tz).strftime('%H%M %d %b %Y %Z') axes = [] for panel in range(num_panels): if panel > 0: ax = fig.add_subplot(num_panels, 1, panel + 1, sharex=ax) else: ax = fig.add_subplot(num_panels, 1, panel + 1) ax.set_title('%s\n%s to %s' % (site, start, end)) panel_twinned = False var_min = [] var_max = [] for varname in layout[panel]: if varname is None: _rescale_yaxis(ax, var_min + var_max) ax = ax.twinx() panel_twinned = True var_min = [] var_max = [] continue # Get the linestyle for this variable style = styles.get(varname, dict()) #Get the variable from the data and plot var = data[varname] #Set special limits if necessary lims = limits.get(varname, (None, None)) #Store the max and min for auto scaling if var.max() is not np.ma.masked: var_max.append(var.max()) var_min.append(var.min()) if style.pop('fill', False): #Plot the filled area. Need latest Matplotlib for date support #with fill_betweeen lower = -500 if lims[0] is None else lims[0] ax.fill_between(time, lower, var, where=~var.mask, **style) #TODO: Matplotlib SVN has support for turning off the #automatic scaling of the y-axis. Can we use that somehow #to simplify our code?? _rescale_yaxis(ax, var_min + var_max) else: ax.plot(time, var, **style) #If then length > 2, then we have ticks and (maybe) labels if len(lims) > 2: other = lims[2:] lims = lims[:2] #Separate out the ticks and perhaps labels if len(other) == 1: ax.set_yticks(other[0]) else: ticks, labels = other ax.set_yticks(ticks) ax.set_yticklabels(labels) ax.set_ylim(*lims) # Set the label to the title-cased nice-version from the # field info with units, if given. if varname in units and units[varname]: unit_str = ' (%s)' % units[varname] if '^' in unit_str and '$' not in unit_str: unit_str = '$' + unit_str + '$' else: unit_str = '' descr = get_title(data, varname) ax.set_ylabel(descr.title() + unit_str) ax.xaxis.set_major_locator(major_ticker) ax.xaxis.set_major_formatter(major_formatter) ax.xaxis.set_minor_locator(minor_ticker) ax.xaxis.set_minor_formatter(minor_formatter) if not panel_twinned: ax.yaxis.set_ticks_position('both') for tick in ax.yaxis.get_major_ticks(): tick.label2On = True axes.append(ax) # Set the xlabel as appropriate depending on the timezone ax.set_xlabel('Hour (%s)' % time[-1].astimezone(tz).tzname()) ax.set_xlim(*time_range) return axes