Skip to content

Instantly share code, notes, and snippets.

@telegraphic
Created December 29, 2015 21:04
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save telegraphic/841212e8ab3252f5cffe to your computer and use it in GitHub Desktop.
Save telegraphic/841212e8ab3252f5cffe to your computer and use it in GitHub Desktop.
Conversion between (theta, phi) and (azimuth, elevation) coordinate systems
"""
# spherical_coords.py
Convert between (theta, phi) and (azimuth, elevation) coordinate systems.
Author: Danny Price
License: MIT
## Overview
Both (theta, phi) and (azimuth, elevation) are spherical coordinate systems.
The first is used commonly in antenna pattern measurements, while the second
is used extensively in ground-based astronomy. Conversion between the two
essentially requires moving the location of the pole from one axis to another.
In (theta, phi) coordinates, phi is the angle from the y-axis toward the z-axis,
as measured from the yz plane. Phi runs [0, 360) degrees. Theta runs [0, 180).
In (azimuth, elevation) coordinates, azimuth is the angle from the x-axis
toward y-ax, as measured from the xy plane. Azimuth runs [-180, 180) degrees.
Elevation runs [-90 to 90) degrees.
A more complete explanation of the coordinate systems can be found
in [1] and [2]. Coordinate transforms, as coded up, are based on the
formulas presented in [1].
## SUPER IMPORTANT NOTE TO READ BEFORE USE
When writing some unit tests, I noticed that a round-trip conversion does
not return the identical angle pair that you started with. My first thought
was that this is due to the interval bounds on inverse functions (arcsin etc),
but some things seem a little odd still. So I'm releasing this as a gist, so
people can decide whether it's a useful start, and hopefully someone will
point to the problem and fix it one day.
## References
[1] phitheta2azel MATLAB documentation, Mathworks.
url: http://www.mathworks.com/help/phased/ref/phitheta2azel.html
[2] COORDINATE SYSTEM PLOTTING FOR ANTENNA MEASUREMENTS, Gregory F. Masters
and Stuart F. Gregson. Tech. Report, Nearfield Systems, 2007.
url: http://ww2.nearfield.com/amta/AMTA07-0092-GFM_SFG.pdf
"""
import numpy as np
def azel_to_thetaphi(az, el):
""" Az-El to Theta-Phi conversion.
Args:
az (float or np.array): Azimuth angle, in radians
el (float or np.array): Elevation angle, in radians
Returns:
(theta, phi): Tuple of corresponding (theta, phi) angles, in radians
"""
cos_theta = np.cos(el) * np.cos(az)
tan_phi = np.tan(el) / np.sin(az)
theta = np.arccos(cos_theta)
phi = np.arctan2(np.tan(el), np.sin(az))
phi = (phi + 2 * np.pi) % (2 * np.pi)
return theta, phi
def thetaphi_to_azel(theta, phi):
""" Az-El to Theta-Phi conversion.
Args:
theta (float or np.array): Theta angle, in radians
phi (float or np.array): Phi angle, in radians
Returns:
(az, el): Tuple of corresponding (azimuth, elevation) angles, in radians
"""
sin_el = np.sin(phi) * np.sin(theta)
tan_az = np.cos(phi) * np.tan(theta)
el = np.arcsin(sin_el)
az = np.arctan(tan_az)
return az, el
def thetaphi_to_uv(theta, phi):
""" Theta-phi to U-V direction cosines
Args:
theta (float or np.array): Theta angle, in radians
phi (float or np.array): Phi angle, in radians
Returns:
(u, v): Tuple of corresponding (u, v) direction cosines
"""
u = np.sin(theta) * np.cos(phi)
v = np.sin(theta) * np.sin(phi)
w = np.cos(theta)
return u, v
def azel_to_uv(az, el):
""" Azimuth-elevation to U-V direction cosines
Args:
az (float or np.array): azimuth angle, in radians
el (float or np.array): elevation angle, in radians
Returns:
(u, v): Tuple of corresponding (u, v) direction cosines
"""
u = np.cos(el) * np.sin(az)
v = np.sin(el)
w = np.cos(az) * np.cos(el)
return u, v
def uv_to_thetaphi(u, v):
""" U-V direction cosines to theta-phi coordinates
Args:
u (float or np.array): U direction cosine
v (float or np.array): V direction cosine
Returns:
(theta, phi): Tuple of corresponding (theta, phi) angles, in radians
"""
theta = np.arcsin(np.sqrt(u**2 + v**2))
phi = np.arctan2(u, v)
phi = (phi + 2 * np.pi) % (2 * np.pi)
return theta, phi
def uv_to_azel(u, v):
""" U-V direction cosines to azimuth-elevation coordinates
Args:
u (float or np.array): U direction cosine
v (float or np.array): V direction cosine
Returns:
(az, el): Tuple of corresponding (azimuth, elevation) angles, in radians
"""
az = np.arctan2(u, np.sqrt(1 - u**2 - v**2))
el = np.arcsin(v)
return az, el
def plot_compare(a, b, ap, bp, title='', show=False, nf=True):
""" Helper function for debugging, plotting before & after """
if nf:
plt.figure()
plt.subplot(211)
plt.plot(a, ap, '.', label=title)
plt.legend()
plt.subplot(212)
plt.plot(b, bp, '.', label=title)
if show:
plt.show()
if __name__ == "__main__":
import pylab as plt
# Generate vectors covering intervals of theta & phi
theta = np.linspace(0, np.pi, 101)
phi = np.linspace(0, 2*np.pi, 101)
# Convert to other coordinates and back
u, v = thetaphi_to_uv(theta, phi)
theta_p, phi_p = uv_to_thetaphi(u, v)
plot_compare(theta, phi, theta_p, phi_p,
title='T-P -> U-V -> T-P')
az, el = thetaphi_to_azel(theta, phi)
theta_p, phi_p = azel_to_thetaphi(az, el)
plot_compare(theta, phi, theta_p, phi_p,
title='T-P -> A-E -> T-P', nf=False)
u, v = thetaphi_to_uv(theta, phi)
az, el = uv_to_azel(u, v)
theta_p, phi_p = azel_to_thetaphi(az, el)
plot_compare(theta, phi, theta_p, phi_p,
title='T-P -> U-V -> A-E -> T-P', nf=False)
u, v = thetaphi_to_uv(theta, phi)
az, el = uv_to_azel(u, v)
u, v = azel_to_uv(az, el)
theta_p, phi_p = uv_to_thetaphi(u, v)
plot_compare(theta, phi, theta_p, phi_p,
title='T-P -> U-V -> A-E -> U-V -> T-P', nf=False)
# Convert to Az-el, two methods
u, v = thetaphi_to_uv(theta, phi)
az, el = uv_to_azel(u, v)
plot_compare(theta, phi, az, el,
title='T-P -> U-V -> A-E')
az, el = thetaphi_to_azel(theta, phi)
plot_compare(theta, phi, az, el,
title='T-P -> A-E', nf=False)
# Generate vectors covering intervals of azimuth & elevation
az = np.linspace(-np.pi, np.pi, 101)
el = np.linspace(-np.pi/2, np.pi/2, 101)
# Convert to UV and back, compare
u, v = azel_to_uv(az, el)
az_p, el_p = uv_to_azel(u, v)
plot_compare(az, el, az_p, el_p,
title='A-E -> U-V -> A-E')
theta, phi = azel_to_thetaphi(az, el)
az_p, el_p = thetaphi_to_azel(theta, phi)
plot_compare(az, el, az_p, el_p,
title='A-E -> T-P -> A-E', nf=False)
u, v = azel_to_uv(az, el)
theta, phi = uv_to_thetaphi(u, v)
az_p, el_p = thetaphi_to_azel(theta, phi)
plot_compare(az, el, az_p, el_p,
title='A-E -> U-V -> T-P -> A-E', nf=False)
u, v = azel_to_uv(az, el)
theta, phi = uv_to_thetaphi(u, v)
u, v = thetaphi_to_uv(theta, phi)
az_p, el_p = uv_to_azel(u, v)
plot_compare(az, el, az_p, el_p,
title='A-E -> U-V -> T-P -> U-V -> A-E', nf=False)
# Convert to Theta-phi, two methods
u, v = thetaphi_to_uv(az, el)
theta, phi = uv_to_thetaphi(u, v)
plot_compare(az, el, theta, phi,
title='A-E -> U-V -> T-P')
theta, phi = azel_to_thetaphi(az, el)
plot_compare(az, el, theta, phi,
title='A-E -> T-P', nf=False)
plt.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment