Try cleaning up ugly API around position angles

This commit is contained in:
Brandon Rhodes 2019-12-06 14:25:26 -05:00
parent 3b366b32af
commit a50b317194
3 changed files with 40 additions and 26 deletions

View File

@ -65,12 +65,11 @@ Try reversing the coordinates, like:
.. testcode::
def rev(tup): return tup[1], tup[0]
print(position_angle_of(rev(m.radec()), rev(s.radec())))
print(position_angle_of(m.radec(), s.radec()))
.. testoutput::
77deg 31' 44.3"
282deg 28' 15.7"
Drat, but this angle is backwards, because right ascension increases
toward the east whereas the other angles, like azimuth, increase the

View File

@ -20,8 +20,5 @@ def test_position_angle_against_nasa_horizons():
a = position_angle_of(j.radec(epoch='date')[1::-1],
i.radec(epoch='date')[1::-1])
# TODO: eliminate the need for this reversal step
from skyfield.api import tau
a2 = Angle(radians=(-a.radians) % tau)
print(abs(a2.degrees - 293.671), 0.002)
assert abs(a2.degrees - 293.671) < 0.002
assert abs(a.degrees - 293.671) < 0.002

View File

@ -4,14 +4,30 @@ from numpy import arctan2, sin, cos, tan
from skyfield.constants import tau
from skyfield.units import Angle
def position_angle_of(latlon1, latlon2):
def position_angle_of(anglepair1, anglepair2):
"""Return the position angle of one position with respect to another.
Both arguments should be a tuple of :class:`~skyfield.units.Angle`
objects whose first item is latitude-like and whose second item is
longitude-like. The position angle returned will be 0 degrees if
position #2 is directly north of position #1 on the celestial
sphere, 90 degrees if east, 180 if south, and 270 if west.
Each argument should be a tuple whose first two items are
:class:`~skyfield.units.Angle` objects, like the tuples returned by
:meth:`~skyfield.positionlib.ICRF.radec()`,
:meth:`~skyfield.positionlib.ICRF.ecliptic_latlon()`, and
:meth:`~skyfield.positionlib.Apparent.altaz()`.
If one of the two angle items is signed (if its ``.signed``
attribute is true), then that angle is used as the latitude and the
other as the longitude; otherwise the first argument is assumed to
be the latitude.
If the longitude has a ``.preference`` attribute of ``'hours'``, it
is assumed to be a right ascension which is positive towards the
east. The position angle returned will be 0 degrees if position #2
is directly north of position #1 on the celestial sphere, 90 degrees
if east, 180 if south, and 270 if west.
Otherwise, the longitude is assumed to be azimuth, which measures
north to east around the horizon. The position angle returned will
be 0 degrees if position #2 is directly above position #1 in the
sky, 90 degrees to its left, 180 if below, and 270 if to the right.
>>> from skyfield.trigonometry import position_angle_of
>>> from skyfield.units import Angle
@ -20,19 +36,21 @@ def position_angle_of(latlon1, latlon2):
>>> position_angle_of(a, b)
<Angle 315deg 00' 15.7">
The tuples provided as arguments can have more than 2 items; any
extra items are ignored. This means you can pass in the output of
routines like :meth:`~skyfield.positionlib.ICRF.ecliptic_latlon()`
and :meth:`~skyfield.positionlib.Apparent.altaz()` as arguments.
Their angles will be used, but the third item of each tuple, the
distance, will be cleanly ignored.
"""
lat1 = latlon1[0].radians
lon1 = latlon1[1].radians
lat2 = latlon2[0].radians
lon2 = latlon2[1].radians
lat1, lon1 = anglepair1[0], anglepair1[1]
if lon1.signed:
lat1, lon1 = lon1, lat1
lat2, lon2 = anglepair2[0], anglepair2[1]
if lon2.signed:
lat2, lon2 = lon2, lat2
lat1 = lat1.radians
lon1 = lon1.radians if lon1.preference == 'hours' else -lon1.radians
lat2 = lat2.radians
lon2 = lon2.radians if lon2.preference == 'hours' else -lon2.radians
return Angle(radians=arctan2(
sin(lon1 - lon2),
sin(lon2 - lon1),
cos(lat1) * tan(lat2) - sin(lat1) * cos(lon2 - lon1),
) % tau)