示例#1
0
    def testDirection(self):

        # Single scalars
        self.assertAlmostEqual(util.u_flow(4.24, 45), 3, 2)
        self.assertAlmostEqual(util.u_met(4.24, 45), -3, 2)
        self.assertAlmostEqual(util.v_flow(4.24, 45), 3, 2)
        self.assertAlmostEqual(util.v_met(4.24, 45), -3, 2)

        # Numpy arrays
        numpy.testing.assert_almost_equal(util.speed([2, 3, 4], [5, 6, 7]),
                                          numpy.array([5.385, 6.708, 8.062]),
                                          3)
        numpy.testing.assert_almost_equal(util.u_flow([3, 3, 2], [0, 90, 60]),
                                          [0, 3, numpy.sqrt(3)],
                                          decimal=2)
        numpy.testing.assert_almost_equal(util.u_met([3, 3, 2], [0, 90, 60]),
                                          [0, -3, -numpy.sqrt(3)],
                                          decimal=2)
        numpy.testing.assert_almost_equal(util.v_flow([3, 3, 2], [0, 90, 60]),
                                          [3, 0, 1],
                                          decimal=2)
        numpy.testing.assert_almost_equal(util.v_met([3, 3, 2], [0, 90, 60]),
                                          [-3, 0, -1],
                                          decimal=2)

        # Test broadcasting one direction to multiple speeds
        numpy.testing.assert_almost_equal(
            util.u_flow([2, 2, 2], [60]),
            [numpy.sqrt(3), numpy.sqrt(3),
             numpy.sqrt(3)],
            decimal=2)
        numpy.testing.assert_almost_equal(util.v_flow([2, 2, 2], [60]),
                                          [1, 1, 1],
                                          decimal=2)
示例#2
0
def uv_column_interp(u_mass_column, v_mass_column, heights_above_ground, heights_to_interp, z_o=None):
    """Calculate the wind speeds at a given height (in meters) using the two adjacent pressure levels u and v
    wind components. Uses a log-law approximation from the ground to 100 m for points that are lower than the
    lowest eta level in the model. Direction interpolation is not performed for levels where the log-law
    approximation is used.

    :param u_mass_column: WRF U wind speed already interpolated to the mass coordinate system (native is flux coordinate)
    :param v_mass_column: WRF V wind speed already interpolated to the mass coordinate system (native is flux coordinate)
    :param heights_above_ground:
    :param heights_to_interp:

    :returns 2, 1D arrays u, v of the height interpolated wind speed and direction in the column.

    """

    # Make sure array lengths match
    assert(u_mass_column.shape == v_mass_column.shape)
    assert(u_mass_column.shape == heights_above_ground.shape)

    # Make sure we've only been given a column
    assert(u_mass_column.ndim == 1)
    assert(v_mass_column.ndim == 1)
    assert(heights_above_ground.ndim == 1)

    # Convert heights_to_interp to an numpy array so we can mask it
    heights_to_interp = numpy.array(heights_to_interp)

    # Find the heights that need log-law interpolation i.e. below 100 m and lower than the lowest eta-half level
    mask_heights_to_interp_log_law = heights_to_interp < numpy.min(heights_above_ground)

    # Linear interp where we have model data
    if numpy.size(heights_to_interp[~mask_heights_to_interp_log_law]) > 0:
        u_mass_coord_interp_linear = numpy.interp(heights_to_interp[~mask_heights_to_interp_log_law],
                                                  heights_above_ground, u_mass_column)
        v_mass_coord_interp_linear = numpy.interp(heights_to_interp[~mask_heights_to_interp_log_law],
                                                  heights_above_ground, v_mass_column)
    else:
        logger.info('No eta-level heights were in the range of LINEAR interpolation.')
        u_mass_coord_interp_linear = []
        v_mass_coord_interp_linear = []

    # Log-law interp for speed using heights 100 m and below, if WRF ZNT was NOT provided
    speed = util.speed(u_mass_column, v_mass_column)
    if numpy.size(heights_to_interp[mask_heights_to_interp_log_law]) > 0 and z_o is None:

        # Make sure there is more than one height to linearly interpolate with below 100 m
        if heights_above_ground[1] < 100:
            speed_interp_log_law = log_law_interp(speed, heights_above_ground,
                                                  heights_to_interp[mask_heights_to_interp_log_law], 100)

        # Otherwise, issue a warning and include the second height above ground
        # This situation can sometimes occur at the edges of nested domains when the PSFC value
        # is lower than the lowest level of (P + PB). Speculate this is a bug in WRF where the PSFC is being taken
        # from the lower resolution domain rather than being calculated with the new topo height in the higher
        # resolution domain.
        else:
            speed_interp_log_law = log_law_interp(speed, heights_above_ground,
                                                  heights_to_interp[mask_heights_to_interp_log_law], heights_above_ground[1])
            logger.info('Had to use height above 100 m for log-linear log-law interp')
            logger.info('Heights used for log-linear log-law interp: {} m and {} m'
                        .format(heights_above_ground[0], heights_above_ground[1]))

    # Use the log-law with the WRF ZNT (surface roughness) to diagnose U,V
    elif numpy.size(heights_to_interp[mask_heights_to_interp_log_law]) > 0 and z_o is not None:
        speed_interp_log_law = speed[0] * \
                               numpy.log(heights_to_interp[mask_heights_to_interp_log_law] / z_o) / \
                               numpy.log(numpy.min(heights_above_ground) / z_o)
    else:
        logger.info('No eta-level heights were in the range of LOG-LAW interpolation.')
        speed_interp_log_law = []

    # For log-law interpolation, use the same wind direction as the bottom level where we have WRF data for the
    # direction. Assumes these heights are sorted from bottom to top, which is the WRF default.
    u_bottom = u_mass_column[0]
    v_bottom = v_mass_column[0]
    dir_interp_log_law = util.calc_dir_deg(u_bottom, v_bottom)
    u_mass_coord_interp_log_law = util.u_flow(speed_interp_log_law, dir_interp_log_law)
    v_mass_coord_interp_log_law = util.v_flow(speed_interp_log_law, dir_interp_log_law)

    return numpy.hstack((u_mass_coord_interp_log_law, u_mass_coord_interp_linear)), \
           numpy.hstack((v_mass_coord_interp_log_law, v_mass_coord_interp_linear))
示例#3
0
 def testSpeed(self):
     self.assertAlmostEqual(util.speed(8.3, 6.3), 10.42, 2)
     numpy.testing.assert_almost_equal(util.speed([2, 3, 4], [5, 6, 7]),
                                       numpy.array([5.385, 6.708, 8.062]),
                                       3)
示例#4
0
文件: insert.py 项目: wxmiked/windb2
    def insert_variable(self,
                        ncfile,
                        var_name,
                        domain_key=None,
                        replace_data=False,
                        sql_where="true",
                        file_type='windb2',
                        mask=None,
                        zero_seconds=False):
        """Inserts a netCDF file with WinDB2 or WRF output into a WinDB2 database.
       *
       * windb2Conn - Connection to a WinDB2 database.
       * ncfile - Either an open file or a string name of a file to open.
       * var_name - Name of WinDB2 supported variable or a WRF 3D variable (currently WIND, THETA, RHO).
       * domain_key - Existing domain key in the database. If left blank, a new domain will be created.
       * replace_data - Deletes data for the same time in the database if True. Useful for freshening data.
       * file_type - Type of netCDF file to insert: {'windb2' (default), or 'wrf'}
       * mask - String name of a mask in the WinDB2 database. Only relevant when creating a new domain (the mask is
       *        applied automatically thereafter).
       *
       * returns timesInsertedList, domain_key - A list of times inserted in ISO time format, and the
         domain_key where the data was inserted.
         :param file_type:
       """

        # Make sure this the file_type of file is support
        if file_type != 'windb2' and file_type != 'wrf':
            raise TypeError('Unsupported file file_type: {}'.format(file_type))

        # Open the WinDB netCDF file
        logger.debug(
            'netCDF file file_type passed to wrf.insertNcFile={}'.format(
                type(ncfile)))
        if type(ncfile) != Dataset:
            ncfile = Dataset(ncfile, 'r')

        # Get the grid dimensions and coordinates
        if file_type == 'windb2':
            nlong = len(ncfile.dimensions['x'])
            nlat = len(ncfile.dimensions['y'])
            x_coord_array = ncfile.groups['WRF']['XLONG'][:]
            y_coord_array = ncfile.groups['WRF']['XLAT'][:]
            if self.config['vars'][var_name]['dims'] == 3:
                height_array = ncfile.variables['height'][:]
            elif self.config['vars'][var_name]['dims'] == 2:
                height_array = [self.config['vars'][var_name]['insert'][0]]
            else:
                raise Exception('Number of dimensions must either be 2 or 3')
            init_t = datetime.strptime(
                ncfile.groups['WRF'].SIMULATION_START_DATE,
                '%Y-%m-%d_%H:%M:%S').replace(tzinfo=pytz.utc)
        elif file_type == 'wrf':
            nlong = len(ncfile.dimensions['west_east'])
            nlat = len(ncfile.dimensions['south_north'])
            x_coord_array = ncfile['XLONG'][:]
            y_coord_array = ncfile['XLAT'][:]
            height_array = [self.config[var_name]['insert'][0]]
            init_t = datetime.strptime(
                ncfile.SIMULATION_START_DATE,
                '%Y-%m-%d_%H:%M:%S').replace(tzinfo=pytz.utc)

        # Read in the vars to insert
        wrf_copied_var = False
        if file_type == 'windb2' and var_name.lower() == 'WIND'.lower():
            u = ncfile.variables['eastward_wind'][:]
            v = ncfile.variables['northward_wind'][:]
        elif file_type == 'windb2' and var_name.lower() == 'DPT'.lower():
            ncVariable = ncfile.variables['dew_point_temperature'][:]
        # Otherwise try find the WinDB2 interp or WRF var
        else:
            try:
                ncVariable = ncfile.variables[var_name][:]
            except KeyError as e:
                wrf_copied_var = True
                ncVariable = ncfile.groups['WRF'][var_name][:]

        # Create a new and/or domain if necessary
        if domain_key is None:
            if file_type == 'windb2':
                domain_key = str(
                    self.create_new_domain(ncfile.groups['WRF'].TITLE, "WRF",
                                           ncfile.groups['WRF'].DX, 'm', mask))
            elif file_type == 'wrf':
                domain_key = str(
                    self.create_new_domain(ncfile.TITLE, "WRF", ncfile.DX, 'm',
                                           mask))
            self.insert_horiz_geom(domain_key, x_coord_array, y_coord_array,
                                   create_wrf_srid(self.windb2, ncfile))

            # Mask the domain if necessary
            if mask is not None:
                self.mask_domain(domain_key, mask)

        # Create a new table if necessary and add an initialization time column
        if file_type == 'windb2' and var_name.lower() == 'wind'.lower():
            if not self.windb2.table_exists('wind' + '_' + domain_key):
                self.create_new_table(domain_key, var_name,
                                      ('speed', 'direction'),
                                      ('real', 'smallint'))
                self._create_initialization_time_column(var_name, domain_key)
        else:
            if not self.windb2.table_exists('{}_{}'.format(
                    var_name.lower(), domain_key)):
                self.create_new_table(domain_key, var_name, ('value', ),
                                      ('real', ))
                self._create_initialization_time_column(
                    var_name.lower(), domain_key)

        # Make sure it's a string so that we don't have concatenation problems later
        domain_key = str(domain_key)

        # Get the geomkeys associated with the WRF coordinates
        horizGeomKey = self.calculateHorizWindGeomKeys(domain_key, nlong, nlat)

        # Create a counter to execute every so often
        counter = 0
        startTime = datetime.now()

        # Get the time array to iterate through
        if file_type == 'windb2':
            time_char_array = chartostring(ncfile.variables['Time'][:])
        elif file_type == 'wrf':
            time_char_array = chartostring(ncfile.variables['Times'][:])

        # Create a statement to use
        tCount = 0
        timeValuesToReturn = []
        for t in time_char_array:

            # Create a datetime from the WRF string
            t = datetime.strptime(t,
                                  '%Y-%m-%d_%H:%M:%S').replace(tzinfo=pytz.utc)

            # Zero the seconds if asked to
            if zero_seconds:
                t = t.replace(second=0)

            # Create the time in GeoServer/GeoWebCache format
            timeValuesToReturn.append(t.strftime('%Y-%m-%dT%H:%M:%S.000Z'))

            # Info
            print('Processing time for {}: {}'.format(var_name,
                                                      timeValuesToReturn[-1]))

            # Iterate through the x,y, and timearr and insert the WRF variable
            for h in height_array:

                # We actually need the index of the height, not the actual height itself
                height = None
                if file_type == 'windb2' and wrf_copied_var is False:
                    try:
                        z = numpy.argwhere(
                            ncfile.variables['height'][:] == h)[0, 0]
                        height = height_array[z]
                    except IndexError:
                        logger.error(
                            'Height {} to insert does not exist in WinDB2 file'
                            .format(h))
                        sys.exit(-1)
                elif wrf_copied_var is True:
                    height = height_array[0]
                else:
                    height = 0

                counter = 0

                # Open a temporary file to COPY from
                tempFile = tempfile.NamedTemporaryFile(mode='w')

                for x in range(horizGeomKey.shape[0]):
                    for y in range(horizGeomKey.shape[1]):

                        # Make sure that this is actually a x,y point we want to insert
                        # In order to create a mask of selective insert points, all
                        # a horizGeomKey of zero means we don't want to insert this one
                        if horizGeomKey[x, y] == 0:
                            continue

                        # Write the data string to the temp file
                        if file_type == 'windb2' and var_name.lower(
                        ) == 'wind'.lower():
                            if not (numpy.isnan(u[tCount, z, y, x])
                                    or numpy.isnan(v[tCount, z, y, x])):

                                # Add this row to be inserted into the database
                                # Note that we negate U and V so they exist in WinDB2 as the vernacular "coming from" wind direction
                                print('{}, {}, {}, {}, {}, {}, {}'.format(
                                    domain_key, horizGeomKey[x, y],
                                    t.strftime('%Y-%m-%d %H:%M:%S %Z'),
                                    util.speed(u[tCount, z, y, x], v[tCount, z,
                                                                     y, x]),
                                    int(
                                        util.calc_dir_deg(
                                            -u[tCount, z, y, x],
                                            -v[tCount, z, y, x])), height,
                                    init_t.strftime('%Y-%m-%d %H:%M:%S %Z')),
                                      file=tempFile)
                                counter += 1

                        elif file_type == 'windb2':
                            # Add this row to be inserted into the database
                            if self.config['vars'][var_name]['dims'] == 2:
                                val = ncVariable[tCount, y, x]
                            elif self.config['vars'][var_name]['dims'] == 3:
                                val = ncVariable[tCount, z, y, x]

                            if not numpy.isnan(val):
                                print('{}, {}, {}, {}, {}, {}'.format(
                                    domain_key, horizGeomKey[x, y],
                                    t.strftime('%Y-%m-%d %H:%M:%S %Z'), val,
                                    height,
                                    init_t.strftime('%Y-%m-%d %H:%M:%S %Z')),
                                      file=tempFile)
                                counter += 1

                        elif file_type == 'wrf':
                            if not numpy.isnan(ncVariable[tCount, y, x]):

                                # Add this row to be inserted into the database
                                print('{}, {}, {}, {}, {}'.format(
                                    domain_key, horizGeomKey[x, y],
                                    t.strftime('%Y-%m-%d %H:%M:%S %Z'),
                                    ncVariable[tCount, y, x],
                                    init_t.strftime('%Y-%m-%d %H:%M:%S %Z')),
                                      file=tempFile)
                                counter += 1

                # Insert the data
                tempFile.flush()
                if file_type == 'windb2' and var_name.lower() == 'wind'.lower(
                ):
                    insertColumns = columns = ('domainkey', 'geomkey', 't',
                                               'speed', 'direction', 'height',
                                               'init')
                else:
                    insertColumns = columns = ('domainkey', 'geomkey', 't',
                                               'value', 'height', 'init')
                try:
                    self.windb2.curs.copy_from(open(tempFile.name, 'r'),
                                               var_name + '_' + domain_key,
                                               sep=',',
                                               columns=insertColumns)
                except psycopg2.IntegrityError as e:

                    # Delete the duplicate data
                    errorTest = 'duplicate key value violates unique constraint "' + var_name.lower(
                    ) + "_" + domain_key + '_domainkey_geomkey_t_height_init_key"'
                    if re.search(errorTest, str(e.pgerror)):

                        # Delete the data and retry the insert if asked to replace data in the function call
                        if replace_data:

                            # Rollback to the last commit (necessary to reset the database connection)
                            self.windb2.conn.rollback()

                            # Delete that timearr (assumes UTC timearr zone)
                            sql = 'DELETE FROM ' +  var_name + '_' + domain_key + \
                                  ' WHERE t = timestamp with time zone\'' + t.strftime('%Y-%m-%d %H:%M:%S %Z') + '\' ' + \
                                  'AND height=' + str(h)
                            print("Deleting conflicting times: " + sql)
                            self.windb2.curs.execute(sql)
                            self.windb2.conn.commit()

                            # Reinsert that timearr
                            self.windb2.curs.copy_from(
                                open(tempFile.name, 'r'),
                                var_name + '_' + domain_key,
                                sep=',',
                                columns=insertColumns)

                            # Commit again or the reinserts won't stick
                            self.windb2.conn.commit()
                            continue

                        # Otherwise, just notify that the insert failed because of duplicate data. We do re-raise this error
                        # because it's assumed that we want to suplement the WinDB with other data-heights if available.
                        else:
                            logging.warning('ERROR ON INSERT: {}'.format(
                                e.pgerror))
                            logging.warning(
                                'Use \'replace_data=True\' if you want the data to be reinserted.'
                            )
                            self.windb2.conn.rollback()
                            continue

                # Commit the changes
                self.windb2.conn.commit()

                # Calaculate the insert rate
                elapsedTime = (datetime.now() - startTime).seconds
                try:
                    print('Inserted {}, {}-m height x,y wind points at {} I/s'.
                          format(counter, height_array[z],
                                 counter / elapsedTime))
                except ZeroDivisionError:
                    print('Inserted {}, {}-m height x,y wind points'.format(
                        counter, height_array[z]))
                except UnboundLocalError:
                    print('Inserted {}, {}-m height x,y wind points'.format(
                        counter, height_array[0]))

                # Close the tempfile so it is deleted
                tempFile.close()

            # Increment the time
            tCount += 1

        return timeValuesToReturn, domain_key
示例#5
0
def plotBuoyWRFWindSpeedPerMonth(yearNum, monthNum, timeDeltaMinutes, wrfDomain, wrfGeomKey, wrfHeight, buoyDomain, curs):
   """Creates a hourly wind speed average and power curve plot for a particular wind farm"""

   import matplotlib.pyplot as plt
   import numpy

   # Execute the statement to get the avg winds
   windBuoyDataSql = """SELECT t, m_u, m_v, b_u, b_v
                        FROM (SELECT t, U(speed,direction) as m_u, V(speed,direction) as m_v
                              FROM wind_""" + str(wrfDomain) + """
                              WHERE geomkey=""" + str(wrfGeomKey) + """ AND 
                                    height=""" + str(wrfHeight) + """ AND 
                                    date_part('month', t)=""" + str(monthNum) + """ AND 
                                    date_part('year', t)=""" + str(yearNum) + """) m
                        LEFT JOIN
                             (SELECT t, U(speed,direction) as b_u, V(speed,direction) as b_v
                              FROM wind_""" + str(buoyDomain) + """
                              WHERE date_part('month', t)=""" + str(monthNum) + """ AND 
                                    date_part('year', t)=""" + str(yearNum) + """) b 
                        USING (t) 
                        ORDER BY t;"""
   logging.debug("Executing the statement: {}".format(windBuoyDataSql))
   curs.execute(windBuoyDataSql)
   queryResult = curs.fetchall()
   queryResult = numpy.array(queryResult)

   # Divide up the data
   time = queryResult[:,0]
   wrfBuoyWind = numpy.array(queryResult[:,1:],dtype=numpy.float)
   
   # Get rid of the None because they cannot be used in masked arrays, convert to NaNs
   for i in range(wrfBuoyWind.shape[1]):
       wrfBuoyWind[:,i] = none2NaN(wrfBuoyWind[:, i])

   # Get the height of the buoy data (and assume they are all the same from this location)
   buoyHeightSql = "SELECT DISTINCT(height) FROM wind_" + str(buoyDomain)
   curs.execute(buoyHeightSql)
   buoyHeight = curs.fetchone()[0]
   
   # Debug
   print("wrfBuoyWind=", wrfBuoyWind)

   #
   # Set up the resources of the wind speed plots
   #
   fig = plt.figure()
   ax = fig.add_subplot(411)

   #
   # Get the name of the buoy
   #
   curs.execute("SELECT name FROM domain WHERE key=" + str(buoyDomain))
   buoyName = str(curs.fetchone()[0]).upper()
   plt.title(buoyName + ", " + str(yearNum) + "-" + str(monthNum))
   
   # Set the output filename
   filename = "wrf-wind-domain-" + str(wrfDomain) + "-buoy-" + buoyName + "-" + str(yearNum) + "-" + str(monthNum)


   # Data reduction on the plots in case we have a lot of data
   obsMarkEvery = int(60/timeDeltaMinutes)

   # MAKE THE PLOTS FOR THE U-DIRECTION
   ax.set_ylabel("u-wind (m/s)",size='small')
   xVals = numpy.arange(wrfBuoyWind.shape[0])
   ax.axhline(y=0,linewidth=0.5,linestyle='--',color='b')
   ax.plot_date(matplotlib.dates.date2num(time), wrfBuoyWind[:,0],
                linewidth=1,linestyle='-',marker=None,color='black',label='WRF(' + str(wrfHeight) + ' m)',
                markevery=2)
   ax.plot_date(matplotlib.dates.date2num(time)[numpy.isfinite(wrfBuoyWind[:,2])], wrfBuoyWind[:,2][numpy.isfinite(wrfBuoyWind[:,2])],
                linewidth=1,linestyle='-',marker=None,color='orange',label='Obs.(' + str(buoyHeight) + ' m)',
                markevery=obsMarkEvery)
   ax.set_ylim((-10,10))
   ax.legend(loc='upper left')
   ax.legend(loc=(0,0.5))

   # Set the legend font to small
   leg = plt.gca().get_legend()
   leg.draw_frame(False)
   ltext  = leg.get_texts()
   llines = leg.get_lines()
   plt.setp(ltext, fontsize='xx-small')
   plt.setp(llines, linewidth=2.0)

   # MAKE THE PLOTS FOR THE V-DIRECTION
   ax = fig.add_subplot(412,sharex=ax)
   ax.set_ylabel("v-wind (m/s)",size='small')
   ax.axhline(y=0,linewidth=0.5,linestyle='--',color='b')
   ax.plot_date(matplotlib.dates.date2num(time), wrfBuoyWind[:,1],
                linewidth=1,linestyle='-',marker=None,color='black',label='WRF(' + str(wrfHeight) + ' m)',
                markevery=2)
   ax.plot_date(matplotlib.dates.date2num(time)[numpy.isfinite(wrfBuoyWind[:,3])], wrfBuoyWind[:,3][numpy.isfinite(wrfBuoyWind[:,3])],
                linewidth=1,linestyle='-',marker=None,color='green',label='Obs.(' + str(buoyHeight) + ' m)',
                markevery=obsMarkEvery)
   ax.set_ylim((-10,10))
   ax.legend(loc='upper left')
   ax.legend(loc=(0,0.5))

   # Set the legend font to small
   leg = plt.gca().get_legend()
   leg.draw_frame(False)
   ltext  = leg.get_texts()
   llines = leg.get_lines()
   plt.setp(ltext, fontsize='xx-small')
   plt.setp(llines, linewidth=2.0)

   # MAKE THE PLOTS FOR THE SPEED
   speedWrf = util.speed(wrfBuoyWind[:,0], wrfBuoyWind[:,1])
   speedObs = util.speed(wrfBuoyWind[:,2], wrfBuoyWind[:,3])
   ax = fig.add_subplot(413,sharex=ax)
   ax.set_ylabel("wind (m/s)",size='small')
   ax.plot_date(matplotlib.dates.date2num(time),speedWrf,
                linewidth=1,linestyle='-',marker=None,color='black',label='WRF(' + str(wrfHeight) + ' m)')
   ax.plot_date(matplotlib.dates.date2num(time)[numpy.isfinite(speedObs)],speedObs[numpy.isfinite(speedObs)],
           linewidth=1,linestyle='-',marker=None,color='red',label='Obs.(' + str(buoyHeight) + ' m)',
           markevery=obsMarkEvery)
   ax.set_ylim((0,15))
   ax.legend(loc='upper left')
   ax.legend(loc=(0,0.5))

   # Set the legend font to small
   leg = plt.gca().get_legend()
   leg.draw_frame(False)
   ltext  = leg.get_texts()   # Set the location of the tick marks
   llines = leg.get_lines() 
   plt.setp(ltext, fontsize='xx-small')
   plt.setp(llines, linewidth=2.0)
   
   # MAKE THE PLOTS FOR ABS ERROR - masked where the obs are NaN
   ax = fig.add_subplot(414,sharex=ax)
   ax.set_ylabel("abs. err. (m/s)",size='small')
   ax.plot_date(matplotlib.dates.date2num(time)[numpy.isfinite(speedObs)],numpy.abs(speedWrf[numpy.isfinite(speedObs)] -
                                                                                    speedObs[numpy.isfinite(speedObs)]),
                linestyle='-',marker=None,color='red',label='speed-error',alpha=0.6)
   ax.set_ylim((0,12))
   ax.legend(loc='upper left')

   # Set the legend font to small
   leg = plt.gca().get_legend()
   leg.draw_frame(False)
   ltext  = leg.get_texts()
   llines = leg.get_lines()
   plt.setp(ltext, fontsize='xx-small')
   plt.setp(llines, linewidth=2.0)

   # # Add an x-axis to the bottom
   # xLabels = [0,7,14,21,28]
   # xLabelsMinor = numpy.arange(31)*24*60./timeDeltaMinutes
   # ax.set_xticks(numpy.array(xLabels)*24*60/timeDeltaMinutes)
   # ax.set_xticks(xLabelsMinor,minor=True)
   # ax.set_xlabel("day of month")
   # ax.set_xticklabels(xLabels)
   # ax.set_xlim(0,numpy.max(xVals))
   fig.autofmt_xdate()

   # Save the plot to a file
   plt.savefig(filename + '.png',bbox_inches='tight')
   plt.savefig(filename + '.eps',bbox_inches='tight')