Пример #1
0
def destination_azimuth_distance(lat, lon, az, dist, dist_units='m'):
    """
    This procedure will calculate a destination lat/lon from
    an initial lat/lon and azimuth and distance.

    Parameters
    ----------
    lat : float
        Initial latitude.
    lon : float
        Initial longitude.
    az : float
        Azimuth in degrees.
    dist : float
        Distance
    dist_units : str
        Units for dist

    Returns
    -------
    lat2 : float
        Latitude of new point in degrees
    lon2 : float
        Longitude of new point in degrees

    """
    # Volumetric Mean Radius of Earth in km
    R = 6378.

    # Convert az to radian
    brng = np.radians(az)

    # Convert distance to km
    d = convert_units(dist, dist_units, 'km')

    # Convert lat/lon to radians
    lat = np.radians(lat)
    lon = np.radians(lon)

    # Using great circle equations
    lat2 = np.arcsin(
        np.sin(lat) * np.cos(d / R) +
        np.cos(lat) * np.sin(d / R) * np.cos(brng))
    lon2 = lon + np.arctan2(
        np.sin(brng) * np.sin(d / R) * np.cos(lat),
        np.cos(d / R) - np.sin(lat) * np.sin(lat2))

    return np.degrees(lat2), np.degrees(lon2)
Пример #2
0
    def compare_time_series_trends(self,
                                   var_name=None,
                                   comp_dataset=None,
                                   comp_var_name=None,
                                   time_match_threshhold=60,
                                   time_shift=60 * 60,
                                   time_step=None,
                                   time_qc_threshold=60 * 15):
        """
        Method to perform a time series comparison test between two Xarray Datasets
        to detect a shift in time based on two similar variables. This test will
        compare two similar measurements and look to see if there is a time shift
        forwards or backwards that makes the comparison better. If so assume the
        time has shifted.

        This test is not 100% accurate. It may be fooled with noisy data. Use
        with your own discretion.

        Parameters
        ----------
        var_name : str
            Data variable name.
        comp_dataset : Xarray Dataset
            Dataset containing comparison data to use in test.
        comp_var_name : str
            Name of variable in comp_dataset to use in test.
        time_match_threshhold : int
            Number of seconds to use in tolerance with reindex() method
            to match time from self to comparison Dataset.
        time_shift : int
            Number of seconds to shift analysis window before and after
            the time in self Dataset time.
        time_step : int
            Time step in seconds for self Dataset time. If not provided
            will attempt to find the most common time step.
        time_qc_threshold : int
            The quality control threshold to use for setting test. If the
            calculated time shift is larger than this value will set all
            values in the QC variable to a tripped test value.

        """

        # If no comparison variable name given assume matches variable name
        if comp_var_name is None:
            comp_var_name = var_name

        # If no comparison Dataset given assume self Dataset
        if comp_dataset is None:
            comp_dataset = self

        # Extract copy of DataArray for work below
        self_da = copy.deepcopy(self._obj[var_name])
        comp_da = copy.deepcopy(comp_dataset[comp_var_name])

        # Convert comp data units to match
        comp_da.values = convert_units(comp_da.values, comp_da.attrs['units'],
                                       self_da.attrs['units'])
        comp_da.attrs['units'] = self_da.attrs['units']

        # Match comparison data to time of data
        if time_step is None:
            time_step = determine_time_delta(self._obj['time'].values)
        sum_diff = np.array([], dtype=float)
        time_diff = np.array([], dtype=np.int32)
        for tm_shift in range(-1 * time_shift, time_shift + int(time_step),
                              int(time_step)):
            self_da_shifted = self_da.assign_coords(
                time=self_da.time.values.astype('datetime64[s]') + tm_shift)

            data_matched, comp_data_matched = xr.align(self_da, comp_da)
            self_da_shifted = self_da_shifted.reindex(
                time=comp_da.time.values,
                method='nearest',
                tolerance=np.timedelta64(time_match_threshhold, 's'))
            diff = np.abs(self_da_shifted.values - comp_da.values)
            sum_diff = np.append(sum_diff, np.nansum(diff))
            time_diff = np.append(time_diff, tm_shift)

        index = np.argmin(np.abs(sum_diff))
        time_diff = time_diff[index]

        index = None
        if np.abs(time_diff) > time_qc_threshold:
            index = np.arange(0, self_da.size)
        meaning = (
            f"Time shift detected with Minimum Difference test. Comparison of "
            f"{var_name} with {comp_var_name} off by {time_diff} seconds "
            f"exceeding absolute threshold of {time_qc_threshold} seconds.")
        self._obj.qcfilter.add_test(var_name,
                                    index=index,
                                    test_meaning=meaning,
                                    test_assessment='Indeterminate')
Пример #3
0
    def bsrn_comparison_tests(self,
                              test,
                              gbl_SW_dn_name=None,
                              glb_diffuse_SW_dn_name=None,
                              direct_normal_SW_dn_name=None,
                              glb_SW_up_name=None,
                              glb_LW_dn_name=None,
                              glb_LW_up_name=None,
                              air_temp_name=None,
                              test_assessment='Indeterminate',
                              lat_name='lat',
                              lon_name='lon',
                              LWdn_lt_LWup_component=25.,
                              LWdn_gt_LWup_component=300.,
                              use_dask=False):
        """
        Method to apply BSRN comparison tests and add results to ancillary quality control variable.
        Need to provided variable name for each measurement for the test to be performed. All radiation
        data must be in W/m^2 units. Test will provided exception if required variable name is missing.

        Parameters
        ----------
        test : str
            Type of tests to apply. Options include: 'Global over Sum SW Ratio', 'Diffuse Ratio',
            'SW up', 'LW down to air temp', 'LW up to air temp', 'LW down to LW up'
        gbl_SW_dn_name : str
            Variable name in Dataset for global shortwave downwelling radiation
            measured by unshaded pyranometer
        glb_diffuse_SW_dn_name : str
            Variable name in Dataset for global diffuse shortwave downwelling radiation
            measured by shaded pyranometer
        direct_normal_SW_dn_name : str
            Variable name in Dataset for direct normal shortwave downwelling radiation
        glb_SW_up_name : str
            Variable name in Dataset for global shortwave upwelling radiation
        glb_LW_dn_name : str
            Variable name in Dataset for global longwave downwelling radiation
        glb_LW_up_name : str
            Variable name in Dataset for global longwave upwelling radiation
        air_temp_name : str
            Variable name in Dataset for atmospheric air temperature. Variable used
            in longwave tests.
        test_assessment : str
            Test assessment string value appended to flag_assessments attribute of QC variable.
        lat_name : str
            Variable name in the Dataset for latitude
        lon_name : str
            Variable name in the Dataset for longitude
        LWdn_lt_LWup_component : int or float
            Value used in longwave down less than longwave up test.
        LWdn_gt_LWup_component : int or float
            Value used in longwave down greater than longwave up test.
        use_dask : boolean
            Option to use Dask for processing if data is stored in a Dask array

        References
        ----------
        Long, Charles N., and Ellsworth G. Dutton. "BSRN Global Network recommended QC tests, V2. x." (2010).

        Examples
        --------
            .. code-block:: python

                ds_object = act.io.armfiles.read_netcdf(act.tests.EXAMPLE_BRS, cleanup_qc=True)
                ds_object.qcfilter.bsrn_comparison_tests(
                    gbl_SW_dn_name='down_short_hemisp',
                    glb_diffuse_SW_dn_name='down_short_diffuse_hemisp',
                    direct_normal_SW_dn_name='short_direct_normal',
                    glb_SW_up_name='up_short_hemisp',
                    glb_LW_dn_name='down_long_hemisp_shaded',
                    glb_LW_up_name='up_long_hemisp',
                    use_dask=True)
        """

        if isinstance(test, str):
            test = [test]

        test_options = [
            'Global over Sum SW Ratio', 'Diffuse Ratio', 'SW up',
            'LW down to air temp', 'LW up to air temp', 'LW down to LW up'
        ]

        solar_constant = 1360.8
        sza, Sa = _calculate_solar_parameters(self._obj, lat_name, lon_name,
                                              solar_constant)

        # Ratio of Global over Sum SW
        if test_options[0] in test:
            if gbl_SW_dn_name is None or glb_diffuse_SW_dn_name is None or direct_normal_SW_dn_name is None:
                raise ValueError(
                    'Must set keywords gbl_SW_dn_name, glb_diffuse_SW_dn_name, '
                    f'direct_normal_SW_dn_name for {test_options[0]} test.')

            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=RuntimeWarning)
                if use_dask and isinstance(
                        self._obj[glb_diffuse_SW_dn_name].data, da.Array):
                    sum_sw_down = (self._obj[glb_diffuse_SW_dn_name].data +
                                   self._obj[direct_normal_SW_dn_name].data *
                                   np.cos(np.radians(sza)))
                    sum_sw_down[sum_sw_down < 50] = np.nan
                    ratio = self._obj[gbl_SW_dn_name].data / sum_sw_down
                    index_a = sza < 75
                    index_1 = da.where((ratio > 1.08) & index_a, True, False)
                    index_2 = da.where((ratio < 0.92) & index_a, True, False)
                    index_b = (sza >= 75) & (sza < 93)
                    index_3 = da.where((ratio > 1.15) & index_b & index_b,
                                       True, False)
                    index_4 = da.where((ratio < 0.85) & index_b, True, False)
                    index = (index_1 | index_2 | index_3 | index_4).compute()
                else:
                    sum_sw_down = (self._obj[glb_diffuse_SW_dn_name].values +
                                   self._obj[direct_normal_SW_dn_name].values *
                                   np.cos(np.radians(sza)))
                    sum_sw_down[sum_sw_down < 50] = np.nan
                    ratio = self._obj[gbl_SW_dn_name].values / sum_sw_down
                    index_a = sza < 75
                    index_1 = (ratio > 1.08) & index_a
                    index_2 = (ratio < 0.92) & index_a
                    index_b = (sza >= 75) & (sza < 93)
                    index_3 = (ratio > 1.15) & index_b
                    index_4 = (ratio < 0.85) & index_b
                    index = index_1 | index_2 | index_3 | index_4

            test_meaning = "Ratio of Global over Sum shortwave larger than expected"
            self._obj.qcfilter.add_test(gbl_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
            self._obj.qcfilter.add_test(glb_diffuse_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
            self._obj.qcfilter.add_test(direct_normal_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)

        # Diffuse Ratio
        if test_options[1] in test:
            if gbl_SW_dn_name is None or glb_diffuse_SW_dn_name is None:
                raise ValueError(
                    'Must set keywords gbl_SW_dn_name, glb_diffuse_SW_dn_name '
                    f'for {test_options[1]} test.')

            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=RuntimeWarning)
                if use_dask and isinstance(
                        self._obj[glb_diffuse_SW_dn_name].data, da.Array):
                    ratio = self._obj[glb_diffuse_SW_dn_name].data / self._obj[
                        gbl_SW_dn_name].data
                    ratio[self._obj[gbl_SW_dn_name].data < 50] = np.nan
                    index_a = sza < 75
                    index_1 = da.where((ratio >= 1.05) & index_a, True, False)
                    index_b = (sza >= 75) & (sza < 93)
                    index_2 = da.where((ratio >= 1.10) & index_b, True, False)
                    index = (index_1 | index_2).compute()
                else:
                    ratio = self._obj[
                        glb_diffuse_SW_dn_name].values / self._obj[
                            gbl_SW_dn_name].values
                    ratio[self._obj[gbl_SW_dn_name].values < 50] = np.nan
                    index_a = sza < 75
                    index_1 = (ratio >= 1.05) & index_a
                    index_b = (sza >= 75) & (sza < 93)
                    index_2 = (ratio >= 1.10) & index_b
                    index = index_1 | index_2

            test_meaning = "Ratio of Diffuse Shortwave over Global Shortwave larger than expected"
            self._obj.qcfilter.add_test(gbl_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
            self._obj.qcfilter.add_test(glb_diffuse_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)

        # Shortwave up comparison
        if test_options[2] in test:
            if glb_SW_up_name is None or glb_diffuse_SW_dn_name is None or direct_normal_SW_dn_name is None:
                raise ValueError(
                    'Must set keywords glb_SW_up_name, glb_diffuse_SW_dn_name, '
                    f'direct_normal_SW_dn_name for {test_options[2]} test.')

            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=RuntimeWarning)
                if use_dask and isinstance(
                        self._obj[glb_diffuse_SW_dn_name].data, da.Array):
                    sum_sw_down = (self._obj[glb_diffuse_SW_dn_name].data +
                                   self._obj[direct_normal_SW_dn_name].data *
                                   np.cos(np.radians(sza)))

                    sum_sw_down[sum_sw_down < 50] = np.nan
                    index = da.where(
                        self._obj[glb_SW_up_name].data > sum_sw_down, True,
                        False).compute()
                else:
                    sum_sw_down = (self._obj[glb_diffuse_SW_dn_name].values +
                                   self._obj[direct_normal_SW_dn_name].values *
                                   np.cos(np.radians(sza)))
                    sum_sw_down[sum_sw_down < 50] = np.nan
                    index = self._obj[glb_SW_up_name].values > sum_sw_down

            test_meaning = "Ratio of Shortwave Upwelling greater than Shortwave Sum"
            self._obj.qcfilter.add_test(glb_SW_up_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
            self._obj.qcfilter.add_test(glb_diffuse_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
            self._obj.qcfilter.add_test(direct_normal_SW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)

        # Longwave down to air temperature comparison
        if test_options[3] in test:
            if glb_LW_dn_name is None or air_temp_name is None:
                raise ValueError(
                    'Must set keywords glb_LW_dn_name, air_temp_name '
                    f' for {test_options[3]} test.')

            air_temp = convert_units(self._obj[air_temp_name].values,
                                     self._obj[air_temp_name].attrs['units'],
                                     'degK')
            if use_dask and isinstance(self._obj[glb_LW_dn_name].data,
                                       da.Array):
                air_temp = da.array(air_temp)
                conversion = da.array(Stefan_Boltzmann * air_temp**4)
                index_1 = (0.4 * conversion) > self._obj[glb_LW_dn_name].data
                index_2 = (conversion + 25.) < self._obj[glb_LW_dn_name].data
                index = (index_1 | index_2).compute()
            else:
                conversion = Stefan_Boltzmann * air_temp**4
                index_1 = (0.4 * conversion) > self._obj[glb_LW_dn_name].values
                index_2 = (conversion + 25.) < self._obj[glb_LW_dn_name].values
                index = index_1 | index_2

            test_meaning = "Longwave downwelling comparison to air temperature out side of expected range"
            self._obj.qcfilter.add_test(glb_LW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)

        # Longwave up to air temperature comparison
        if test_options[4] in test:
            if glb_LW_up_name is None or air_temp_name is None:
                raise ValueError(
                    'Must set keywords glb_LW_up_name, air_temp_name '
                    f'for {test_options[3]} test.')

            air_temp = convert_units(self._obj[air_temp_name].values,
                                     self._obj[air_temp_name].attrs['units'],
                                     'degK')
            if use_dask and isinstance(self._obj[glb_LW_up_name].data,
                                       da.Array):
                air_temp = da.array(air_temp)
                index_1 = (Stefan_Boltzmann *
                           (air_temp - 15)**4) > self._obj[glb_LW_up_name].data
                index_2 = (Stefan_Boltzmann *
                           (air_temp + 25)**4) < self._obj[glb_LW_up_name].data
                index = (index_1 | index_2).compute()
            else:
                index_1 = (
                    Stefan_Boltzmann *
                    (air_temp - 15)**4) > self._obj[glb_LW_up_name].values
                index_2 = (
                    Stefan_Boltzmann *
                    (air_temp + 25)**4) < self._obj[glb_LW_up_name].values
                index = index_1 | index_2

            test_meaning = "Longwave upwelling comparison to air temperature out side of expected range"
            self._obj.qcfilter.add_test(glb_LW_up_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)

        # Lonwave down to longwave up comparison
        if test_options[5] in test:
            if glb_LW_dn_name is None or glb_LW_up_name is None:
                raise ValueError(
                    'Must set keywords glb_LW_dn_name, glb_LW_up_name '
                    f'for {test_options[3]} test.')

            if use_dask and isinstance(self._obj[glb_LW_dn_name].data,
                                       da.Array):
                index_1 = da.where(
                    self._obj[glb_LW_dn_name].data >
                    (self._obj[glb_LW_up_name].data + LWdn_lt_LWup_component),
                    True, False)
                index_2 = da.where(
                    self._obj[glb_LW_dn_name].data <
                    (self._obj[glb_LW_up_name].data - LWdn_gt_LWup_component),
                    True, False)
                index = (index_1 | index_2).compute()
            else:
                index_1 = self._obj[glb_LW_dn_name].values > (
                    self._obj[glb_LW_up_name].values + LWdn_lt_LWup_component)
                index_2 = self._obj[glb_LW_dn_name].values < (
                    self._obj[glb_LW_up_name].values - LWdn_gt_LWup_component)
                index = index_1 | index_2

            test_meaning = "Lonwave downwelling compared to longwave upwelling outside of expected range"
            self._obj.qcfilter.add_test(glb_LW_dn_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
            self._obj.qcfilter.add_test(glb_LW_up_name,
                                        index=index,
                                        test_assessment=test_assessment,
                                        test_meaning=test_meaning)
Пример #4
0
 def run(self, data: np.ndarray, in_units: str,
         out_units: str) -> np.ndarray:
     return data_utils.convert_units(data, in_units, out_units)