Esempio n. 1
0
def xyz_to_spherical(xyz, alt=0, proj=None, ke=4.0 / 3.0):
    """Returns spherical representation (r, theta, phi) of given cartesian
    coordinates (x, y, z) with respect to the reference altitude (asl)
    considering earth's geometry (proj).

    Parameters
    ----------
    xyz : :class:`numpy:numpy.ndarray`
        Array of shape (..., 3). Contains cartesian coordinates.
    alt : float
        Altitude (in meters)
        defaults to 0.
    proj : osr object
        projection of the source coordinates (aeqd) with spheroid model
        defaults to None.
    ke : float
        Adjustment factor to account for the refractivity gradient that
        affects radar beam propagation. In principle this is wavelength-
        dependent. The default of 4/3 is a good approximation for most
        weather radar wavelengths

    Returns
    -------
    r : :class:`numpy:numpy.ndarray`
        Array of xyz.shape. Contains the radial distances.
    theta: :class:`numpy:numpy.ndarray`
        Array of xyz.shape. Contains the elevation angles.
    phi : :class:`numpy:numpy.ndarray`
        Array of xyz.shape. Contains the azimuthal angles.
    """

    # get the approximate radius of the projection's ellipsoid
    # for the latitude_of_center, if no projection is given assume
    # spherical earth
    try:
        lat0 = proj.GetProjParm("latitude_of_center")
        re = projection.get_earth_radius(lat0, proj)
    except Exception:
        re = 6370040.0

    # calculate xy-distance
    s = np.sqrt(np.sum(xyz[..., 0:2] ** 2, axis=-1))

    # calculate earth's arc angle
    gamma = s / (re * ke)

    # calculate elevation angle theta
    numer = np.cos(gamma) - (re * ke + alt) / (re * ke + xyz[..., 2])
    denom = np.sin(gamma)
    theta = np.arctan(numer / denom)

    # calculate radial distance r
    r = (re * ke + xyz[..., 2]) * denom / np.cos(theta)
    # another method using gamma only, but slower
    # keep it here for reference
    # f1 = (re * ke + xyz[..., 2])
    # f2 = (re * ke + alt)
    # r = np.sqrt(f1**2 + f2**2  - 2 * f1 * f2 * np.cos(gamma))

    # calculate azimuth angle phi
    phi = 90 - np.rad2deg(np.arctan2(xyz[..., 1], xyz[..., 0]))
    phi[phi <= 0] += 360

    return r, phi, np.degrees(theta)
Esempio n. 2
0
def spherical_to_xyz(r,
                     phi,
                     theta,
                     sitecoords,
                     re=None,
                     ke=4. / 3.,
                     squeeze=None,
                     strict_dims=False):
    """Transforms spherical coordinates (r, phi, theta) to cartesian
    coordinates (x, y, z) centered at sitecoords (aeqd).

    It takes the shortening of the great circle
    distance with increasing elevation angle as well as the resulting
    increase in height into account.

    Parameters
    ----------
    r : :class:`numpy:numpy.ndarray`
        Contains the radial distances in meters.
    phi : :class:`numpy:numpy.ndarray`
        Contains the azimuthal angles in degree.
    theta: :class:`numpy:numpy.ndarray`
        Contains the elevation angles in degree.
    sitecoords : a sequence of three floats
        the lon / lat coordinates of the radar location and its altitude
        a.m.s.l. (in meters)
        if sitecoords is of length two, altitude is assumed to be zero
    re : float
        earth's radius [m]
    ke : float
        adjustment factor to account for the refractivity gradient that
        affects radar beam propagation. In principle this is wavelength-
        dependent. The default of 4/3 is a good approximation for most
        weather radar wavelengths.
    squeeze : bool
        If True, returns squeezed array.
    strict_dims : bool
        If True, generates output of (theta, phi, r, 3) in any case.
        If False, dimensions with same length are "merged".

    Returns
    -------
    xyz : :class:`numpy:numpy.ndarray`
        Array of shape (..., 3). Contains cartesian coordinates.
    rad : osr object
        Destination Spatial Reference System (Projection).
        Defaults to wgs84 (epsg 4326).
    """
    # if site altitude is present, use it, else assume it to be zero
    try:
        centalt = sitecoords[2]
    except IndexError:
        centalt = 0.

    # if no radius is given, get the approximate radius of the WGS84
    # ellipsoid for the site's latitude
    if re is None:
        re = projection.get_earth_radius(sitecoords[1])
        # Set up aeqd-projection sitecoord-centered, wgs84 datum and ellipsoid
        # use world azimuthal equidistant projection
        projstr = ('+proj=aeqd +lon_0={lon:f} +x_0=0 +y_0=0 +lat_0={lat:f} ' +
                   '+ellps=WGS84 +datum=WGS84 +units=m +no_defs' + '').format(
                       lon=sitecoords[0], lat=sitecoords[1])

    else:
        # Set up aeqd-projection sitecoord-centered, assuming spherical earth
        # use Sphere azimuthal equidistant projection
        projstr = ('+proj=aeqd +lon_0={lon:f} +lat_0={lat:f} +a={a:f} '
                   '+b={b:f} +units=m +no_defs').format(lon=sitecoords[0],
                                                        lat=sitecoords[1],
                                                        a=re,
                                                        b=re)

    rad = projection.proj4_to_osr(projstr)

    r = np.asanyarray(r)
    theta = np.asanyarray(theta)
    phi = np.asanyarray(phi)

    if r.ndim:
        r = r.reshape((1, ) * (3 - r.ndim) + r.shape)

    if phi.ndim:
        phi = phi.reshape((1, ) + phi.shape + (1, ) * (2 - phi.ndim))

    if not theta.ndim:
        theta = np.broadcast_to(theta, phi.shape)

    dims = 3
    if not strict_dims:
        if phi.ndim and theta.ndim and (theta.shape[0] == phi.shape[1]):
            dims -= 1
        if r.ndim and theta.ndim and (theta.shape[0] == r.shape[2]):
            dims -= 1

    if theta.ndim and phi.ndim:
        theta = theta.reshape(theta.shape + (1, ) * (dims - theta.ndim))

    z = misc.bin_altitude(r, theta, centalt, re, ke=ke)
    dist = misc.site_distance(r, theta, z, re, ke=ke)

    if ((not strict_dims) and phi.ndim and r.ndim
            and (r.shape[2] == phi.shape[1])):
        z = np.squeeze(z)
        dist = np.squeeze(dist)
        phi = np.squeeze(phi)

    x = dist * np.cos(np.radians(90 - phi))
    y = dist * np.sin(np.radians(90 - phi))

    if z.ndim:
        z = np.broadcast_to(z, x.shape)

    xyz = np.stack((x, y, z), axis=-1)

    if xyz.ndim == 1:
        xyz.shape = (1, ) * 3 + xyz.shape
    elif xyz.ndim == 2:
        xyz.shape = (xyz.shape[0], ) + (1, ) * 2 + (xyz.shape[1], )

    if squeeze is None:
        warnings.warn(
            "Function `spherical_to_xyz` returns an array "
            "of shape (theta, phi, range, 3). Use `squeeze=True` "
            "to remove singleton dimensions."
            "", DeprecationWarning)
    if squeeze:
        xyz = np.squeeze(xyz)

    return xyz, rad