import numpy as np
from .rot import _warn_degree
from ..log import get_logger
logger = get_logger()
[docs]def cart2sph(pts_cart, convention='lat-lng'):
r"""Converts 3D Cartesian coordinates to spherical coordinates.
Args:
pts_cart (array_like): Cartesian :math:`x`, :math:`y` and
:math:`z`. Of shape N-by-3 or length 3 if just one point.
convention (str, optional): Convention for spherical coordinates:
``'lat-lng'`` or ``'theta-phi'``:
.. code-block:: none
lat-lng
^ z (lat = 90)
|
|
(lng = -90) ---------+---------> y (lng = 90)
,'|
,' |
(lat = 0, lng = 0) x | (lat = -90)
.. code-block:: none
theta-phi
^ z (theta = 0)
|
|
(phi = 270) ---------+---------> y (phi = 90)
,'|
,' |
(theta = 90, phi = 0) x | (theta = 180)
Returns:
numpy.ndarray: Spherical coordinates :math:`(r, \theta_1, \theta_2)`
in radians.
"""
pts_cart = np.array(pts_cart)
# Validate inputs
is_one_point = False
if pts_cart.shape == (3,):
is_one_point = True
pts_cart = pts_cart.reshape(1, 3)
elif pts_cart.ndim != 2 or pts_cart.shape[1] != 3:
raise ValueError("Shape of input must be either (3,) or (n, 3)")
# Compute r
r = np.sqrt(np.sum(np.square(pts_cart), axis=1))
# Compute latitude
z = pts_cart[:, 2]
lat = np.arcsin(z / r)
# Compute longitude
x = pts_cart[:, 0]
y = pts_cart[:, 1]
lng = np.arctan2(y, x) # choosing the quadrant correctly
# Assemble
pts_r_lat_lng = np.stack((r, lat, lng), axis=-1)
# Select output convention
if convention == 'lat-lng':
pts_sph = pts_r_lat_lng
elif convention == 'theta-phi':
pts_sph = _convert_sph_conventions(
pts_r_lat_lng, 'lat-lng_to_theta-phi')
else:
raise NotImplementedError(convention)
if is_one_point:
pts_sph = pts_sph.reshape(3)
return pts_sph
def _convert_sph_conventions(pts_r_angle1_angle2, what2what):
"""Internal function converting between different conventions for
spherical coordinates. See :func:`cart2sph` for conventions.
"""
if what2what == 'lat-lng_to_theta-phi':
pts_r_theta_phi = np.zeros(pts_r_angle1_angle2.shape)
# Radius is the same
pts_r_theta_phi[:, 0] = pts_r_angle1_angle2[:, 0]
# Angle 1
pts_r_theta_phi[:, 1] = np.pi / 2 - pts_r_angle1_angle2[:, 1]
# Angle 2
ind = pts_r_angle1_angle2[:, 2] < 0
pts_r_theta_phi[ind, 2] = 2 * np.pi + pts_r_angle1_angle2[ind, 2]
pts_r_theta_phi[np.logical_not(ind), 2] = \
pts_r_angle1_angle2[np.logical_not(ind), 2]
return pts_r_theta_phi
if what2what == 'theta-phi_to_lat-lng':
pts_r_lat_lng = np.zeros(pts_r_angle1_angle2.shape)
# Radius is the same
pts_r_lat_lng[:, 0] = pts_r_angle1_angle2[:, 0]
# Angle 1
pts_r_lat_lng[:, 1] = np.pi / 2 - pts_r_angle1_angle2[:, 1]
# Angle 2
ind = pts_r_angle1_angle2[:, 2] > np.pi
pts_r_lat_lng[ind, 2] = pts_r_angle1_angle2[ind, 2] - 2 * np.pi
pts_r_lat_lng[np.logical_not(ind), 2] = \
pts_r_angle1_angle2[np.logical_not(ind), 2]
return pts_r_lat_lng
raise NotImplementedError(what2what)
[docs]def sph2cart(pts_sph, convention='lat-lng'):
"""Inverse of :func:`cart2sph`.
See :func:`cart2sph`.
"""
pts_sph = np.array(pts_sph)
# Validate inputs
is_one_point = False
if pts_sph.shape == (3,):
is_one_point = True
pts_sph = pts_sph.reshape(1, 3)
elif pts_sph.ndim != 2 or pts_sph.shape[1] != 3:
raise ValueError("Shape of input must be either (3,) or (n, 3)")
# Degrees?
_warn_degree(pts_sph[:, 1:])
# Convert to latitude-longitude convention, if necessary
if convention == 'lat-lng':
pts_r_lat_lng = pts_sph
elif convention == 'theta-phi':
pts_r_lat_lng = _convert_sph_conventions(
pts_sph, 'theta-phi_to_lat-lng')
else:
raise NotImplementedError(convention)
# Compute x, y and z
r = pts_r_lat_lng[:, 0]
lat = pts_r_lat_lng[:, 1]
lng = pts_r_lat_lng[:, 2]
z = r * np.sin(lat)
x = r * np.cos(lat) * np.cos(lng)
y = r * np.cos(lat) * np.sin(lng)
# Assemble and return
pts_cart = np.stack((x, y, z), axis=-1)
if is_one_point:
pts_cart = pts_cart.reshape(3)
return pts_cart
[docs]def main(func_name):
"""Unit tests that can also serve as example usage."""
if func_name in ('sph2cart', 'cart2sph'):
# cart2sph() and sph2cart()
pts_car = np.array([
[-1, 2, 3],
[4, -5, 6],
[3, 5, -8],
[-2, -5, 2],
[4, -2, -23]])
print(pts_car)
pts_sph = cart2sph(pts_car)
print(pts_sph)
pts_car_recover = sph2cart(pts_sph)
print(pts_car_recover)
else:
raise NotImplementedError("Unit tests for %s" % func_name)
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('func', type=str, help="function to test")
args = parser.parse_args()
main(args.func)