Esempio n. 1
0
def mooring_array(od, Ymoor, Xmoor, **kwargs):
    """
    Extract a mooring array section following the grid.
    Trajectories are great circle paths if coordinates are spherical.

    Parameters
    ----------
    od: OceanDataset
        od that will be subsampled.
    Ymoor: 1D array_like, scalar
        Y coordinates of moorings.
    Xmoor: 1D array_like, scalar
        X coordinates of moorings.
    **kwargs:
        Keyword arguments for :py:func:`oceanspy.subsample.cutout`.

    Returns
    -------
    od: OceanDataset
        Subsampled oceandataset.
    """

    # Check
    _check_native_grid(od, 'mooring_array')

    # Convert variables to numpy arrays and make some check
    Ymoor = _check_range(od, Ymoor, 'Ymoor')
    Xmoor = _check_range(od, Xmoor, 'Xmoor')

    # Cutout
    if "YRange" not in kwargs:
        kwargs['YRange'] = Ymoor
    if "XRange" not in kwargs:
        kwargs['XRange'] = Xmoor
    if "add_Hbdr" not in kwargs:
        kwargs['add_Hbdr'] = True
    od = od.subsample.cutout(**kwargs)

    # Message
    print('Extracting mooring array.')

    # Unpack ds
    ds = od._ds

    # Useful variables
    YC = od._ds['YC']
    XC = od._ds['XC']
    R = od.parameters['rSphere']
    shape = XC.shape
    Yindex = XC.dims.index('Y')
    Xindex = XC.dims.index('X')

    # Convert to cartesian if spherical
    if R is not None:
        x, y, z = _utils.spherical2cartesian(Y=Ymoor, X=Xmoor, R=R)
    else:
        x = Xmoor
        y = Ymoor
        z = _np.zeros(Ymoor.shape)

    # Create tree
    tree = od.create_tree(grid_pos='C')

    # Indexes of nearest grid points
    _, indexes = tree.query(_np.column_stack((x, y, z)))
    indexes = _np.unravel_index(indexes, shape)
    iY = _np.ndarray.tolist(indexes[Yindex])
    iX = _np.ndarray.tolist(indexes[Xindex])

    # Remove duplicates
    diff_iY = _np.diff(iY)
    diff_iX = _np.diff(iX)
    to_rem = []
    for k, (diY, diX) in enumerate(zip(diff_iY, diff_iX)):
        if diY == 0 and diX == 0:
            to_rem = to_rem + [k]
    iY = _np.asarray([i for j, i in enumerate(iY) if j not in to_rem])
    iX = _np.asarray([i for j, i in enumerate(iX) if j not in to_rem])

    # Nearest coordinates
    near_Y = YC.isel(Y=_xr.DataArray(iY, dims=('tmp')),
                     X=_xr.DataArray(iX, dims=('tmp'))).values
    near_X = XC.isel(Y=_xr.DataArray(iY, dims=('tmp')),
                     X=_xr.DataArray(iX, dims=('tmp'))).values

    # Steps
    diff_iY = _np.fabs(_np.diff(iY))
    diff_iX = _np.fabs(_np.diff(iX))

    # Loop until all steps are 1
    while any(diff_iY + diff_iX != 1):

        # Find where need to add grid points
        k = _np.argwhere(diff_iY + diff_iX != 1)[0][0]
        lat0 = near_Y[k]
        lon0 = near_X[k]
        lat1 = near_Y[k + 1]
        lon1 = near_X[k + 1]

        # Find grid point in the middle
        if R is not None:
            # SPHERICAL: follow great circle path
            dist = _great_circle((lat0, lon0), (lat1, lon1), radius=R).km

            # Divide dist by 2.1 to make sure that returns 3 points
            dist = dist / 2.1
            this_Y, this_X, this_dists = _utils.great_circle_path(
                lat0, lon0, lat1, lon1, dist)

            # Cartesian coordinate of point in the middle
            x, y, z = _utils.spherical2cartesian(this_Y[1], this_X[1], R)
        else:
            # CARTESIAN: take the average
            x = (lon0 + lon1) / 2
            y = (lat0 + lat1) / 2
            z = 0

        # Indexes of 3 nearest grid point
        _, indexes = tree.query(_np.column_stack((x, y, z)), k=3)
        indexes = _np.unravel_index(indexes, shape)
        new_iY = _np.ndarray.tolist(indexes[Yindex])[0]
        new_iX = _np.ndarray.tolist(indexes[Xindex])[0]

        # Extract just one point
        to_rem = []
        for i, (this_iY, this_iX) in enumerate(zip(new_iY, new_iX)):
            check1 = (this_iY == iY[k] and this_iX == iX[k])
            check2 = (this_iY == iY[k + 1] and this_iX == iX[k + 1])
            if check1 or check2:
                to_rem = to_rem + [i]
        new_iY = _np.asarray(
            [i for j, i in enumerate(new_iY) if j not in to_rem])[0]
        new_iX = _np.asarray(
            [i for j, i in enumerate(new_iX) if j not in to_rem])[0]

        # Extract new lat and lon
        new_lat = YC.isel(Y=new_iY, X=new_iX).values
        new_lon = XC.isel(Y=new_iY, X=new_iX).values

        # Insert
        near_Y = _np.insert(near_Y, k + 1, new_lat)
        near_X = _np.insert(near_X, k + 1, new_lon)
        iY = _np.insert(iY, k + 1, new_iY)
        iX = _np.insert(iX, k + 1, new_iX)

        # Steps
        diff_iY = _np.fabs(_np.diff(iY))
        diff_iX = _np.fabs(_np.diff(iX))

    # New dimensions
    mooring = _xr.DataArray(_np.arange(len(iX)),
                            dims=('mooring'),
                            attrs={
                                'long_name': 'index of mooring',
                                'units': 'none'
                            })
    y = _xr.DataArray(_np.arange(1),
                      dims=('y'),
                      attrs={
                          'long_name': 'j-index of cell center',
                          'units': 'none'
                      })
    x = _xr.DataArray(_np.arange(1),
                      dims=('x'),
                      attrs={
                          'long_name': 'i-index of cell corner',
                          'units': 'none'
                      })
    yp1 = _xr.DataArray(_np.arange(2),
                        dims=('yp1'),
                        attrs={
                            'long_name': 'j-index of cell center',
                            'units': 'none'
                        })
    xp1 = _xr.DataArray(_np.arange(2),
                        dims=('xp1'),
                        attrs={
                            'long_name': 'i-index of cell corner',
                            'units': 'none'
                        })

    # Transform indexes in DataArray
    iy = _xr.DataArray(_np.reshape(iY, (len(mooring), len(y))),
                       coords={
                           'mooring': mooring,
                           'y': y
                       },
                       dims=('mooring', 'y'))
    ix = _xr.DataArray(_np.reshape(iX, (len(mooring), len(x))),
                       coords={
                           'mooring': mooring,
                           'x': x
                       },
                       dims=('mooring', 'x'))
    iyp1 = _xr.DataArray(_np.stack((iY, iY + 1), 1),
                         coords={
                             'mooring': mooring,
                             'yp1': yp1
                         },
                         dims=('mooring', 'yp1'))
    ixp1 = _xr.DataArray(_np.stack((iX, iX + 1), 1),
                         coords={
                             'mooring': mooring,
                             'xp1': xp1
                         },
                         dims=('mooring', 'xp1'))

    # Initialize new dataset
    new_ds = _xr.Dataset(
        {
            'mooring': mooring,
            'Y': y.rename(y='Y'),
            'Yp1': yp1.rename(yp1='Yp1'),
            'X': x.rename(x='X'),
            'Xp1': xp1.rename(xp1='Xp1')
        },
        attrs=ds.attrs)

    # Loop and take out (looping is faster than apply to the whole dataset)
    all_vars = {var: new_ds[var] for var in new_ds}
    for var in ds.variables:
        if var in ['X', 'Y', 'Xp1', 'Yp1']:
            da = new_ds[var]
            da.attrs.update({
                attr: ds[var].attrs[attr]
                for attr in ds[var].attrs
                if attr not in ['units', 'long_name']
            })
            continue
        elif not any(dim in ds[var].dims for dim in ['X', 'Y', 'Xp1', 'Yp1']):
            da = ds[var]
        else:
            for this_dims in [['Y', 'X'], ['Yp1', 'Xp1'], ['Y', 'Xp1'],
                              ['Yp1', 'X']]:
                if set(this_dims).issubset(ds[var].dims):
                    da = ds[var].isel({
                        dim: eval('i' + dim.lower(), {}, {
                            'iy': iy,
                            'ix': ix,
                            'iyp1': iyp1,
                            'ixp1': ixp1
                        })
                        for dim in this_dims
                    })
                    da = da.drop(this_dims).rename(
                        {dim.lower(): dim
                         for dim in this_dims})

        # Add to dictionary
        all_vars = {**all_vars, **{var: da}}

    new_ds = _xr.Dataset(all_vars)

    # Merge removes the attributes: put them back!
    new_ds.attrs = ds.attrs

    # Add distance
    dists = _np.zeros(near_Y.shape)
    for i in range(1, len(dists)):
        coord1 = (near_Y[i - 1], near_X[i - 1])
        coord2 = (near_Y[i], near_X[i])

        if R is not None:
            # SPHERICAL
            dists[i] = _great_circle(coord1, coord2, radius=R).km
        else:
            # CARTESIAN
            dists[i] = _np.sqrt((coord2[0] - coord1[0])**2 +
                                (coord2[1] - coord1[1])**2)

    dists = _np.cumsum(dists)
    distance = _xr.DataArray(
        dists,
        coords={'mooring': mooring},
        dims=('mooring'),
        attrs={'long_name': 'Distance from first mooring'})

    if R is not None:
        # SPHERICAL
        distance.attrs['units'] = 'km'
    else:
        # CARTESIAN
        if 'units' in XC.attrs:
            distance.attrs['units'] = XC.attrs['units']
    new_ds['mooring_dist'] = distance

    # Reset coordinates
    new_ds = new_ds.set_coords([coord
                                for coord in ds.coords] + ['mooring_dist'])

    # Recreate od
    od._ds = new_ds
    od = od.set_grid_coords({'mooring': {
        'mooring': -0.5
    }},
                            add_midp=True,
                            overwrite=False)

    # Create dist_midp
    _grid = od._grid
    dist_midp = _xr.DataArray(_grid.interp(od._ds['mooring_dist'], 'mooring'),
                              attrs=od._ds['mooring_dist'].attrs)
    od = od.merge_into_oceandataset(dist_midp.rename('mooring_midp_dist'))
    od._ds = od._ds.set_coords([coord for coord in od._ds.coords] +
                               ['mooring_midp_dist'])

    return od
Esempio n. 2
0
def great_circle_path(lat1, lon1, lat2, lon2, delta_km=None, R=None):
    """
    Generate a great circle trajectory specifying the distance resolution.

    Parameters
    ----------
    lat1: scalar
        Latitude of vertex 1 [degrees N]
    lon1: scalar
        Longitude of vertex 1 [degrees E]
    lat2: scalar
        Latitude of vertex 2 [degrees N]
    lon2: scalar
        Longitude of vertex 2 [degrees E]
    delta_km: scalar, None
        Distance resolution [km]
        If None, only use vertices and return distance
    R: scalar, None
        Earth radius in km
        If None, use geopy default

    Returns
    -------
    lats: 1D numpy.ndarray
        Great circle latitudes [degrees N]
    lons: 1D numpy.ndarray
        Great circle longitudes [degrees E]
    dist: 1D numpy.ndarray
        Distances from vertex 1 [km]

    References
    ----------
    Converted to python and adapted from:
    `<https://ww2.mathworks.cn/matlabcentral/mlc-downloads/downloads/
    submissions/8493/versions/2/previews/generate_great_circle_path.m
    /index.html?access_key=>`_
    """

    # Check parameters
    _check_instance(
        {
            "lat1": lat1,
            "lon1": lon1,
            "lat2": lat2,
            "lon2": lon2,
            "delta_km": delta_km,
            "R": R,
        },
        {
            "lat1": "numpy.ScalarType",
            "lon1": "numpy.ScalarType",
            "lat2": "numpy.ScalarType",
            "lon2": "numpy.ScalarType",
            "delta_km": ["type(None)", "numpy.ScalarType"],
            "R": ["type(None)", "numpy.ScalarType"],
        },
    )

    # Check parameters
    if lat1 == lat2 and lon1 == lon2:
        raise ValueError("Vertexes are overlapping")
    if delta_km is not None and delta_km <= 0:
        raise ValueError("`delta_km` can not be zero or negative")

    # Check parameters
    if R is None:
        from geopy.distance import EARTH_RADIUS

        R = EARTH_RADIUS

    if delta_km is not None:
        # Convert to radians
        lat1 = _np.deg2rad(lat1)
        lon1 = _np.deg2rad(lon1)
        lat2 = _np.deg2rad(lat2)
        lon2 = _np.deg2rad(lon2)

        # Using the right-handed coordinate frame
        # with Z toward (lat,lon)=(0,0) and
        # Y toward (lat,lon)=(90,0),
        # the unit_radial of a (lat,lon) is given by:
        #               [ cos(lat)*sin(lon) ]
        # unit_radial = [     sin(lat)      ]
        #               [ cos(lat)*cos(lon) ]
        unit_radial_1 = _np.array([
            _np.cos(lat1) * _np.sin(lon1),
            _np.sin(lat1),
            _np.cos(lat1) * _np.cos(lon1),
        ])
        unit_radial_2 = _np.array([
            _np.cos(lat2) * _np.sin(lon2),
            _np.sin(lat2),
            _np.cos(lat2) * _np.cos(lon2),
        ])

        # Define the vector that is normal to
        # both unit_radial_1 & unit_radial_2
        normal_vec = _np.cross(unit_radial_1, unit_radial_2)
        unit_normal = normal_vec / _np.sqrt(_np.sum(normal_vec**2))

        # Define the vector that is tangent to the great circle flight path at
        # (lat1,lon1)
        tangent_1_vec = _np.cross(unit_normal, unit_radial_1)
        unit_tangent_1 = tangent_1_vec / _np.sqrt(_np.sum(tangent_1_vec**2))

        # Determine the total arc distance from 1 to 2 (radians)
        total_arc_angle_1_to_2 = _np.arccos(unit_radial_1.dot(unit_radial_2))

        # Determine the set of arc angles to use
        # (approximately spaced by delta_km)
        angs2use = _np.linspace(
            0,
            total_arc_angle_1_to_2,
            int(_np.ceil(total_arc_angle_1_to_2 / (delta_km / R))),
        )  # radians
        M = angs2use.size

        # unit_radials is a 3xM array of M unit radials
        term1 = _np.array([unit_radial_1]).transpose().dot(_np.ones((1, M)))
        term2 = _np.ones((3, 1)).dot(_np.array([_np.cos(angs2use)]))
        term3 = _np.array([unit_tangent_1]).transpose().dot(_np.ones((1, M)))
        term4 = _np.ones((3, 1)).dot(_np.array([_np.sin(angs2use)]))
        unit_radials = term1 * term2 + term3 * term4

        # Convert to latitudes and longitudes
        lats = _np.rad2deg(_np.arcsin(unit_radials[1, :]))
        lons = _np.rad2deg(_np.arctan2(unit_radials[0, :], unit_radials[2, :]))

    else:
        # Use input lon and lat
        lats = _np.concatenate((_np.reshape(lat1, 1), _np.reshape(lat2, 1)))
        lons = _np.concatenate((_np.reshape(lon1, 1), _np.reshape(lon2, 1)))

    # Compute distance
    dists = _np.zeros(lons.shape)
    for i in range(1, len(lons)):
        coord1 = (lats[i - 1], lons[i - 1])
        coord2 = (lats[i], lons[i])
        dists[i] = _great_circle(coord1, coord2, radius=R).km
    dists = _np.cumsum(dists)

    return lats, lons, dists
Esempio n. 3
0
def great_circle_path(lat1, lon1, lat2, lon2, delta_km=None, R=None):
    """
    Generate a great circle trajectory specifying the distance resolution.
    
    Parameters
    ----------
    lat1: scalar
        Latitude of vertex 1 [degrees N]
    lon1: scalar
        Longitude of vertex 1 [degrees E]
    lat2: scalar
        Latitude of vertex 2 [degrees N]
    lon2: scalar
        Longitude of vertex 2 [degrees E]
    delta_km: scalar, None
        Distance resolution [km]
        If None, only use vertices and return distance
    R: scalar
        Earth radius in km
        If None, use geopy default
        
    Returns
    -------
    lats: 1D numpy.ndarray
        Great circle latitudes [degrees N]
    lons: 1D numpy.ndarray
        Great circle longitudes [degrees E]
    dist: 1D numpy.ndarray
        Distances from vertex 1 [km]
        
    References
    ----------
    Converted to python and adapted from: https://ww2.mathworks.cn/matlabcentral/mlc-downloads/downloads/submissions/8493/versions/2/previews/generate_great_circle_path.m/index.html?access_key=
    """

    # Check parameters
    if not isinstance(lat1, _np.ScalarType):
        raise TypeError('`lat1` must be scalar')
    if not isinstance(lon1, _np.ScalarType):
        raise TypeError('`lon1` must be scalar')
    if not isinstance(lat2, _np.ScalarType):
        raise TypeError('`lat2` must be scalar')
    if not isinstance(lon2, _np.ScalarType):
        raise TypeError('`lon2` must be scalar')
    if not isinstance(delta_km, (type(None), _np.ScalarType)):
        raise TypeError('`delta_km` must be scalar or None')
    if not isinstance(R, (_np.ScalarType, type(None))):
        raise TypeError('`R` must be scalar or None')
    if lat1 == lat2 and lon1 == lon2:
        raise TypeError('Vertexes are overlapping')
    if delta_km is not None and delta_km <= 0:
        raise TypeError('`delta_km` can not be zero or negative')

    # Check parameters
    if R is None:
        from geopy.distance import EARTH_RADIUS
        R = EARTH_RADIUS

    # Import packages
    from geopy.distance import great_circle as _great_circle

    if delta_km is not None:
        # Convert to radians
        lat1 = _np.deg2rad(lat1)
        lon1 = _np.deg2rad(lon1)
        lat2 = _np.deg2rad(lat2)
        lon2 = _np.deg2rad(lon2)

        # Using the right-handed coordinate frame with Z toward (lat,lon)=(0,0) and
        # Y toward (lat,lon)=(90,0), the unit_radial of a (lat,lon) is given by:
        #
        #               [ cos(lat)*sin(lon) ]
        # unit_radial = [     sin(lat)      ]
        #               [ cos(lat)*cos(lon) ]
        unit_radial_1 = _np.array([
            _np.cos(lat1) * _np.sin(lon1),
            _np.sin(lat1),
            _np.cos(lat1) * _np.cos(lon1)
        ])
        unit_radial_2 = _np.array([
            _np.cos(lat2) * _np.sin(lon2),
            _np.sin(lat2),
            _np.cos(lat2) * _np.cos(lon2)
        ])

        # Define the vector that is normal to both unit_radial_1 & unit_radial_2
        normal_vec = _np.cross(unit_radial_1, unit_radial_2)
        unit_normal = normal_vec / _np.sqrt(_np.sum(normal_vec**2))

        # Define the vector that is tangent to the great circle flight path at
        # (lat1,lon1)
        tangent_1_vec = _np.cross(unit_normal, unit_radial_1)
        unit_tangent_1 = tangent_1_vec / _np.sqrt(_np.sum(tangent_1_vec**2))

        # Determine the total arc distance from 1 to 2
        total_arc_angle_1_to_2 = _np.arccos(
            unit_radial_1.dot(unit_radial_2))  # radians

        # Determine the set of arc angles to use
        # (approximately spaced by delta_km)
        angs2use = _np.linspace(
            0, total_arc_angle_1_to_2,
            int(_np.ceil(total_arc_angle_1_to_2 / (delta_km / R))))
        # radians
        M = angs2use.size

        # Now find the unit radials of the entire "trajectory"
        #                                                              [ cos(angs2use(m)) -sin(angs2use(m)) 0 ]   [ 1 ]
        # unit_radial_m = [unit_radial_1 unit_tangent_1 unit_normal] * [ sin(angs2use(m))  cos(angs2use(m)) 0 ] * [ 0 ]
        #                                                              [        0                 0         1 ]   [ 0 ]
        # equals
        #                                                              [ cos(angs2use(m)) ]
        # unit_radial_m = [unit_radial_1 unit_tangent_1 unit_normal] * [ sin(angs2use(m)) ]
        #                                                              [        0         ]
        # equals
        #
        # unit_radial_m = [unit_radial_1*cos(angs2use(m)) + unit_tangent_1*sin(angs2use(m)) + 0]
        #
        # unit_radials is a 3xM array of M unit radials
        unit_radials = _np.array([unit_radial_1]).transpose().dot(_np.ones((1,M))) *\
                       _np.ones((3,1)).dot(_np.array([_np.cos(angs2use)])) +\
                       _np.array([unit_tangent_1]).transpose().dot(_np.ones((1,M))) *\
                       _np.ones((3,1)).dot(_np.array([_np.sin(angs2use)]))

        # Convert to latitudes and longitudes
        lats = _np.rad2deg(_np.arcsin(unit_radials[1, :]))
        lons = _np.rad2deg(_np.arctan2(unit_radials[0, :], unit_radials[2, :]))

    else:

        # Use input lon and lat
        lats = _np.concatenate((_np.reshape(lat1, 1), _np.reshape(lat2, 1)))
        lons = _np.concatenate((_np.reshape(lon1, 1), _np.reshape(lon2, 1)))

    # Compute distance
    dists = _np.zeros(lons.shape)
    for i in range(1, len(lons)):
        coord1 = (lats[i - 1], lons[i - 1])
        coord2 = (lats[i], lons[i])
        dists[i] = _great_circle(coord1, coord2, radius=R).km
    dists = _np.cumsum(dists)

    return lats, lons, dists
Esempio n. 4
0
def mooring_array(ds,
                  info,
                  lats,  
                  lons,
                  varList    = None,
                  depthRange = None,
                  timeRange  = None,
                  timeFreq   = None,
                  sampMethod = 'snapshot',
                  deep_copy  = False):
    
    """
    Extract a mooring array section avoiding interpolation.
    Sections are obtained following the grid.
    Every grid cell can have multiple moorings: 
    1 at the center (e.g., for Temp and S);
    2 at the meridional boundaries (e.g., for V). Dimension Yv=['Yb', 'Yf']
    2 at the zonal boundaries (e.g., for U). Dimension Xu=['Xb', 'Xf']
    4 at the corners (e.g., for momVort3). Dimension XYg=['Xb Yb', 'Xb Yf', 'Xf Yb', 'Xf Yf']
    
    Parameters
    ----------
    ds: xarray.Dataset
    info: oceanspy.open_dataset._info
    lats: list
        Latitudes of moorings [degrees N]. 
    lons: list
        Longitudes of moorings [degrees E]
    varList: list or None
        List of variables
    depthRange: list or None
        Depth limits.  (e.g, [0, -float('Inf')])  
    timeRange: list or None
        Time limits.   (e.g, ['2007-09-01T00', '2008-09-01T00'])
    timeFreq: str or None
        Time frequency. Available options are pandas Offset Aliases (e.g., '6H'):
        http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
    sampMethod: {'snapshot', 'mean'}
        Downsampling method (only if timeFreq is not None)
    deep_copy: bool
        If True, deep copy ds and info

    Returns
    -------
    ds: xarray.Dataset
    info: open_dataset._info
    """      
    
    # Deep copy
    if deep_copy: ds, info = _utils.deep_copy(ds, info)
        
    # Message
    print('Extracting mooring array')
    
    # Cutout
    ds, info = cutout(ds,
                      info,
                      varList    = varList,
                      latRange   = [min(lats), max(lats)],
                      lonRange   = [min(lons), max(lons)],
                      depthRange = depthRange,
                      timeRange  = timeRange,
                      timeFreq   = timeFreq,
                      sampMethod = sampMethod)
    
    # Add missing variables
    varList = ['X', 'Y', 'Xp1', 'Yp1']
    ds, info = _utils.compute_missing_variables(ds, info, varList)
    
    # Define variables
    X   = ds['X'];   Y   = ds['Y'];
    Xp1 = ds['Xp1']; Yp1 = ds['Yp1']
    
    # Find nearest coordinates
    near_lons = [X.sel(X=this_lons, method='nearest').values for this_lons in lons]
    near_lats = [Y.sel(Y=this_lats, method='nearest').values for this_lats in lats]

    # Remove duplicates
    diff_lons = _np.diff(near_lons); diff_lats = _np.diff(near_lats)
    to_rem = []
    for k, (dlon, dlat) in enumerate(zip(diff_lons, diff_lats)):
        if dlon==0 and dlat==0: to_rem = to_rem + [k]
    near_lons = [i for j, i in enumerate(near_lons) if j not in to_rem]
    near_lats = [i for j, i in enumerate(near_lats) if j not in to_rem]

    # Add points using linear fit
    # Could it generate infinite loops?
    k=0
    diff_lons = _np.diff(near_lons); diff_lats = _np.diff(near_lats)
    while k!=len(diff_lons)-1:
        K = k
        diff_lons = _np.diff(near_lons); diff_lats = _np.diff(near_lats)
        for k, (dlon, dlat) in enumerate(zip(diff_lons, diff_lats)):
            if k<K: continue
                
            # Additional points
            more_lons= X.where(_xr.ufuncs.logical_and(X>=_np.min(near_lons[k:k+2]),
                                                      X<=_np.max(near_lons[k:k+2])),
                                     drop=True).values
            more_lats= Y.where(_xr.ufuncs.logical_and(Y>=_np.min(near_lats[k:k+2]),
                                                      Y<=_np.max(near_lats[k:k+2])),
                                    drop=True).values
            
            # Sort
            if dlon<0: more_lons = -_np.sort(-more_lons)
            if dlat<0: more_lats = -_np.sort(-more_lats)
            
            if dlon!=0 and dlat!=0:
                if len(more_lons)>2 or len(more_lats)>2:
                    x0=more_lons[0]; x1=more_lons[-1]
                    y0=more_lats[0]; y1=more_lats[-1]
                    if len(more_lats)>len(more_lons):
                        new_lons=(more_lats-y0)*(x1-x0)/(y1-y0)+x0
                        new_lats=more_lats
                    else:
                        new_lons=more_lons
                        new_lats=(more_lons-x0)*(y1-y0)/(x1-x0)+y0

                    # Find nearest coordinates
                    new_lons = [X.sel(X=this_lons, method='nearest').values 
                                for this_lons in new_lons][1:-1]
                    new_lats = [Y.sel(Y=this_lats, method='nearest').values 
                                for this_lats in new_lats][1:-1]

                    # Remove duplicates
                    diff_new_lons = _np.diff(new_lons); diff_new_lats = _np.diff(new_lats)
                    to_rem = []
                    for kk, (dnlon, dnlat) in enumerate(zip(diff_new_lons, diff_new_lats)):
                        if dnlon==0 and dnlat==0: to_rem = to_rem + [kk]
                    new_lons = [i for j, i in enumerate(new_lons) if j not in to_rem]
                    new_lats = [i for j, i in enumerate(new_lats) if j not in to_rem]

                    # Insert
                    near_lons = _np.insert(near_lons,k+1,new_lons)
                    near_lats = _np.insert(near_lats,k+1,new_lats)
                    diff_lons = _np.diff(near_lons); diff_lats = _np.diff(near_lats)
                    break      
                

    # Add points where there is a gap
    diff_lons = _np.diff(near_lons); diff_lats = _np.diff(near_lats)
    add_lons = []
    add_lats = []
    for k, (dlon, dlat) in enumerate(zip(diff_lons, diff_lats)):
        add_lons = add_lons + [float(near_lons[k])]
        add_lats = add_lats + [float(near_lats[k])]

        # Additional points
        more_lons= X.where(_xr.ufuncs.logical_and(X>=_np.min(near_lons[k:k+2]),
                                                  X<=_np.max(near_lons[k:k+2])),
                                 drop=True).values
        more_lats= Y.where(_xr.ufuncs.logical_and(Y>=_np.min(near_lats[k:k+2]),
                                                        Y<=_np.max(near_lats[k:k+2])),
                                drop=True).values

        # Sort
        if dlon<0: more_lons = -_np.sort(-more_lons)
        if dlat<0: more_lats = -_np.sort(-more_lats)

        # Meridional and Zonal
        if   len(more_lons)==1 and len(more_lats)>2: # Meridional
            for _, thislat in enumerate(more_lats[1:-1]):
                add_lons = add_lons + [float(more_lons)]
                add_lats = add_lats + [float(thislat)]
        elif len(more_lats)==1 and len(more_lons)>2: # Zonal
            for _, thislon in enumerate(more_lons[1:-1]):
                add_lons = add_lons + [float(thislon)]
                add_lats = add_lats + [float(more_lats)]

        # Diagonal
        if dlon!=0 and dlat!=0:
            if len(more_lons)==2 and len(more_lats)==2: 
                x_diff = near_lons[k+1] - near_lons[k]
                y_diff = near_lats[k+1] - near_lats[k]
                den = _np.sqrt(y_diff**2 + x_diff**2)

                dist1 = _np.fabs(y_diff*near_lons[k+1] - 
                                 x_diff*near_lats[k] + 
                                 near_lons[k+1]*near_lats[k] - 
                                 near_lats[k+1]*near_lons[k])/den
                dist2 = _np.fabs(y_diff*near_lons[k] - 
                                 x_diff*near_lats[k+1] + 
                                 near_lons[k+1]*near_lats[k] - 
                                 near_lats[k+1]*near_lons[k])/den
                if dist1<dist2:
                    add_lons = add_lons + [float(near_lons[k+1])]
                    add_lats = add_lats + [float(near_lats[k])]
                else:
                    add_lons = add_lons + [float(near_lons[k])]
                    add_lats = add_lats + [float(near_lats[k+1])]
            else: raise ValueError('There is a bug: At this point of the function the gap should be smaller!')
    
    # Add
    add_lons = add_lons + [float(near_lons[k+1])]
    add_lats = add_lats + [float(near_lats[k+1])]
            
    # Create dimensions
    Xc  = _np.asarray(add_lons)
    Yc  = _np.asarray(add_lats)
    Xb  = _np.asarray([Xp1.sel(Xp1=this_lons, method='bfill').values for this_lons in Xc])
    Xf  = _np.asarray([Xp1.sel(Xp1=this_lons, method='ffill').values for this_lons in Xc])
    Yb  = _np.asarray([Yp1.sel(Yp1=this_lats, method='bfill').values for this_lats in Yc])
    Yf  = _np.asarray([Yp1.sel(Yp1=this_lats, method='ffill').values for this_lats in Yc])
    cell = _np.arange(len(Xc))
    Xu  = ['Xb', 'Xf']
    Yv  = ['Yb', 'Yf']
    XYg = ['Xb Yb', 'Xb Yf', 'Xf Yb', 'Xf Yf']
    
    # Create DataArrays
    cell = _xr.DataArray(cell, coords={'cell': cell}, dims=('cell'), 
                        attrs={'long_name': 'cells where moorings are located (adjacent cells have consecutive numbers)'})
    Xu  = _xr.DataArray(Xu,  coords={'Xu': Xu}, dims=('Xu'), 
                        attrs={'long_name': 'X coordinates of moorings on cell boundaries'})
    Yv  = _xr.DataArray(Yv,  coords={'Yv': Yv}, dims=('Yv'), 
                        attrs={'long_name': 'Y coordinates of moorings on cell boundaries'})
    XYg = _xr.DataArray(XYg, coords={'XYg': XYg}, dims=('XYg'), 
                        attrs={'long_name': 'XY coordinates of moorings on cell corners'})
    Xc  = _xr.DataArray(Xc,  coords={'cell': cell}, dims=('cell'), 
                        attrs={'long_name': 'longitude of moorings on cell center',
                               'units': 'degrees_east'})
    Yc  = _xr.DataArray(Yc,  coords={'cell': cell}, dims=('cell'),
                        attrs={'long_name': 'latitude of moorings on cell center',
                               'units': 'degrees_north'})
    Xb  = _xr.DataArray(Xb,  coords={'cell': cell}, dims=('cell'), 
                        attrs={'long_name': 'longitude of moorings on cell boundaries (bfill of Xc)',
                               'units': 'degrees_east'})
    Xf  = _xr.DataArray(Xf,  coords={'cell': cell}, dims=('cell'), 
                        attrs={'long_name': 'longitude of moorings on cell boundaries (ffill of Xc)',
                               'units': 'degrees_east'})
    Yb  = _xr.DataArray(Yb,  coords={'cell': cell}, dims=('cell'), 
                        attrs={'long_name': 'latitude of moorings on cell boundaries (bfill of Yc)',
                               'units': 'degrees_north'})
    Yf  = _xr.DataArray(Yf,  coords={'cell': cell}, dims=('cell'),
                        attrs={'long_name': 'latitude of moorings on cell boundaries (ffill of Yc)',
                               'units': 'degrees_north'})
    
    # Add to dataset
    coords = _xr.Dataset({'cell': cell, 'Xu': Xu, 'Yv': Yv, 'XYg': XYg,
                         'Xc': Xc, 'Yc': Yc, 'Xb': Xb, 'Xf': Xf, 'Yb': Yb, 'Yf': Yf})
    ds = _xr.merge([ds, coords])
    
    # Extract paths
    for var in [var for var in ds.variables if var not in ds.dims]:
        if all(dim in ds[var].dims for dim in ['X', 'Y']):
            ds[var] = ds[var].sel(X = Xc,
                                  Y = Yc)
        elif all(dim in ds[var].dims for dim in ['Xp1', 'Y']):
            var_x0  = ds[var].sel(Xp1 = Xb,
                                  Y   = Yc).expand_dims('Xu')
            var_x1  = ds[var].sel(Xp1 = Xf,
                                   Y  = Yc).expand_dims('Xu')
            ds[var] = _xr.concat([var_x0, var_x1],dim='Xu')
        elif all(dim in ds[var].dims for dim in ['X', 'Yp1']):
            var_y0  = ds[var].sel(X   = Xc,
                                  Yp1 = Yb).expand_dims('Yv')
            var_y1  = ds[var].sel(X   = Xc,
                                  Yp1 = Yf).expand_dims('Yv')
            ds[var] = _xr.concat([var_y0, var_y1],dim='Yv')
        elif all(dim in ds[var].dims for dim in ['Xp1', 'Yp1']):
            var_x0_y0 = ds[var].sel(Xp1 = Xb,
                                    Yp1 = Yb).expand_dims('XYg')
            var_x1_y0 = ds[var].sel(Xp1 = Xb,
                                    Yp1 = Yf).expand_dims('XYg')
            var_x0_y1 = ds[var].sel(Xp1 = Xf,
                                    Yp1 = Yb).expand_dims('XYg')
            var_x1_y1 = ds[var].sel(Xp1 = Xf,
                                    Yp1 = Yf).expand_dims('XYg')
            ds[var]   = _xr.concat([var_x0_y0, var_x1_y0, var_x0_y1, var_x1_y1],dim='XYg')
        elif 'Xp1' in ds[var].dims:
            var_x0  = ds[var].isel(Xp1 = Xb).expand_dims('Xu')
            var_x1  = ds[var].isel(Xp1 = Xf).expand_dims('Xu')
            ds[var] = _xr.concat([var_x0, var_x1],dim='Xu') 
        elif 'Yp1' in ds[var].dims:
            var_y0  = ds[var].isel(Yp1 = Yb).expand_dims('Yv')
            var_y1  = ds[var].isel(Yp1 = Yf).expand_dims('Yv')
            ds[var] = _xr.concat([var_y0, var_y1],dim='Yv') 

    # Drop old variables
    for var in ds.variables:
        if var in ['X', 'Y', 'Xp1', 'Yp1']: ds = ds.drop(var)
    for dim in ds.dims:
        if dim in ['X', 'Y', 'Xp1', 'Yp1']: ds = ds.drop(dim)  
    
    # Add distances
    from geopy.distance import great_circle as _great_circle
    dist = _np.zeros(Xc.shape)
    for i in range(1,len(Xc)):
        coord1   = (Yc[i-1],Xc[i-1])
        coord2   = (Yc[i]  ,Xc[i])
        dist[i] = _great_circle(coord1,coord2).km
    ds['dist_array']  = _xr.DataArray(_np.cumsum(dist), coords={'cell': cell}, dims=('cell'),
                                      attrs={'long_name': 'Cumulative distance from mooring 0',
                                             'units': 'km'})
    
    # Update info
    info.grid    = 'Mooring array'
    for name in ['cell', 'Xu', 'Yv', 'XYg', 'Xc', 'Yc', 'Xb', 'Xf', 'Yb', 'Yf', 'dist_array']: info.var_names[name] = name
    
    return _utils.reorder_ds(ds), info
Esempio n. 5
0
def great_circle_path(lat1, lon1, lat2, lon2, delta_km):
    """
    Generate a great circle trajectory specifying the resolution.
    
    Parameters
    ----------
    lat1: number
        Latitude of vertex 1 [degrees N]
    lon1: number
        Longitude of vertex 1 [degrees E]
    lat2: number
        Latitude of vertex 2 [degrees N]
    lon2: number
        Longitude of vertex 2 [degrees E]
    delta_km: number
        Horizontal resolution [km]
        
    Returns
    -------
    lats: vector
        Great circle latitudes [degrees N]
    lons: vector
        Great circle longitudes [degrees E]
    dist: vector
        Distances from vertex 1 [km]
    """
    from geopy.distance import great_circle as _great_circle
    from geopy.distance import EARTH_RADIUS as _EARTH_RADIUS

    # Convert to radians
    lat1 = _np.deg2rad(lat1)
    lon1 = _np.deg2rad(lon1)
    lat2 = _np.deg2rad(lat2)
    lon2 = _np.deg2rad(lon2)

    # Using the right-handed coordinate frame with Z toward (lat,lon)=(0,0) and
    # Y toward (lat,lon)=(90,0), the unit_radial of a (lat,lon) is given by:
    #
    #               [ cos(lat)*sin(lon) ]
    # unit_radial = [     sin(lat)      ]
    #               [ cos(lat)*cos(lon) ]
    unit_radial_1 = _np.array([
        _np.cos(lat1) * _np.sin(lon1),
        _np.sin(lat1),
        _np.cos(lat1) * _np.cos(lon1)
    ])
    unit_radial_2 = _np.array([
        _np.cos(lat2) * _np.sin(lon2),
        _np.sin(lat2),
        _np.cos(lat2) * _np.cos(lon2)
    ])

    # Define the vector that is normal to both unit_radial_1 & unit_radial_2
    normal_vec = _np.cross(unit_radial_1, unit_radial_2)
    unit_normal = normal_vec / _np.sqrt(_np.sum(normal_vec**2))

    # Define the vector that is tangent to the great circle flight path at
    # (lat1,lon1)
    tangent_1_vec = _np.cross(unit_normal, unit_radial_1)
    unit_tangent_1 = tangent_1_vec / _np.sqrt(_np.sum(tangent_1_vec**2))

    # Determine the total arc distance from 1 to 2
    total_arc_angle_1_to_2 = _np.arccos(
        unit_radial_1.dot(unit_radial_2))  # radians

    # Determine the set of arc angles to use
    # (approximately spaced by delta_km)
    R0 = _EARTH_RADIUS
    # km, radius of a circle having approximately the surface area of the earth
    angs2use = _np.linspace(0, total_arc_angle_1_to_2,
                            _np.ceil(total_arc_angle_1_to_2 / (delta_km / R0)))
    # radians
    M = angs2use.size

    # Now find the unit radials of the entire "trajectory"
    #                                                              [ cos(angs2use(m)) -sin(angs2use(m)) 0 ]   [ 1 ]
    # unit_radial_m = [unit_radial_1 unit_tangent_1 unit_normal] * [ sin(angs2use(m))  cos(angs2use(m)) 0 ] * [ 0 ]
    #                                                              [        0                 0         1 ]   [ 0 ]
    # equals
    #                                                              [ cos(angs2use(m)) ]
    # unit_radial_m = [unit_radial_1 unit_tangent_1 unit_normal] * [ sin(angs2use(m)) ]
    #                                                              [        0         ]
    # equals
    #
    # unit_radial_m = [unit_radial_1*cos(angs2use(m)) + unit_tangent_1*sin(angs2use(m)) + 0]
    #
    # unit_radials is a 3xM array of M unit radials
    unit_radials = _np.array([unit_radial_1]).transpose().dot(_np.ones((1,M))) *\
                   _np.ones((3,1)).dot(_np.array([_np.cos(angs2use)])) +\
                   _np.array([unit_tangent_1]).transpose().dot(_np.ones((1,M))) *\
                   _np.ones((3,1)).dot(_np.array([_np.sin(angs2use)]))

    # Convert to latitudes and longitudes
    lats = _np.rad2deg(_np.arcsin(unit_radials[1, :]))
    lons = _np.rad2deg(_np.arctan2(unit_radials[0, :], unit_radials[2, :]))

    # Compute distance
    dists = _np.zeros(lons.shape)
    for i in range(1, len(lons)):
        coord1 = (lats[i - 1], lons[i - 1])
        coord2 = (lats[i], lons[i])
        dists[i] = _great_circle(coord1, coord2).km
    dists = _np.cumsum(dists)

    return lats, lons, dists