Exemple #1
0
    def soundings_plot_3d(self, mode: str = 'svcorr', color_by: str = 'depth', start_time: float = None, end_time: float = None):
        """
        Plots a 3d representation of the alongtrack/acrosstrack/depth values generated by sv correct.
        If a time is provided, isolates that time.

        Parameters
        ----------
        mode
            str, either 'svcorr' to plot the svcorrected offsets, or 'georef' to plot the georeferenced soundings
        color_by
            str, either 'depth' or 'sector'
        start_time
            start time in utc seconds, optional if you want to subset by time
        end_time
            end time in utc seconds, optional if you want to subset by time

        Returns
        -------
        plt.Axes
            matplotlib axes object for plot
        """

        if start_time is not None or start_time is not None:
            self.fqpr.subset_by_time(start_time, end_time)

        xvar, yvar, zvar = self._parse_plot_mode(mode)

        minz = self.fqpr.calc_min_var(zvar)
        maxz = self.fqpr.calc_max_var(zvar)
        miny = self.fqpr.calc_min_var(yvar)
        maxy = self.fqpr.calc_max_var(yvar)
        if mode == 'svcorr':  # svcorrected is alongtrack/acrosstrack in meters.  Want the scales to be equal so it doesnt look weird
            minx = miny
            maxx = maxy
        else:  # georeferenced is northing/easting, scales cant be equal of course
            minx = self.fqpr.calc_min_var(xvar)
            maxx = self.fqpr.calc_max_var(xvar)

        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')

        for rp in self.fqpr.multibeam.raw_ping:
            x_idx, x_stck = stack_nan_array(rp[xvar], stack_dims=('time', 'beam'))
            y_idx, y_stck = stack_nan_array(rp[yvar], stack_dims=('time', 'beam'))
            z_idx, z_stck = stack_nan_array(rp[zvar], stack_dims=('time', 'beam'))

            if color_by == 'depth':
                ax.scatter(x_stck.values, y_stck.values, z_stck.values, marker='o', s=10, c=z_stck.values)
            elif color_by == 'sector':
                sector_vals = rp.txsector_beam.values[x_idx]
                ax.scatter(x_stck.values, y_stck.values, z_stck.values, marker='o', s=10, c=sector_vals)

        ax.set_xlim(minx, maxx)
        ax.set_ylim(miny, maxy)
        ax.set_zlim(maxz, minz)

        if start_time is not None or start_time is not None:
            self.fqpr.restore_subset()
        return ax
    def test_stack_and_reform_nan_array(self):
        # build an array with nans
        data = np.full((10, 400), 1.5)
        data[1, 50] = np.nan
        data[2, 75] = np.nan
        data[3, 150] = np.nan
        data_array = xr.DataArray(data,
                                  coords={
                                      'time': np.arange(10),
                                      'beam': np.arange(400)
                                  },
                                  dims=['time', 'beam'])
        # flatten the array and remove the nans by stacking the time and beam dimensions to get a 1dim result
        original_index, stacked_data = stack_nan_array(data_array)

        # size of the stacked data should be the original size minus the number of nans
        assert original_index[0].shape == (3997, )
        assert original_index[1].shape == (3997, )
        assert stacked_data.shape == (3997, )
        assert np.all(stacked_data == stacked_data[0])

        # we do the stack/reform thing so that we can operate on the data with functions that do not play well with nan
        # we can run a routine on the stacked result, and rebuild the original shape with the nans afterwards
        # multibeam data with varying beams or with beams with no data will show as nan in the time/beam arrays

        # get back to the original array by building an array of nans equal in shape to the original array and repopulate
        #  with the original data
        orig_array = reform_nan_array(stacked_data, original_index,
                                      data_array.shape, data_array.coords,
                                      data_array.dims)
        assert np.isnan(orig_array[1, 50])
        assert np.isnan(orig_array[2, 75])
        assert np.isnan(orig_array[3, 150])
Exemple #3
0
def get_times(pingtime: xr.DataArray, additional: xr.DataArray):
    """
    Given ping time and beamwise time addition, return the time for each beam.  Provides unique times, as
    by just adding ping time and twtt, you might end up with duplicate times.  To get back to beamwise times, an
    index is provided that you can select by.

    Parameters
    ----------
    pingtime
        1dim array of timestamps representing time of ping
    additional
        2dim (time/beam) array of timestamps representing additional delay

    Returns
    -------
    tuple
        tuple of numpy arrays, 2d indices of original rx_tstmp Dataarray, used to reconstruct array
    np.array
        1d indices of time of receive that can be used to reconstruct non-unique timestamps
    xr.DataArray
        1 dim array of timestamps for unique times of receive

    """
    rx_tstmp = pingtime + additional
    rx_tstmp_idx, rx_tstmp_stck = stack_nan_array(rx_tstmp, stack_dims=('time', 'beam'))
    unique_rx_times, inv_idx = np.unique(rx_tstmp_stck.values, return_inverse=True)
    rx_interptimes = xr.DataArray(unique_rx_times, coords=[unique_rx_times], dims=['time']).chunk()
    return rx_tstmp_idx, inv_idx, rx_interptimes
Exemple #4
0
def transform_vyperdatum(x: xr.DataArray,
                         y: xr.DataArray,
                         z: xr.DataArray,
                         source_datum: Union[str, int] = 'nad83',
                         final_datum: str = 'mllw',
                         vdatum_directory: str = None):
    """
    When we specify a NOAA vertical datum (NOAA Mean Lower Low Water, NOAA Mean High Water) in Kluster, we use
    vyperdatum/VDatum to transform the points to the appropriate vertical datum.

    Parameters
    ----------
    x
        easting for each point in source_datum coordinate system
    y
        northing for each point in source_datum coordinate system
    z
        depth offset for each point in source_datum coordinate system
    source_datum
        The horizontal coordinate system of the xyz provided, should be a string identifier ('nad83') or an EPSG code
        specifying the horizontal coordinate system
    final_datum
        The desired final_datum vertical datum as a string (one of 'mllw', 'mhw')
    vdatum_directory
            if 'NOAA MLLW' 'NOAA MHW' is the vertical reference, a path to the vdatum directory is required here

    Returns
    -------
    xr.DataArray
        original z array with vertical transformation applied, this new z is at final_datum
    xr.DataArray
        uncertainty associated with the vertical transformation between the source and destination datum
    """

    if vdatum_directory:
        vp = VyperPoints(vdatum_directory=vdatum_directory, silent=True)
    else:
        vp = VyperPoints(silent=True)

    if not os.path.exists(vp.vdatum.vdatum_path):
        raise EnvironmentError(
            'Unable to find path to VDatum folder: {}'.format(
                vp.vdatum.vdatum_path))
    z_idx, z_stck = stack_nan_array(z, stack_dims=('time', 'beam'))
    vp.transform_points(source_datum, final_datum, x, y, z=z_stck.values)
    z = reform_nan_array(np.around(vp.z, 3), z_idx, z.shape, z.coords, z.dims)
    xarray_unc = reform_nan_array(np.around(vp.unc, 3), z_idx, z.shape,
                                  z.coords, z.dims)

    return z, xarray_unc
Exemple #5
0
def georef_by_worker(sv_corr: list,
                     alt: xr.DataArray,
                     lon: xr.DataArray,
                     lat: xr.DataArray,
                     hdng: xr.DataArray,
                     heave: xr.DataArray,
                     wline: float,
                     vert_ref: str,
                     input_crs: CRS,
                     horizontal_crs: CRS,
                     z_offset: float,
                     vdatum_directory: str = None):
    """
    Use the raw attitude/navigation to transform the vessel relative along/across/down offsets to georeferenced
    soundings.  Will support transformation to geographic and projected coordinate systems and with a vertical
    reference that you select.

    Parameters
    ----------
    sv_corr
        [x, y, z] offsets generated with sv_correct
    alt
        1d (time) altitude in meters
    lon
        1d (time) longitude in degrees
    lat
        1d (time) latitude in degrees
    hdng
        1d (time) heading in degrees
    heave
        1d (time) heave in degrees
    wline
        waterline offset from reference point
    vert_ref
        vertical reference point, one of ['ellipse', 'vessel', 'waterline']
    input_crs
        pyproj CRS object, input coordinate reference system information for this run
    horizontal_crs
        pyproj CRS object, destination coordinate reference system information for this run
    z_offset
        lever arm from reference point to transmitter
    vdatum_directory
            if 'NOAA MLLW' 'NOAA MHW' is the vertical reference, a path to the vdatum directory is required here

    Returns
    -------
    list
        [xr.DataArray alongtrack offset (time, beam), xr.DataArray acrosstrack offset (time, beam),
         xr.DataArray down offset (time, beam), xr.DataArray corrected heave for TX - RP lever arm, all zeros if in 'ellipse' mode (time),
         xr.DataArray corrected altitude for TX - RP lever arm, all zeros if in 'vessel' or 'waterline' mode (time)]
    """
    g = horizontal_crs.get_geod()

    # unpack the sv corrected data output
    alongtrack = sv_corr[0]
    acrosstrack = sv_corr[1]
    depthoffset = sv_corr[2] + z_offset
    # generate the corrected depth offset depending on the desired vertical reference
    corr_dpth = None
    corr_heave = None
    corr_altitude = None
    if vert_ref in kluster_variables.ellipse_based_vertical_references:
        corr_altitude = alt
        corr_heave = xr.zeros_like(corr_altitude)
        corr_dpth = (depthoffset - corr_altitude.values[:, None]).astype(
            np.float32)
    elif vert_ref == 'vessel':
        corr_heave = heave
        corr_altitude = xr.zeros_like(corr_heave)
        corr_dpth = (depthoffset + corr_heave.values[:, None]).astype(
            np.float32)
    elif vert_ref == 'waterline':
        corr_heave = heave
        corr_altitude = xr.zeros_like(corr_heave)
        corr_dpth = (depthoffset + corr_heave.values[:, None] - wline).astype(
            np.float32)

    # get the sv corrected alongtrack/acrosstrack offsets stacked without the NaNs (arrays have NaNs for beams that do not exist in that sector)
    at_idx, alongtrack_stck = stack_nan_array(alongtrack,
                                              stack_dims=('time', 'beam'))
    ac_idx, acrosstrack_stck = stack_nan_array(acrosstrack,
                                               stack_dims=('time', 'beam'))

    # determine the beam wise offsets
    bm_azimuth = np.rad2deg(np.arctan2(acrosstrack_stck,
                                       alongtrack_stck)) + np.float32(
                                           hdng[at_idx[0]].values)
    bm_radius = np.sqrt(acrosstrack_stck**2 + alongtrack_stck**2)
    pos = g.fwd(lon[at_idx[0]].values, lat[at_idx[0]].values,
                bm_azimuth.values, bm_radius.values)

    z = np.around(corr_dpth, 3)
    if vert_ref == 'NOAA MLLW':
        sep, vdatum_unc = transform_vyperdatum(
            pos[0],
            pos[1],
            xr.zeros_like(z),
            input_crs.to_epsg(),
            'mllw',
            vdatum_directory=vdatum_directory)
    elif vert_ref == 'NOAA MHW':
        sep, vdatum_unc = transform_vyperdatum(
            pos[0],
            pos[1],
            xr.zeros_like(z),
            input_crs.to_epsg(),
            'mhw',
            vdatum_directory=vdatum_directory)
    else:
        sep = 0
        vdatum_unc = xr.zeros_like(z)
    z = z - sep

    if horizontal_crs.is_projected:
        # Transformer.transform input order is based on the CRS, see CRS.geodetic_crs.axis_info
        # - lon, lat - this appears to be valid when using CRS from proj4 string
        # - lat, lon - this appears to be valid when using CRS from epsg
        # use the always_xy option to force the transform to expect lon/lat order
        georef_transformer = Transformer.from_crs(input_crs,
                                                  horizontal_crs,
                                                  always_xy=True)
        newpos = georef_transformer.transform(
            pos[0], pos[1], errcheck=True)  # longitude / latitude order (x/y)
    else:
        newpos = pos

    x = reform_nan_array(np.around(newpos[0], 3), at_idx, alongtrack.shape,
                         alongtrack.coords, alongtrack.dims)
    y = reform_nan_array(np.around(newpos[1], 3), ac_idx, acrosstrack.shape,
                         acrosstrack.coords, acrosstrack.dims)

    return [x, y, z, corr_heave, corr_altitude, vdatum_unc]
Exemple #6
0
    def soundings_plot_2d(self, mode: str = 'svcorr', color_by: str = 'depth', start_time: float = None, end_time: float = None):
        """
        Plots a 2d representation of the acrosstrack/depth values generated by sv correct.  If sector is
        provided, isolates that sector.  If a time is provided, isolates that time.

        Parameters
        ----------
        mode
            str, either 'svcorr' to plot the svcorrected offsets, or 'georef' to plot the georeferenced soundings
        color_by
            str, either 'depth' or 'sector'
        start_time
            start time in utc seconds, optional if you want to subset by time
        end_time
            end time in utc seconds, optional if you want to subset by time

        Returns
        -------
        plt.Figure
            matplotlib.pyplot.figure instance
        """

        if start_time is not None or start_time is not None:
            self.fqpr.subset_by_time(start_time, end_time)

        xvar, yvar, zvar = self._parse_plot_mode(mode)

        minz = self.fqpr.calc_min_var(zvar)
        maxz = self.fqpr.calc_max_var(zvar)
        miny = self.fqpr.calc_min_var(yvar)
        maxy = self.fqpr.calc_max_var(yvar)
        if mode == 'svcorr':  # svcorrected is alongtrack/acrosstrack in meters.  Want the scales to be equal so it doesnt look weird
            minx = miny
            maxx = maxy
        else:  # georeferenced is northing/easting, scales cant be equal of course
            minx = self.fqpr.calc_min_var(xvar)
            maxx = self.fqpr.calc_max_var(xvar)

        fig = plt.figure()

        for rp in self.fqpr.multibeam.raw_ping:
            x_idx, x_stck = stack_nan_array(rp[xvar], stack_dims=('time', 'beam'))
            y_idx, y_stck = stack_nan_array(rp[yvar], stack_dims=('time', 'beam'))
            z_idx, z_stck = stack_nan_array(rp[zvar], stack_dims=('time', 'beam'))

            if color_by == 'depth':
                plt.scatter(y_stck, x_stck, marker='+', c=z_stck, cmap='coolwarm', s=5)
                plt.clim(minz, maxz)
            elif color_by == 'sector':
                sector_vals = rp.txsector_beam.values[x_idx]
                plt.scatter(y_stck, x_stck, marker='+', c=sector_vals, s=5)
        plt.xlim(miny, maxy)
        plt.ylim(minx, maxx)
        if color_by != 'sector':
            plt.colorbar().set_label(zvar, rotation=270, labelpad=10)
        plt.title('{}: {}/{} colored by {}'.format(mode, xvar, yvar, color_by))

        if start_time is not None or start_time is not None:
            self.fqpr.restore_subset()

        return fig