3721 lines
134 KiB
Python
3721 lines
134 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
|
||
|
# PyMeeus: Python module implementing astronomical algorithms.
|
||
|
# Copyright (C) 2018 Dagoberto Salazar
|
||
|
#
|
||
|
# This program is free software: you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU Lesser General Public License as published by
|
||
|
# the Free Software Foundation, either version 3 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU Lesser General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU Lesser General Public License
|
||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
|
||
|
|
||
|
from math import (
|
||
|
sqrt, sin, cos, tan, atan, atan2, asin, acos, radians, pi, copysign,
|
||
|
degrees
|
||
|
)
|
||
|
from pymeeus.base import TOL, iint
|
||
|
from pymeeus.Angle import Angle
|
||
|
from pymeeus.Epoch import Epoch, JDE2000
|
||
|
from pymeeus.Interpolation import Interpolation
|
||
|
|
||
|
|
||
|
"""
|
||
|
.. module:: Coordinates
|
||
|
:synopsis: Module including different functions to handle coordinates
|
||
|
:license: GNU Lesser General Public License v3 (LGPLv3)
|
||
|
|
||
|
.. moduleauthor:: Dagoberto Salazar
|
||
|
"""
|
||
|
|
||
|
|
||
|
NUTATION_ARG_TABLE = [
|
||
|
[0, 0, 0, 0, 1],
|
||
|
[-2, 0, 0, 2, 2],
|
||
|
[0, 0, 0, 2, 2],
|
||
|
[0, 0, 0, 0, 2],
|
||
|
[0, 1, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 0],
|
||
|
[-2, 1, 0, 2, 2],
|
||
|
[0, 0, 0, 2, 1],
|
||
|
[0, 0, 1, 2, 2],
|
||
|
[-2, -1, 0, 2, 2],
|
||
|
[-2, 0, 1, 0, 0],
|
||
|
[-2, 0, 0, 2, 1],
|
||
|
[0, 0, -1, 2, 2],
|
||
|
[2, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 1],
|
||
|
[2, 0, -1, 2, 2],
|
||
|
[0, 0, -1, 0, 1],
|
||
|
[0, 0, 1, 2, 1],
|
||
|
[-2, 0, 2, 0, 0],
|
||
|
[0, 0, -2, 2, 1],
|
||
|
[2, 0, 0, 2, 2],
|
||
|
[0, 0, 2, 2, 2],
|
||
|
[0, 0, 2, 0, 0],
|
||
|
[-2, 0, 1, 2, 2],
|
||
|
[0, 0, 0, 2, 0],
|
||
|
[-2, 0, 0, 2, 0],
|
||
|
[0, 0, -1, 2, 1],
|
||
|
[0, 2, 0, 0, 0],
|
||
|
[2, 0, -1, 0, 1],
|
||
|
[-2, 2, 0, 2, 2],
|
||
|
[0, 1, 0, 0, 1],
|
||
|
[-2, 0, 1, 0, 1],
|
||
|
[0, -1, 0, 0, 1],
|
||
|
[0, 0, 2, -2, 0],
|
||
|
[2, 0, -1, 2, 1],
|
||
|
[2, 0, 1, 2, 2],
|
||
|
[0, 1, 0, 2, 2],
|
||
|
[-2, 1, 1, 0, 0],
|
||
|
[0, -1, 0, 2, 2],
|
||
|
[2, 0, 0, 2, 1],
|
||
|
[2, 0, 1, 0, 0],
|
||
|
[-2, 0, 2, 2, 2],
|
||
|
[-2, 0, 1, 2, 1],
|
||
|
[2, 0, -2, 0, 1],
|
||
|
[2, 0, 0, 0, 1],
|
||
|
[0, -1, 1, 0, 0],
|
||
|
[-2, -1, 0, 2, 1],
|
||
|
[-2, 0, 0, 0, 1],
|
||
|
[0, 0, 2, 2, 1],
|
||
|
[-2, 0, 2, 0, 1],
|
||
|
[-2, 1, 0, 2, 1],
|
||
|
[0, 0, 1, -2, 0],
|
||
|
[-1, 0, 1, 0, 0],
|
||
|
[-2, 1, 0, 0, 0],
|
||
|
[1, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 2, 0],
|
||
|
[0, 0, -2, 2, 2],
|
||
|
[-1, -1, 1, 0, 0],
|
||
|
[0, 1, 1, 0, 0],
|
||
|
[0, -1, 1, 2, 2],
|
||
|
[2, -1, -1, 2, 2],
|
||
|
[0, 0, 3, 2, 2],
|
||
|
[2, -1, 0, 2, 2],
|
||
|
]
|
||
|
"""This table contains the periodic terms for the argument of the nutation. In
|
||
|
Meeus' book this is Table 22.A and can be found in pages 145-146."""
|
||
|
|
||
|
NUTATION_SINE_COEF_TABLE = [
|
||
|
[-171996.0, -174.2],
|
||
|
[-13187.0, -1.6],
|
||
|
[-2274.0, -0.2],
|
||
|
[2062.0, 0.2],
|
||
|
[1426.0, -3.4],
|
||
|
[712.0, 0.1],
|
||
|
[-517.0, 1.2],
|
||
|
[-386.0, -0.4],
|
||
|
[-301.0, 0.0],
|
||
|
[217.0, -0.5],
|
||
|
[-158.0, 0.0],
|
||
|
[129.0, 0.1],
|
||
|
[123.0, 0.0],
|
||
|
[63.0, 0.0],
|
||
|
[63.0, 0.1],
|
||
|
[-59.0, 0.0],
|
||
|
[-58.0, -0.1],
|
||
|
[-51.0, 0.0],
|
||
|
[48.0, 0.0],
|
||
|
[46.0, 0.0],
|
||
|
[-38.0, 0.0],
|
||
|
[-31.0, 0.0],
|
||
|
[29.0, 0.0],
|
||
|
[29.0, 0.0],
|
||
|
[26.0, 0.0],
|
||
|
[-22.0, 0.0],
|
||
|
[21.0, 0.0],
|
||
|
[17.0, -0.1],
|
||
|
[16.0, 0.0],
|
||
|
[-16.0, 0.1],
|
||
|
[-15.0, 0.0],
|
||
|
[-13.0, 0.0],
|
||
|
[-12.0, 0.0],
|
||
|
[11.0, 0.0],
|
||
|
[-10.0, 0.0],
|
||
|
[-8.0, 0.0],
|
||
|
[7.0, 0.0],
|
||
|
[-7.0, 0.0],
|
||
|
[-7.0, 0.0],
|
||
|
[-7.0, 0.0],
|
||
|
[6.0, 0.0],
|
||
|
[6.0, 0.0],
|
||
|
[6.0, 0.0],
|
||
|
[-6.0, 0.0],
|
||
|
[-6.0, 0.0],
|
||
|
[5.0, 0.0],
|
||
|
[-5.0, 0.0],
|
||
|
[-5.0, 0.0],
|
||
|
[-5.0, 0.0],
|
||
|
[4.0, 0.0],
|
||
|
[4.0, 0.0],
|
||
|
[4.0, 0.0],
|
||
|
[-4.0, 0.0],
|
||
|
[-4.0, 0.0],
|
||
|
[-4.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
]
|
||
|
"""This table contains the periodic terms for the coefficients of the sine of
|
||
|
the argument of the nutation, and they are used to compute Delta psi. Units are
|
||
|
in 0.0001''. In Meeus' book this is Table 22.A and can be found in pages
|
||
|
145-146."""
|
||
|
|
||
|
NUTATION_COSINE_COEF_TABLE = [
|
||
|
[92025.0, 8.9],
|
||
|
[5736.0, -3.1],
|
||
|
[977.0, -0.5],
|
||
|
[-895.0, 0.5],
|
||
|
[54.0, -0.1],
|
||
|
[-7.0, 0.0],
|
||
|
[224.0, -0.6],
|
||
|
[200.0, 0.0],
|
||
|
[129.0, -0.1],
|
||
|
[-95.0, 0.3],
|
||
|
[0.0, 0.0],
|
||
|
[-70.0, 0.0],
|
||
|
[-53.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[-33.0, 0.0],
|
||
|
[26.0, 0.0],
|
||
|
[32.0, 0.0],
|
||
|
[27.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[-24.0, 0.0],
|
||
|
[16.0, 0.0],
|
||
|
[13.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[-12.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[-10.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[-8.0, 0.0],
|
||
|
[7.0, 0.0],
|
||
|
[9.0, 0.0],
|
||
|
[7.0, 0.0],
|
||
|
[6.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[5.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[-3.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[0.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
[3.0, 0.0],
|
||
|
]
|
||
|
"""This table contains the periodic terms for the coefficients of the cosine of
|
||
|
the argument of the nutation, and they are used to compute Delta epsilon. Units
|
||
|
are in 0.0001''. In Meeus' book this is Table 22.A and can be found in pages
|
||
|
145-146."""
|
||
|
|
||
|
|
||
|
def mean_obliquity(*args, **kwargs):
|
||
|
"""This function computes the mean obliquity (epsilon0) at the provided
|
||
|
date.
|
||
|
|
||
|
This function internally uses an :class:`Epoch` object, and the **utc**
|
||
|
argument then controls the way the UTC->TT conversion is handled for that
|
||
|
object. If **leap_seconds** argument is set to a value different than zero,
|
||
|
then that value will be used for the UTC->TAI conversion, and the internal
|
||
|
leap seconds table will be bypassed.
|
||
|
|
||
|
:param args: Either :class:`Epoch`, date, datetime or year, month,
|
||
|
day values, by themselves or inside a tuple or list
|
||
|
:type args: int, float, :py:class:`Epoch`, datetime, date, tuple,
|
||
|
list
|
||
|
:param utc: Whether the provided epoch is a civil time (UTC) or TT
|
||
|
:type utc: bool
|
||
|
:param leap_seconds: This is the value to be used in the UTC->TAI
|
||
|
conversion, instead of taking it from internal leap seconds table.
|
||
|
:type leap_seconds: int, float
|
||
|
|
||
|
:returns: The mean obliquity of the ecliptic, as an :class:`Angle`
|
||
|
:rtype: :class:`Angle`
|
||
|
:raises: ValueError if input values are in the wrong range.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> e0 = mean_obliquity(1987, 4, 10)
|
||
|
>>> a = e0.dms_tuple()
|
||
|
>>> a[0]
|
||
|
23
|
||
|
>>> a[1]
|
||
|
26
|
||
|
>>> round(a[2], 3)
|
||
|
27.407
|
||
|
"""
|
||
|
|
||
|
# Get the Epoch object corresponding to input parameters
|
||
|
t = Epoch.check_input_date(*args, **kwargs)
|
||
|
# Let's redefine u in units of 100 Julian centuries from Epoch J2000.0
|
||
|
u = (t.jde() - 2451545.0) / 3652500.0
|
||
|
epsilon0 = Angle(23, 26, 21.448)
|
||
|
delta = u * (
|
||
|
-4680.93
|
||
|
+ u
|
||
|
* (
|
||
|
-1.55
|
||
|
+ u
|
||
|
* (
|
||
|
1999.25
|
||
|
+ u
|
||
|
* (
|
||
|
-51.38
|
||
|
+ u
|
||
|
* (
|
||
|
-249.67
|
||
|
+ u
|
||
|
* (-39.05 + u * (7.12 + u *
|
||
|
(27.87 + u * (5.79 + u * 2.45))))
|
||
|
)
|
||
|
)
|
||
|
)
|
||
|
)
|
||
|
)
|
||
|
delta = Angle(0, 0, delta)
|
||
|
epsilon0 += delta
|
||
|
return epsilon0
|
||
|
|
||
|
|
||
|
def true_obliquity(*args, **kwargs):
|
||
|
"""This function computes the true obliquity (epsilon) at the provided
|
||
|
date. The true obliquity is the mean obliquity (epsilon0) plus the
|
||
|
correction provided by the nutation in obliquity (Delta epsilon).
|
||
|
|
||
|
This function internally uses an :class:`Epoch` object, and the **utc**
|
||
|
argument then controls the way the UTC->TT conversion is handled for that
|
||
|
object. If **leap_seconds** argument is set to a value different than zero,
|
||
|
then that value will be used for the UTC->TAI conversion, and the internal
|
||
|
leap seconds table will be bypassed.
|
||
|
|
||
|
:param args: Either :class:`Epoch`, date, datetime or year, month,
|
||
|
day values, by themselves or inside a tuple or list
|
||
|
:type args: int, float, :py:class:`Epoch`, datetime, date, tuple,
|
||
|
list
|
||
|
:param utc: Whether the provided epoch is a civil time (UTC) or TT
|
||
|
:type utc: bool
|
||
|
:param leap_seconds: This is the value to be used in the UTC->TAI
|
||
|
conversion, instead of taking it from internal leap seconds table.
|
||
|
:type leap_seconds: int, float
|
||
|
|
||
|
:returns: The true obliquity of the ecliptic, as an Angle
|
||
|
:rtype: :class:`Angle`
|
||
|
:raises: ValueError if input values are in the wrong range.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> epsilon = true_obliquity(1987, 4, 10)
|
||
|
>>> a = epsilon.dms_tuple()
|
||
|
>>> a[0]
|
||
|
23
|
||
|
>>> a[1]
|
||
|
26
|
||
|
>>> round(a[2], 3)
|
||
|
36.849
|
||
|
"""
|
||
|
|
||
|
epsilon0 = mean_obliquity(*args, **kwargs)
|
||
|
delta_epsilon = nutation_obliquity(*args, **kwargs)
|
||
|
return epsilon0 + delta_epsilon
|
||
|
|
||
|
|
||
|
def nutation_longitude(*args, **kwargs):
|
||
|
"""This function computes the nutation in longitude (Delta psi) at the
|
||
|
provided date.
|
||
|
|
||
|
This function internally uses an :class:`Epoch` object, and the **utc**
|
||
|
argument then controls the way the UTC->TT conversion is handled for that
|
||
|
object. If **leap_seconds** argument is set to a value different than zero,
|
||
|
then that value will be used for the UTC->TAI conversion, and the internal
|
||
|
leap seconds table will be bypassed.
|
||
|
|
||
|
:param args: Either :class:`Epoch`, date, datetime or year, month,
|
||
|
day values, by themselves or inside a tuple or list
|
||
|
:type args: int, float, :py:class:`Epoch`, datetime, date, tuple,
|
||
|
list
|
||
|
:param utc: Whether the provided epoch is a civil time (UTC) or TT
|
||
|
:type utc: bool
|
||
|
:param leap_seconds: This is the value to be used in the UTC->TAI
|
||
|
conversion, instead of taking it from internal leap seconds table.
|
||
|
:type leap_seconds: int, float
|
||
|
|
||
|
:returns: The nutation in longitude (Delta psi), as an Angle
|
||
|
:rtype: :class:`Angle`
|
||
|
:raises: ValueError if input values are in the wrong range.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> dpsi = nutation_longitude(1987, 4, 10)
|
||
|
>>> a = dpsi.dms_tuple()
|
||
|
>>> a[0]
|
||
|
0
|
||
|
>>> a[1]
|
||
|
0
|
||
|
>>> round(a[2], 3)
|
||
|
3.788
|
||
|
>>> a[3]
|
||
|
-1.0
|
||
|
"""
|
||
|
|
||
|
# Get the Epoch object corresponding to input parameters
|
||
|
t = Epoch.check_input_date(*args, **kwargs)
|
||
|
# Let's redefine t in units of Julian centuries from Epoch J2000.0
|
||
|
t = (t.jde() - 2451545.0) / 36525.0
|
||
|
# Let's compute the mean elongation of the Moon from the Sun
|
||
|
d = 297.85036 + t * (445267.111480 + t * (-0.0019142 + t / 189474.0))
|
||
|
d = Angle(d) # Convert into an Angle: It is easier to handle
|
||
|
# Compute the mean anomaly of the Sun (from Earth)
|
||
|
m = 357.52772 + t * (35999.050340 + t * (-0.0001603 - t / 300000.0))
|
||
|
m = Angle(m)
|
||
|
# Compute the mean anomaly of the Moon
|
||
|
mprime = 134.96298 + t * (477198.867398 + t * (0.0086972 + t / 56250.0))
|
||
|
mprime = Angle(mprime)
|
||
|
# Now, let's compute the Moon's argument of latitude
|
||
|
f = 93.27191 + t * (483202.017538 + t * (-0.0036825 + t / 327270.0))
|
||
|
f = Angle(f)
|
||
|
# And finally, the longitude of the ascending node of the Moon's mean
|
||
|
# orbit on the ecliptic, measured from the mean equinox of date
|
||
|
omega = 125.04452 + t * (-1934.136261 + t * (0.0020708 + t / 450000.0))
|
||
|
omega = Angle(omega)
|
||
|
# Let's store this results in a list, in preparation for using tables
|
||
|
arguments = [d, m, mprime, f, omega]
|
||
|
# Now is time of using the nutation tables
|
||
|
deltapsi = 0.0
|
||
|
for i, value in enumerate(NUTATION_SINE_COEF_TABLE):
|
||
|
argument = Angle()
|
||
|
coeff = 0.0
|
||
|
for j in range(5):
|
||
|
if NUTATION_ARG_TABLE[i][j]: # Avoid multiplications by zero
|
||
|
argument += NUTATION_ARG_TABLE[i][j] * arguments[j]
|
||
|
coeff = value[0]
|
||
|
if value[1]:
|
||
|
coeff += value[1] * t
|
||
|
deltapsi += (coeff * sin(argument.rad())) / 10000.0
|
||
|
return Angle(0, 0, deltapsi)
|
||
|
|
||
|
|
||
|
def nutation_obliquity(*args, **kwargs):
|
||
|
"""This function computes the nutation in obliquity (Delta epsilon) at
|
||
|
the provided date.
|
||
|
|
||
|
This function internally uses an :class:`Epoch` object, and the **utc**
|
||
|
argument then controls the way the UTC->TT conversion is handled for that
|
||
|
object. If **leap_seconds** argument is set to a value different than zero,
|
||
|
then that value will be used for the UTC->TAI conversion, and the internal
|
||
|
leap seconds table will be bypassed.
|
||
|
|
||
|
:param args: Either :class:`Epoch`, date, datetime or year, month,
|
||
|
day values, by themselves or inside a tuple or list
|
||
|
:type args: int, float, :py:class:`Epoch`, datetime, date, tuple,
|
||
|
list
|
||
|
:param utc: Whether the provided epoch is a civil time (UTC) or TT
|
||
|
:type utc: bool
|
||
|
:param leap_seconds: This is the value to be used in the UTC->TAI
|
||
|
conversion, instead of taking it from internal leap seconds table.
|
||
|
:type leap_seconds: int, float
|
||
|
|
||
|
:returns: The nutation in obliquity (Delta epsilon), as an
|
||
|
:class:`Angle`
|
||
|
:rtype: :class:`Angle`
|
||
|
:raises: ValueError if input values are in the wrong range.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> depsilon = nutation_obliquity(1987, 4, 10)
|
||
|
>>> a = depsilon.dms_tuple()
|
||
|
>>> a[0]
|
||
|
0
|
||
|
>>> a[1]
|
||
|
0
|
||
|
>>> round(a[2], 3)
|
||
|
9.443
|
||
|
>>> a[3]
|
||
|
1.0
|
||
|
"""
|
||
|
|
||
|
# Get the Epoch object corresponding to input parameters
|
||
|
t = Epoch.check_input_date(*args, **kwargs)
|
||
|
# Let's redefine t in units of Julian centuries from Epoch J2000.0
|
||
|
t = (t.jde() - 2451545.0) / 36525.0
|
||
|
# Let's compute the mean elongation of the Moon from the Sun
|
||
|
d = 297.85036 + t * (445267.111480 + t * (-0.0019142 + t / 189474.0))
|
||
|
d = Angle(d) # Convert into an Angle: It is easier to handle
|
||
|
# Compute the mean anomaly of the Sun (from Earth)
|
||
|
m = 357.52772 + t * (35999.050340 + t * (-0.0001603 - t / 300000.0))
|
||
|
m = Angle(m)
|
||
|
# Compute the mean anomaly of the Moon
|
||
|
mprime = 134.96298 + t * (477198.867398 + t * (0.0086972 + t / 56250.0))
|
||
|
mprime = Angle(mprime)
|
||
|
# Now, let's compute the Moon's argument of latitude
|
||
|
f = 93.27191 + t * (483202.017538 + t * (-0.0036825 + t / 327270.0))
|
||
|
f = Angle(f)
|
||
|
# And finally, the longitude of the ascending node of the Moon's mean
|
||
|
# orbit on the ecliptic, measured from the mean equinox of date
|
||
|
omega = 125.04452 + t * (-1934.136261 + t * (0.0020708 + t / 450000.0))
|
||
|
omega = Angle(omega)
|
||
|
# Let's store this results in a list, in preparation for using tables
|
||
|
arguments = [d, m, mprime, f, omega]
|
||
|
# Now is time of using the nutation tables
|
||
|
deltaepsilon = 0.0
|
||
|
for i, value in enumerate(NUTATION_COSINE_COEF_TABLE):
|
||
|
argument = Angle()
|
||
|
coeff = 0.0
|
||
|
for j in range(5):
|
||
|
if NUTATION_ARG_TABLE[i][j]: # Avoid multiplications by zero
|
||
|
argument += NUTATION_ARG_TABLE[i][j] * arguments[j]
|
||
|
coeff = value[0]
|
||
|
if value[1]:
|
||
|
coeff += value[1] * t
|
||
|
deltaepsilon += (coeff * cos(argument.rad())) / 10000.0
|
||
|
return Angle(0, 0, deltaepsilon)
|
||
|
|
||
|
|
||
|
def precession_equatorial(
|
||
|
start_epoch, final_epoch, start_ra, start_dec, p_motion_ra=0.0,
|
||
|
p_motion_dec=0.0
|
||
|
):
|
||
|
"""This function converts the equatorial coordinates (right ascension and
|
||
|
declination) given for an epoch and a equinox, to the corresponding
|
||
|
values for another epoch and equinox. Only the **mean** positions, i.e.
|
||
|
the effects of precession and proper motion, are considered here.
|
||
|
|
||
|
:param start_epoch: Initial epoch when initial coordinates are given
|
||
|
:type start_epoch: :py:class:`Epoch`
|
||
|
:param final_epoch: Final epoch for when coordinates are going to be
|
||
|
computed
|
||
|
:type final_epoch: :py:class:`Epoch`
|
||
|
:param start_ra: Initial right ascension
|
||
|
:type start_ra: :py:class:`Angle`
|
||
|
:param start_dec: Initial declination
|
||
|
:type start_dec: :py:class:`Angle`
|
||
|
:param p_motion_ra: Proper motion in right ascension, in degrees per
|
||
|
year. Zero by default.
|
||
|
:type p_motion_ra: :py:class:`Angle`
|
||
|
:param p_motion_dec: Proper motion in declination, in degrees per year.
|
||
|
Zero by default.
|
||
|
:type p_motion_dec: :py:class:`Angle`
|
||
|
|
||
|
:returns: Equatorial coordinates (right ascension, declination, in that
|
||
|
order) corresponding to the final epoch, given as two objects
|
||
|
:class:`Angle` inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> start_epoch = JDE2000
|
||
|
>>> final_epoch = Epoch(2028, 11, 13.19)
|
||
|
>>> alpha0 = Angle(2, 44, 11.986, ra=True)
|
||
|
>>> delta0 = Angle(49, 13, 42.48)
|
||
|
>>> pm_ra = Angle(0, 0, 0.03425, ra=True)
|
||
|
>>> pm_dec = Angle(0, 0, -0.0895)
|
||
|
>>> alpha, delta = precession_equatorial(start_epoch, final_epoch, alpha0,
|
||
|
... delta0, pm_ra, pm_dec)
|
||
|
>>> print(alpha.ra_str(False, 3))
|
||
|
2:46:11.331
|
||
|
>>> print(delta.dms_str(False, 2))
|
||
|
49:20:54.54
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(start_epoch, Epoch)
|
||
|
and isinstance(final_epoch, Epoch)
|
||
|
and isinstance(start_ra, Angle)
|
||
|
and isinstance(start_dec, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if isinstance(p_motion_ra, (int, float)):
|
||
|
p_motion_ra = Angle(p_motion_ra)
|
||
|
if isinstance(p_motion_dec, (int, float)):
|
||
|
p_motion_dec = Angle(p_motion_dec)
|
||
|
if not (isinstance(p_motion_ra, Angle) and
|
||
|
isinstance(p_motion_dec, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
tt = (start_epoch - JDE2000) / 36525.0
|
||
|
t = (final_epoch - start_epoch) / 36525.0
|
||
|
# Correct starting coordinates by proper motion
|
||
|
start_ra += p_motion_ra * t * 100.0
|
||
|
start_dec += p_motion_dec * t * 100.0
|
||
|
# Compute the conversion parameters
|
||
|
zeta = t * (
|
||
|
(2306.2181 + tt * (1.39656 - 0.000139 * tt))
|
||
|
+ t * ((0.30188 - 0.000344 * tt) + 0.017998 * t)
|
||
|
)
|
||
|
z = t * (
|
||
|
(2306.2181 + tt * (1.39656 - 0.000139 * tt))
|
||
|
+ t * ((1.09468 + 0.000066 * tt) + 0.018203 * t)
|
||
|
)
|
||
|
theta = t * (
|
||
|
2004.3109
|
||
|
+ tt * (-0.85330 - 0.000217 * tt)
|
||
|
+ t * (-(0.42665 + 0.000217 * tt) - 0.041833 * t)
|
||
|
)
|
||
|
# Redefine the former values as Angles
|
||
|
zeta = Angle(0, 0, zeta)
|
||
|
z = Angle(0, 0, z)
|
||
|
theta = Angle(0, 0, theta)
|
||
|
a = cos(start_dec.rad()) * sin(start_ra.rad() + zeta.rad())
|
||
|
b = cos(theta.rad()) * cos(start_dec.rad()) * cos(
|
||
|
start_ra.rad() + zeta.rad()
|
||
|
) - sin(theta.rad()) * sin(start_dec.rad())
|
||
|
c = sin(theta.rad()) * cos(start_dec.rad()) * cos(
|
||
|
start_ra.rad() + zeta.rad()
|
||
|
) + cos(theta.rad()) * sin(start_dec.rad())
|
||
|
final_ra = atan2(a, b) + z.rad()
|
||
|
if start_dec > 85.0: # Coordinates are close to the pole
|
||
|
final_dec = sqrt(a * a + b * b)
|
||
|
else:
|
||
|
final_dec = asin(c)
|
||
|
# Convert results to Angles. Please note results are in radians
|
||
|
final_ra = Angle(final_ra, radians=True)
|
||
|
final_dec = Angle(final_dec, radians=True)
|
||
|
return (final_ra, final_dec)
|
||
|
|
||
|
|
||
|
def precession_ecliptical(
|
||
|
start_epoch, final_epoch, start_lon, start_lat, p_motion_lon=0.0,
|
||
|
p_motion_lat=0.0
|
||
|
):
|
||
|
"""This function converts the ecliptical coordinates (longitude and
|
||
|
latitude) given for an epoch and a equinox, to the corresponding
|
||
|
values for another epoch and equinox. Only the **mean** positions, i.e.
|
||
|
the effects of precession and proper motion, are considered here.
|
||
|
|
||
|
:param start_epoch: Initial epoch when initial coordinates are given
|
||
|
:type start_epoch: :py:class:`Epoch`
|
||
|
:param final_epoch: Final epoch for when coordinates are going to be
|
||
|
computed
|
||
|
:type final_epoch: :py:class:`Epoch`
|
||
|
:param start_lon: Initial longitude
|
||
|
:type start_lon: :py:class:`Angle`
|
||
|
:param start_lat: Initial latitude
|
||
|
:type start_lat: :py:class:`Angle`
|
||
|
:param p_motion_lon: Proper motion in longitude, in degrees per year.
|
||
|
Zero by default.
|
||
|
:type p_motion_lon: :py:class:`Angle`
|
||
|
:param p_motion_lat: Proper motion in latitude, in degrees per year.
|
||
|
Zero by default.
|
||
|
:type p_motion_lat: :py:class:`Angle`
|
||
|
|
||
|
:returns: Ecliptical coordinates (longitude, latitude, in that order)
|
||
|
corresponding to the final epoch, given as two :class:`Angle`
|
||
|
objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> start_epoch = JDE2000
|
||
|
>>> final_epoch = Epoch(-214, 6, 30.0)
|
||
|
>>> lon0 = Angle(149.48194)
|
||
|
>>> lat0 = Angle(1.76549)
|
||
|
>>> lon, lat = precession_ecliptical(start_epoch, final_epoch, lon0, lat0)
|
||
|
>>> print(round(lon(), 3))
|
||
|
118.704
|
||
|
>>> print(round(lat(), 3))
|
||
|
1.615
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(start_epoch, Epoch)
|
||
|
and isinstance(final_epoch, Epoch)
|
||
|
and isinstance(start_lon, Angle)
|
||
|
and isinstance(start_lat, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if isinstance(p_motion_lon, (int, float)):
|
||
|
p_motion_lon = Angle(p_motion_lon)
|
||
|
if isinstance(p_motion_lat, (int, float)):
|
||
|
p_motion_lat = Angle(p_motion_lat)
|
||
|
if not (isinstance(p_motion_lon, Angle) and
|
||
|
isinstance(p_motion_lat, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
tt = (start_epoch - JDE2000) / 36525.0
|
||
|
t = (final_epoch - start_epoch) / 36525.0
|
||
|
# Correct starting coordinates by proper motion
|
||
|
start_lon += p_motion_lon * t * 100.0
|
||
|
start_lat += p_motion_lat * t * 100.0
|
||
|
# Compute the conversion parameters
|
||
|
eta = t * (
|
||
|
(47.0029 + tt * (-0.06603 + 0.000598 * tt))
|
||
|
+ t * ((-0.03302 + 0.000598 * tt) + 0.00006 * t)
|
||
|
)
|
||
|
pie = tt * (3289.4789 + 0.60622 * tt) + t * (
|
||
|
-(869.8089 + 0.50491 * tt) + 0.03536 * t
|
||
|
)
|
||
|
p = t * (
|
||
|
5029.0966
|
||
|
+ tt * (2.22226 - 0.000042 * tt)
|
||
|
+ t * (1.11113 - 0.000042 * tt - 0.000006 * t)
|
||
|
)
|
||
|
eta = Angle(0, 0, eta)
|
||
|
pie = Angle(0, 0, pie)
|
||
|
p = Angle(0, 0, p)
|
||
|
# But beware!: There is still a missing constant for pie. We didn't add
|
||
|
# it before because of the mismatch between degrees and seconds
|
||
|
pie += 174.876384
|
||
|
a = (cos(eta.rad()) * cos(start_lat.rad()) *
|
||
|
sin(pie.rad() - start_lon.rad()) - sin(eta.rad()) *
|
||
|
sin(start_lat.rad()))
|
||
|
b = cos(start_lat.rad()) * cos(pie.rad() - start_lon.rad())
|
||
|
c = cos(eta.rad()) * sin(start_lat.rad()) + sin(eta.rad()) * cos(
|
||
|
start_lat.rad()
|
||
|
) * sin(pie.rad() - start_lon.rad())
|
||
|
final_lon = p.rad() + pie.rad() - atan2(a, b)
|
||
|
final_lat = asin(c)
|
||
|
# Convert results to Angles. Please note results are in radians
|
||
|
final_lon = Angle(final_lon, radians=True)
|
||
|
final_lat = Angle(final_lat, radians=True)
|
||
|
return (final_lon, final_lat)
|
||
|
|
||
|
|
||
|
def p_motion_equa2eclip(p_motion_ra, p_motion_dec, ra, dec, lat, epsilon):
|
||
|
"""It is usual that proper motions are given in equatorial coordinates,
|
||
|
not in ecliptical ones. Therefore, this function converts the provided
|
||
|
proper motions in equatorial coordinates to the corresponding ones in
|
||
|
ecliptical coordinates.
|
||
|
|
||
|
:param p_motion_ra: Proper motion in right ascension, in degrees per
|
||
|
year, as an :class:`Angle` object
|
||
|
:type p_motion_ra: :py:class:`Angle`
|
||
|
:param p_motion_dec: Proper motion in declination, in degrees per year,
|
||
|
as an :class:`Angle` object
|
||
|
:type p_motion_dec: :py:class:`Angle`
|
||
|
:param ra: Right ascension of the astronomical object, as degrees in an
|
||
|
:class:`Angle` object
|
||
|
:type ra: :py:class:`Angle`
|
||
|
:param dec: Declination of the astronomical object, as degrees in an
|
||
|
:class:`Angle` object
|
||
|
:type dec: :py:class:`Angle`
|
||
|
:param lat: Ecliptical latitude of the astronomical object, as degrees
|
||
|
in an :class:`Angle` object
|
||
|
:type lat: :py:class:`Angle`
|
||
|
:param epsilon: Obliquity of the ecliptic
|
||
|
:type epsilon: :py:class:`Angle`
|
||
|
|
||
|
:returns: Proper motions in ecliptical longitude and latitude (in that
|
||
|
order), given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(p_motion_ra, Angle)
|
||
|
and isinstance(p_motion_dec, Angle)
|
||
|
and isinstance(ra, Angle)
|
||
|
and isinstance(dec, Angle)
|
||
|
and isinstance(lat, Angle)
|
||
|
and isinstance(epsilon, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
pm_ra = p_motion_ra.rad()
|
||
|
pm_dec = p_motion_dec.rad()
|
||
|
s_eps = sin(epsilon.rad())
|
||
|
c_eps = cos(epsilon.rad())
|
||
|
s_ra = sin(ra.rad())
|
||
|
c_ra = cos(ra.rad())
|
||
|
s_dec = sin(dec.rad())
|
||
|
c_dec = cos(dec.rad())
|
||
|
c_lat = cos(lat.rad())
|
||
|
se_ca = s_eps * c_ra
|
||
|
se_sd_sa = s_eps * s_dec * s_ra
|
||
|
pa_cd = pm_ra * c_dec
|
||
|
ce_cd = c_eps * c_dec
|
||
|
cl2 = c_lat * c_lat
|
||
|
p_motion_lon = (pm_dec * se_ca + pa_cd * (ce_cd + se_sd_sa)) / cl2
|
||
|
p_motion_lat = (pm_dec * (ce_cd + se_sd_sa) - pa_cd * se_ca) / c_lat
|
||
|
return (p_motion_lon, p_motion_lat)
|
||
|
|
||
|
|
||
|
def precession_newcomb(
|
||
|
start_epoch, final_epoch, start_ra, start_dec, p_motion_ra=0.0,
|
||
|
p_motion_dec=0.0
|
||
|
):
|
||
|
"""This function implements the Newcomb precessional equations used in
|
||
|
the old FK4 system. It takes equatorial coordinates (right ascension
|
||
|
and declination) given for an epoch and a equinox, and converts them to
|
||
|
the corresponding values for another epoch and equinox. Only the
|
||
|
**mean** positions, i.e. the effects of precession and proper motion,
|
||
|
are considered here.
|
||
|
|
||
|
:param start_epoch: Initial epoch when initial coordinates are given
|
||
|
:type start_epoch: :py:class:`Epoch`
|
||
|
:param final_epoch: Final epoch for when coordinates are going to be
|
||
|
computed
|
||
|
:type final_epoch: :py:class:`Epoch`
|
||
|
:param start_ra: Initial right ascension
|
||
|
:type start_ra: :py:class:`Angle`
|
||
|
:param start_dec: Initial declination
|
||
|
:type start_dec: :py:class:`Angle`
|
||
|
:param p_motion_ra: Proper motion in right ascension, in degrees per
|
||
|
year. Zero by default.
|
||
|
:type p_motion_ra: :py:class:`Angle`
|
||
|
:param p_motion_dec: Proper motion in declination, in degrees per year.
|
||
|
Zero by default.
|
||
|
:type p_motion_dec: :py:class:`Angle`
|
||
|
|
||
|
:returns: Equatorial coordinates (right ascension, declination, in that
|
||
|
order) corresponding to the final epoch, given as two objects
|
||
|
:class:`Angle` inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(start_epoch, Epoch)
|
||
|
and isinstance(final_epoch, Epoch)
|
||
|
and isinstance(start_ra, Angle)
|
||
|
and isinstance(start_dec, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if isinstance(p_motion_ra, (int, float)):
|
||
|
p_motion_ra = Angle(p_motion_ra)
|
||
|
if isinstance(p_motion_dec, (int, float)):
|
||
|
p_motion_dec = Angle(p_motion_dec)
|
||
|
if not (isinstance(p_motion_ra, Angle) and
|
||
|
isinstance(p_motion_dec, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
tt = (start_epoch - 2415020.3135) / 36524.2199
|
||
|
t = (final_epoch - start_epoch) / 36524.2199
|
||
|
# Correct starting coordinates by proper motion
|
||
|
start_ra += p_motion_ra * t * 100.0
|
||
|
start_dec += p_motion_dec * t * 100.0
|
||
|
# Compute the conversion parameters
|
||
|
zeta = t * (2304.25 + 1.396 * tt + t * (0.302 + 0.018 * t))
|
||
|
z = zeta + t * t * (0.791 + 0.001 * t)
|
||
|
theta = t * (2004.682 - 0.853 * tt - t * (0.426 + 0.042 * t))
|
||
|
# Redefine the former values as Angles
|
||
|
zeta = Angle(0, 0, zeta)
|
||
|
z = Angle(0, 0, z)
|
||
|
theta = Angle(0, 0, theta)
|
||
|
a = cos(start_dec.rad()) * sin(start_ra.rad() + zeta.rad())
|
||
|
b = cos(theta.rad()) * cos(start_dec.rad()) * cos(
|
||
|
start_ra.rad() + zeta.rad()
|
||
|
) - sin(theta.rad()) * sin(start_dec.rad())
|
||
|
c = sin(theta.rad()) * cos(start_dec.rad()) * cos(
|
||
|
start_ra.rad() + zeta.rad()
|
||
|
) + cos(theta.rad()) * sin(start_dec.rad())
|
||
|
final_ra = atan2(a, b) + z.rad()
|
||
|
if start_dec > 85.0: # Coordinates are close to the pole
|
||
|
final_dec = sqrt(a * a + b * b)
|
||
|
else:
|
||
|
final_dec = asin(c)
|
||
|
# Convert results to Angles. Please note results are in radians
|
||
|
final_ra = Angle(final_ra, radians=True)
|
||
|
final_dec = Angle(final_dec, radians=True)
|
||
|
return (final_ra, final_dec)
|
||
|
|
||
|
|
||
|
def motion_in_space(
|
||
|
start_ra, start_dec, distance, velocity, p_motion_ra, p_motion_dec, time
|
||
|
):
|
||
|
"""This function computes the star's true motion through space relative
|
||
|
to the Sun, allowing to compute the start proper motion at a given
|
||
|
time.
|
||
|
|
||
|
:param start_ra: Initial right ascension
|
||
|
:type start_ra: :py:class:`Angle`
|
||
|
:param start_dec: Initial declination
|
||
|
:type start_dec: :py:class:`Angle`
|
||
|
:param distance: Star's distance to the Sun, in parsecs. If distance is
|
||
|
given in light-years, multipy it by 0.3066. If the star's parallax
|
||
|
**pie** (in arcseconds) is given, use (1.0/pie).
|
||
|
:type distance: float
|
||
|
:param velocity: Radial velocity in km/s
|
||
|
:type velocity: float
|
||
|
:param p_motion_ra: Proper motion in right ascension, in degrees per
|
||
|
year.
|
||
|
:type p_motion_ra: :py:class:`Angle`
|
||
|
:param p_motion_dec: Proper motion in declination, in degrees per year.
|
||
|
:type p_motion_dec: :py:class:`Angle`
|
||
|
:param time: Number of years since starting epoch, positive in the
|
||
|
future, negative in the past
|
||
|
:type time: float
|
||
|
|
||
|
:returns: Equatorial coordinates (right ascension, declination, in that
|
||
|
order) corresponding to the final epoch, given as two objects
|
||
|
:class:`Angle` inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> ra = Angle(6, 45, 8.871, ra=True)
|
||
|
>>> dec = Angle(-16.716108)
|
||
|
>>> pm_ra = Angle(0, 0, -0.03847, ra=True)
|
||
|
>>> pm_dec = Angle(0, 0, -1.2053)
|
||
|
>>> dist = 2.64
|
||
|
>>> vel = -7.6
|
||
|
>>> alpha, delta = motion_in_space(ra, dec, dist, vel, pm_ra, pm_dec,
|
||
|
... -1000.0)
|
||
|
>>> print(alpha.ra_str(False, 2))
|
||
|
6:45:47.16
|
||
|
>>> print(delta.dms_str(False, 1))
|
||
|
-16:22:56.0
|
||
|
>>> alpha, delta = motion_in_space(ra, dec, dist, vel, pm_ra, pm_dec,
|
||
|
... -4000.0)
|
||
|
>>> print(alpha.ra_str(False, 2))
|
||
|
6:47:39.91
|
||
|
>>> print(delta.dms_str(False, 1))
|
||
|
-15:23:30.6
|
||
|
"""
|
||
|
# >>> ra = Angle(101.286962)
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (isinstance(start_ra, Angle) and isinstance(start_dec, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if isinstance(p_motion_ra, (int, float)):
|
||
|
p_motion_ra = Angle(p_motion_ra)
|
||
|
if isinstance(p_motion_dec, (int, float)):
|
||
|
p_motion_dec = Angle(p_motion_dec)
|
||
|
if not (isinstance(p_motion_ra, Angle) and
|
||
|
isinstance(p_motion_dec, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if not (
|
||
|
isinstance(distance, (int, float))
|
||
|
and isinstance(velocity, (int, float))
|
||
|
and isinstance(time, (int, float))
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
dr = velocity / 977792.0
|
||
|
x = distance * cos(start_dec.rad()) * cos(start_ra.rad())
|
||
|
y = distance * cos(start_dec.rad()) * sin(start_ra.rad())
|
||
|
z = distance * sin(start_dec.rad())
|
||
|
dx = (
|
||
|
(x / distance) * dr
|
||
|
- z * p_motion_dec.rad() * cos(start_ra.rad())
|
||
|
- y * p_motion_ra.rad()
|
||
|
)
|
||
|
dy = (
|
||
|
(y / distance) * dr
|
||
|
- z * p_motion_dec.rad() * sin(start_ra.rad())
|
||
|
+ x * p_motion_ra.rad()
|
||
|
)
|
||
|
dz = ((z / distance) * dr + distance *
|
||
|
p_motion_dec.rad() * cos(start_dec.rad()))
|
||
|
xp = x + time * dx
|
||
|
yp = y + time * dy
|
||
|
zp = z + time * dz
|
||
|
final_ra = atan2(yp, xp)
|
||
|
final_dec = atan(zp / sqrt(xp * xp + yp * yp))
|
||
|
# Convert results to Angles. Please note results are in radians
|
||
|
final_ra = Angle(final_ra, radians=True)
|
||
|
final_dec = Angle(final_dec, radians=True)
|
||
|
return (final_ra, final_dec)
|
||
|
|
||
|
|
||
|
def equatorial2ecliptical(right_ascension, declination, obliquity):
|
||
|
"""This function converts from equatorial coordinated (right ascension and
|
||
|
declination) to ecliptical coordinates (longitude and latitude).
|
||
|
|
||
|
:param right_ascension: Right ascension, as an Angle object
|
||
|
:type start_epoch: :py:class:`Angle`
|
||
|
:param declination: Declination, as an Angle object
|
||
|
:type start_epoch: :py:class:`Angle`
|
||
|
:param obliquity: Obliquity of the ecliptic, as an Angle object
|
||
|
:type obliquity: :py:class:`Angle`
|
||
|
|
||
|
:returns: Ecliptical coordinates (longitude, latitude, in that order),
|
||
|
given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> ra = Angle(7, 45, 18.946, ra=True)
|
||
|
>>> dec = Angle(28, 1, 34.26)
|
||
|
>>> epsilon = Angle(23.4392911)
|
||
|
>>> lon, lat = equatorial2ecliptical(ra, dec, epsilon)
|
||
|
>>> print(round(lon(), 5))
|
||
|
113.21563
|
||
|
>>> print(round(lat(), 5))
|
||
|
6.68417
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(right_ascension, Angle)
|
||
|
and isinstance(declination, Angle)
|
||
|
and isinstance(obliquity, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
ra = right_ascension.rad()
|
||
|
dec = declination.rad()
|
||
|
eps = obliquity.rad()
|
||
|
lon = atan2((sin(ra) * cos(eps) + tan(dec) * sin(eps)), cos(ra))
|
||
|
lat = asin(sin(dec) * cos(eps) - cos(dec) * sin(eps) * sin(ra))
|
||
|
lon = Angle(lon, radians=True)
|
||
|
lon = lon.to_positive()
|
||
|
lat = Angle(lat, radians=True)
|
||
|
return (lon, lat)
|
||
|
|
||
|
|
||
|
def ecliptical2equatorial(longitude, latitude, obliquity):
|
||
|
"""This function converts from ecliptical coordinates (longitude and
|
||
|
latitude) to equatorial coordinated (right ascension and declination).
|
||
|
|
||
|
:param longitude: Ecliptical longitude, as an Angle object
|
||
|
:type longitude: :py:class:`Angle`
|
||
|
:param latitude: Ecliptical latitude, as an Angle object
|
||
|
:type latitude: :py:class:`Angle`
|
||
|
:param obliquity: Obliquity of the ecliptic, as an Angle object
|
||
|
:type obliquity: :py:class:`Angle`
|
||
|
|
||
|
:returns: Equatorial coordinates (right ascension, declination, in that
|
||
|
order), given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> lon = Angle(113.21563)
|
||
|
>>> lat = Angle(6.68417)
|
||
|
>>> epsilon = Angle(23.4392911)
|
||
|
>>> ra, dec = ecliptical2equatorial(lon, lat, epsilon)
|
||
|
>>> print(ra.ra_str(n_dec=3))
|
||
|
7h 45' 18.946''
|
||
|
>>> print(dec.dms_str(n_dec=2))
|
||
|
28d 1' 34.26''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(longitude, Angle)
|
||
|
and isinstance(latitude, Angle)
|
||
|
and isinstance(obliquity, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
lon = longitude.rad()
|
||
|
lat = latitude.rad()
|
||
|
eps = obliquity.rad()
|
||
|
ra = atan2((sin(lon) * cos(eps) - tan(lat) * sin(eps)), cos(lon))
|
||
|
dec = asin(sin(lat) * cos(eps) + cos(lat) * sin(eps) * sin(lon))
|
||
|
ra = Angle(ra, radians=True)
|
||
|
ra = ra.to_positive()
|
||
|
dec = Angle(dec, radians=True)
|
||
|
return (ra, dec)
|
||
|
|
||
|
|
||
|
def equatorial2horizontal(hour_angle, declination, geo_latitude):
|
||
|
"""This function converts from equatorial coordinates (right ascension and
|
||
|
declination) to local horizontal coordinates (azimuth and elevation).
|
||
|
|
||
|
Following Meeus' convention, the azimuth is measured westward from the
|
||
|
SOUTH. If you want the azimuth to be measured from the north (common custom
|
||
|
between navigators and meteorologits), you should add 180 degrees.
|
||
|
|
||
|
The hour angle (H) comprises information about the sidereal time, the
|
||
|
observer's geodetic longitude (positive west from Greenwich) and the right
|
||
|
ascension. If theta is the local sidereal time, theta0 the sidereal time at
|
||
|
Greenwich, lon the observer's longitude and ra the right ascension, the
|
||
|
following expressions hold:
|
||
|
|
||
|
H = theta - ra
|
||
|
H = theta0 - lon - ra
|
||
|
|
||
|
:param hour_angle: Hour angle, as an Angle object
|
||
|
:type hour_angle: :py:class:`Angle`
|
||
|
:param declination: Declination, as an Angle object
|
||
|
:type declination: :py:class:`Angle`
|
||
|
:param geo_latitude: Geodetic latitude of the observer, as an Angle object
|
||
|
:type geo_latitude: :py:class:`Angle`
|
||
|
|
||
|
:returns: Local horizontal coordinates (azimuth, elevation, in that order),
|
||
|
given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> lon = Angle(77, 3, 56)
|
||
|
>>> lat = Angle(38, 55, 17)
|
||
|
>>> ra = Angle(23, 9, 16.641, ra=True)
|
||
|
>>> dec = Angle(-6, 43, 11.61)
|
||
|
>>> theta0 = Angle(8, 34, 57.0896, ra=True)
|
||
|
>>> eps = Angle(23, 26, 36.87)
|
||
|
>>> delta = Angle(0, 0, ((-3.868*cos(eps.rad()))/15.0), ra=True)
|
||
|
>>> theta0 += delta
|
||
|
>>> h = theta0 - lon - ra
|
||
|
>>> azi, ele = equatorial2horizontal(h, dec, lat)
|
||
|
>>> print(round(azi, 3))
|
||
|
68.034
|
||
|
>>> print(round(ele, 3))
|
||
|
15.125
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(hour_angle, Angle)
|
||
|
and isinstance(declination, Angle)
|
||
|
and isinstance(geo_latitude, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
h = hour_angle.rad()
|
||
|
dec = declination.rad()
|
||
|
lat = geo_latitude.rad()
|
||
|
azi = atan2(sin(h), (cos(h) * sin(lat) - tan(dec) * cos(lat)))
|
||
|
ele = asin(sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h))
|
||
|
azi = Angle(azi, radians=True)
|
||
|
ele = Angle(ele, radians=True)
|
||
|
return (azi, ele)
|
||
|
|
||
|
|
||
|
def horizontal2equatorial(azimuth, elevation, geo_latitude):
|
||
|
"""This function converts from local horizontal coordinates (azimuth and
|
||
|
elevation) to equatorial coordinates (right ascension and declination).
|
||
|
|
||
|
Following Meeus' convention, the azimuth is measured westward from the
|
||
|
SOUTH.
|
||
|
|
||
|
This function returns the hour angle and the declination. The hour angle
|
||
|
(H) comprises information about the sidereal time, the observer's geodetic
|
||
|
longitude (positive west from Greenwich) and the right ascension. If theta
|
||
|
is the local sidereal time, theta0 the sidereal time at Greenwich, lon the
|
||
|
observer's longitude and ra the right ascension, the following expressions
|
||
|
hold:
|
||
|
|
||
|
H = theta - ra
|
||
|
H = theta0 - lon - ra
|
||
|
|
||
|
:param azimuth: Azimuth, measured westward from south, as an Angle object
|
||
|
:type azimuth: :py:class:`Angle`
|
||
|
:param elevation: Elevation from the horizon, as an Angle object
|
||
|
:type elevation: :py:class:`Angle`
|
||
|
:param geo_latitude: Geodetic latitude of the observer, as an Angle object
|
||
|
:type geo_latitude: :py:class:`Angle`
|
||
|
|
||
|
:returns: Equatorial coordinates (as hour angle and declination, in that
|
||
|
order), given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> azi = Angle(68.0337)
|
||
|
>>> ele = Angle(15.1249)
|
||
|
>>> lat = Angle(38, 55, 17)
|
||
|
>>> h, dec = horizontal2equatorial(azi, ele, lat)
|
||
|
>>> print(round(h, 4))
|
||
|
64.3521
|
||
|
>>> print(dec.dms_str(n_dec=0))
|
||
|
-6d 43' 12.0''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(azimuth, Angle)
|
||
|
and isinstance(elevation, Angle)
|
||
|
and isinstance(geo_latitude, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
azi = azimuth.rad()
|
||
|
ele = elevation.rad()
|
||
|
lat = geo_latitude.rad()
|
||
|
h = atan2(sin(azi), (cos(azi) * sin(lat) + tan(ele) * cos(lat)))
|
||
|
dec = asin(sin(lat) * sin(ele) - cos(lat) * cos(ele) * cos(azi))
|
||
|
h = Angle(h, radians=True)
|
||
|
dec = Angle(dec, radians=True)
|
||
|
return (h, dec)
|
||
|
|
||
|
|
||
|
def equatorial2galactic(right_ascension, declination):
|
||
|
"""This function converts from equatorial coordinates (right ascension and
|
||
|
declination) to galactic coordinates (longitude and latitude).
|
||
|
|
||
|
The current galactic system of coordinates was defined by the International
|
||
|
Astronomical Union in 1959, using the standard equatorial system of epoch
|
||
|
B1950.0.
|
||
|
|
||
|
:param right_ascension: Right ascension, as an Angle object
|
||
|
:type right_ascension: :py:class:`Angle`
|
||
|
:param declination: Declination, as an Angle object
|
||
|
:type declination: :py:class:`Angle`
|
||
|
|
||
|
:returns: Galactic coordinates (longitude and latitude, in that order),
|
||
|
given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> ra = Angle(17, 48, 59.74, ra=True)
|
||
|
>>> dec = Angle(-14, 43, 8.2)
|
||
|
>>> lon, lat = equatorial2galactic(ra, dec)
|
||
|
>>> print(round(lon, 4))
|
||
|
12.9593
|
||
|
>>> print(round(lat, 4))
|
||
|
6.0463
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (isinstance(right_ascension, Angle) and
|
||
|
isinstance(declination, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
ra = right_ascension.rad()
|
||
|
dec = declination.rad()
|
||
|
c1 = Angle(192.25)
|
||
|
c1 = c1.rad()
|
||
|
c1ra = c1 - ra
|
||
|
c2 = Angle(27.4)
|
||
|
c2 = c2.rad()
|
||
|
x = atan2(sin(c1ra), (cos(c1ra) * sin(c2) - tan(dec) * cos(c2)))
|
||
|
lon = Angle(-x, radians=True)
|
||
|
lon = 303.0 + lon
|
||
|
lon = lon.to_positive()
|
||
|
lat = asin(sin(dec) * sin(c2) + cos(dec) * cos(c2) * cos(c1ra))
|
||
|
lat = Angle(lat, radians=True)
|
||
|
return (lon, lat)
|
||
|
|
||
|
|
||
|
def galactic2equatorial(longitude, latitude):
|
||
|
"""This function converts from galactic coordinates (longitude and latitude)
|
||
|
to equatorial coordinates (right ascension and declination).
|
||
|
|
||
|
The current galactic system of coordinates was defined by the International
|
||
|
Astronomical Union in 1959, using the standard equatorial system of epoch
|
||
|
B1950.0.
|
||
|
|
||
|
:param longitude: Longitude, as an Angle object
|
||
|
:type longitude: :py:class:`Angle`
|
||
|
:param latitude: Latitude, as an Angle object
|
||
|
:type latitude: :py:class:`Angle`
|
||
|
|
||
|
:returns: Equatorial coordinates (right ascension and declination, in that
|
||
|
order), given as two :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> lon = Angle(12.9593)
|
||
|
>>> lat = Angle(6.0463)
|
||
|
>>> ra, dec = galactic2equatorial(lon, lat)
|
||
|
>>> print(ra.ra_str(n_dec=1))
|
||
|
17h 48' 59.7''
|
||
|
>>> print(dec.dms_str(n_dec=0))
|
||
|
-14d 43' 8.0''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (isinstance(longitude, Angle) and isinstance(latitude, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
lon = longitude.rad()
|
||
|
lat = latitude.rad()
|
||
|
c1 = Angle(123.0)
|
||
|
c1 = c1.rad()
|
||
|
c2 = Angle(27.4)
|
||
|
c2 = c2.rad()
|
||
|
lc1 = lon - c1
|
||
|
y = atan2(sin(lc1), (cos(lc1) * sin(c2) - tan(lat) * cos(c2)))
|
||
|
y = Angle(y, radians=True)
|
||
|
ra = y + 12.25
|
||
|
ra.to_positive()
|
||
|
dec = asin(sin(lat) * sin(c2) + cos(lat) * cos(c2) * cos(lc1))
|
||
|
dec = Angle(dec, radians=True)
|
||
|
return (ra, dec)
|
||
|
|
||
|
|
||
|
def parallactic_angle(hour_angle, declination, geo_latitude):
|
||
|
"""This function computes the parallactic angle, an apparent rotation that
|
||
|
appears because celestial bodies move along parallel circles. By
|
||
|
convention, the parallactic angle is negative before the passage through
|
||
|
the southern meridian (in the north hemisphere), and positive afterwards.
|
||
|
Exactly on the meridian, its value is zero.
|
||
|
|
||
|
Please note that when the celestial body is exactly at the zenith, the
|
||
|
parallactic angle is not defined, and this function will return 'None'.
|
||
|
|
||
|
The hour angle (H) comprises information about the sidereal time, the
|
||
|
observer's geodetic longitude (positive west from Greenwich) and the right
|
||
|
ascension. If theta is the local sidereal time, theta0 the sidereal time at
|
||
|
Greenwich, lon the observer's longitude and ra the right ascension, the
|
||
|
following expressions hold:
|
||
|
|
||
|
H = theta - ra
|
||
|
H = theta0 - lon - ra
|
||
|
|
||
|
:param hour_angle: Hour angle, as an Angle object
|
||
|
:type hour_angle: :py:class:`Angle`
|
||
|
:param declination: Declination, as an Angle object
|
||
|
:type declination: :py:class:`Angle`
|
||
|
:param geo_latitude: Geodetic latitude of the observer, as an Angle object
|
||
|
:type geo_latitude: :py:class:`Angle`
|
||
|
|
||
|
:returns: Parallactic angle as an py:class:`Angle` object
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> hour_angle = Angle(0.0)
|
||
|
>>> declination = Angle(45.0)
|
||
|
>>> latitude = Angle(50.0)
|
||
|
>>> q = parallactic_angle(hour_angle, declination, latitude)
|
||
|
>>> print(q.dms_str(n_dec=1))
|
||
|
0d 0' 0.0''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(hour_angle, Angle)
|
||
|
and isinstance(declination, Angle)
|
||
|
and isinstance(geo_latitude, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
h = hour_angle.rad()
|
||
|
dec = declination.rad()
|
||
|
lat = geo_latitude.rad()
|
||
|
den = tan(lat) * cos(dec) - sin(dec) * cos(h)
|
||
|
if abs(den) < TOL:
|
||
|
return None
|
||
|
q = atan2(sin(h), den)
|
||
|
q = Angle(q, radians=True)
|
||
|
return q
|
||
|
|
||
|
|
||
|
def ecliptic_horizon(local_sidereal_time, geo_latitude, obliquity):
|
||
|
"""This function returns the longitudes of the two points of the ecliptic
|
||
|
which are on the horizon, as well as the angle between the ecliptic and the
|
||
|
horizon.
|
||
|
|
||
|
:param local_sidereal_time: Local sidereal time, as an Angle object
|
||
|
:type local_sidereal_time: :py:class:`Angle`
|
||
|
:param geo_latitude: Geodetic latitude, as an Angle object
|
||
|
:type geo_latitude: :py:class:`Angle`
|
||
|
:param obliquity: Obliquity of the ecliptic, as an Angle object
|
||
|
:type obliquity: :py:class:`Angle`
|
||
|
|
||
|
:returns: Longitudes of the two points of the ecliptic which are on the
|
||
|
horizon, and the angle between the ecliptic and the horizon (in that
|
||
|
order), given as three :class:`Angle` objects inside a tuple
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> sidereal_time = Angle(5.0, ra=True)
|
||
|
>>> lat = Angle(51.0)
|
||
|
>>> epsilon = Angle(23.44)
|
||
|
>>> lon1, lon2, i = ecliptic_horizon(sidereal_time, lat, epsilon)
|
||
|
>>> print(lon1.dms_str(n_dec=1))
|
||
|
169d 21' 29.9''
|
||
|
>>> print(lon2.dms_str(n_dec=1))
|
||
|
349d 21' 29.9''
|
||
|
>>> print(round(i, 0))
|
||
|
62.0
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(local_sidereal_time, Angle)
|
||
|
and isinstance(geo_latitude, Angle)
|
||
|
and isinstance(obliquity, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
theta = local_sidereal_time.rad()
|
||
|
lat = geo_latitude.rad()
|
||
|
eps = obliquity.rad()
|
||
|
# First, let's compute the longitudes of the ecliptic points on the horizon
|
||
|
lon1 = atan2(-cos(theta), (sin(eps) * tan(lat) + cos(eps) * sin(theta)))
|
||
|
lon1 = Angle(lon1, radians=True)
|
||
|
lon1.to_positive()
|
||
|
# Get the second point, which is 180 degrees apart
|
||
|
if lon1 < 180.0:
|
||
|
lon2 = lon1 + 180.0
|
||
|
else:
|
||
|
lon2 = lon1
|
||
|
lon1 = lon2 - 180.0
|
||
|
# Now, compute the angle between the ecliptic and the horizon
|
||
|
i = acos(cos(eps) * sin(lat) - sin(eps) * cos(lat) * sin(theta))
|
||
|
i = Angle(i, radians=True)
|
||
|
return (lon1, lon2, i)
|
||
|
|
||
|
|
||
|
def ecliptic_equator(longitude, latitude, obliquity):
|
||
|
"""This function returns the angle between the direction of the northern
|
||
|
celestial pole and the direction of the north pole of the ecliptic, taking
|
||
|
as reference the point whose ecliptic longitude and latitude are given.
|
||
|
|
||
|
Please note that if we make latitude=0, the result is the angle between the
|
||
|
ecliptic (at the given ecliptical longitude) and the east-west direction on
|
||
|
the celestial sphere.
|
||
|
|
||
|
:param longitude: Ecliptical longitude, as an Angle object
|
||
|
:type longitude: :py:class:`Angle`
|
||
|
:param latitude: Ecliptical latitude, as an Angle object
|
||
|
:type latitude: :py:class:`Angle`
|
||
|
:param obliquity: Obliquity of the ecliptic, as an Angle object
|
||
|
:type obliquity: :py:class:`Angle`
|
||
|
|
||
|
:returns: Angle between the direction of the northern celestial pole and
|
||
|
the direction of the north pole of the ecliptic, given as one
|
||
|
:class:`Angle` object
|
||
|
:rtype: :class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> lon = Angle(0.0)
|
||
|
>>> lat = Angle(0.0)
|
||
|
>>> eps = Angle(23.5)
|
||
|
>>> ang_ecl_equ = ecliptic_equator(lon, lat, eps)
|
||
|
>>> print(ang_ecl_equ.dms_str(n_dec=1))
|
||
|
156d 30' 0.0''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(longitude, Angle)
|
||
|
and isinstance(latitude, Angle)
|
||
|
and isinstance(obliquity, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
lon = longitude.rad()
|
||
|
lat = latitude.rad()
|
||
|
eps = obliquity.rad()
|
||
|
q = (atan2((cos(lon) * tan(eps)), (sin(lat) *
|
||
|
sin(lon) * tan(eps) - cos(lat))))
|
||
|
q = Angle(q, radians=True)
|
||
|
return q
|
||
|
|
||
|
|
||
|
def diurnal_path_horizon(declination, geo_latitude):
|
||
|
"""This function returns the angle of the diurnal path of a celestial body
|
||
|
relative to the horizon at the time of its rising or setting.
|
||
|
|
||
|
:param declination: Declination, as an Angle object
|
||
|
:type declination: :py:class:`Angle`
|
||
|
:param geo_latitude: Geodetic latitude, as an Angle object
|
||
|
:type geo_latitude: :py:class:`Angle`
|
||
|
|
||
|
:returns: Angle of the diurnal path of the celestial body relative to the
|
||
|
horizon at the time of rising or setting, given as one
|
||
|
:class:`Angle` object
|
||
|
:rtype: :class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> declination = Angle(23.44)
|
||
|
>>> latitude = Angle(40.0)
|
||
|
>>> path_angle = diurnal_path_horizon(declination, latitude)
|
||
|
>>> print(path_angle.dms_str(n_dec=1))
|
||
|
45d 31' 28.4''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (isinstance(declination, Angle) and
|
||
|
isinstance(geo_latitude, Angle)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
dec = declination.rad()
|
||
|
lat = geo_latitude.rad()
|
||
|
b = tan(dec) * tan(lat)
|
||
|
c = sqrt(1.0 - b * b)
|
||
|
j = atan2(c * cos(dec), tan(lat))
|
||
|
j = Angle(j, radians=True)
|
||
|
return j
|
||
|
|
||
|
|
||
|
def times_rise_transit_set(
|
||
|
longitude,
|
||
|
latitude,
|
||
|
alpha1,
|
||
|
delta1,
|
||
|
alpha2,
|
||
|
delta2,
|
||
|
alpha3,
|
||
|
delta3,
|
||
|
h0,
|
||
|
delta_t,
|
||
|
theta0,
|
||
|
):
|
||
|
"""This function computes the times (in Universal Time UT) of rising,
|
||
|
transit and setting of a given celestial body.
|
||
|
|
||
|
.. note:: If the body is circumpolar there are no rising, transit nor
|
||
|
setting times. In such a case a tuple with None's is returned
|
||
|
|
||
|
.. note:: Care must be taken when interpreting the results. For instance,
|
||
|
if the setting time is **smaller** than the rising time, it means that
|
||
|
it belongs to the **following** day. Also, if the rising time is
|
||
|
**bigger** than the setting time, it belong to the **previous** day.
|
||
|
The same applies to the transit time.
|
||
|
|
||
|
:param longitude: Geodetic longitude, as an Angle object. It is measured
|
||
|
positively west from Greenwich, and negatively to the east.
|
||
|
:type longitude: :py:class:`Angle`
|
||
|
:param latitude: Geodetic latitude, as an Angle object
|
||
|
:type latitude: :py:class:`Angle`
|
||
|
:param alpha1: Apparent right ascension the previous day at 0h TT, as an
|
||
|
Angle object
|
||
|
:type alpha1: :py:class:`Angle`
|
||
|
:param delta1: Apparent declination the previous day at 0h TT, as an Angle
|
||
|
object
|
||
|
:type delta1: :py:class:`Angle`
|
||
|
:param alpha2: Apparent right ascension the current day at 0h TT, as an
|
||
|
Angle object
|
||
|
:type alpha2: :py:class:`Angle`
|
||
|
:param delta2: Apparent declination the current day at 0h TT, as an Angle
|
||
|
object
|
||
|
:type delta2: :py:class:`Angle`
|
||
|
:param alpha3: Apparent right ascension the following day at 0h TT, as an
|
||
|
Angle object
|
||
|
:type alpha3: :py:class:`Angle`
|
||
|
:param delta3: Apparent declination the following day at 0h TT, as an Angle
|
||
|
object
|
||
|
:type delta3: :py:class:`Angle`
|
||
|
:param h0: 'Standard' altitude: the geometric altitude of the center of the
|
||
|
body at the time of apparent rising or setting, as degrees in an Angle
|
||
|
object. It should be -0.5667 deg for stars and planets, -0.8333 deg
|
||
|
for the Sun, and 0.125 deg for the Moon.
|
||
|
:type h0: :py:class:`Angle`
|
||
|
:param delta_t: The difference between Terrestrial Time and Universal Time
|
||
|
(TT - UT) in seconds of time
|
||
|
:type delta_t: float
|
||
|
:param theta0: Apparent sidereal time at 0h TT on the current day for the
|
||
|
meridian of Greenwich, as degrees in an Angle object
|
||
|
:type theta0: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with the times of rising, transit and setting, in that
|
||
|
order, as hours in UT.
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> longitude = Angle(71, 5, 0.0)
|
||
|
>>> latitude = Angle(42, 20, 0.0)
|
||
|
>>> alpha1 = Angle(2, 42, 43.25, ra=True)
|
||
|
>>> delta1 = Angle(18, 2, 51.4)
|
||
|
>>> alpha2 = Angle(2, 46, 55.51, ra=True)
|
||
|
>>> delta2 = Angle(18, 26, 27.3)
|
||
|
>>> alpha3 = Angle(2, 51, 7.69, ra=True)
|
||
|
>>> delta3 = Angle(18, 49, 38.7)
|
||
|
>>> h0 = Angle(-0.5667)
|
||
|
>>> delta_t = 56.0
|
||
|
>>> theta0 = Angle(11, 50, 58.1, ra=True)
|
||
|
>>> rising, transit, setting = times_rise_transit_set(longitude, latitude,\
|
||
|
alpha1, delta1, \
|
||
|
alpha2, delta2, \
|
||
|
alpha3, delta3, h0, \
|
||
|
delta_t, theta0)
|
||
|
>>> print(round(rising, 4))
|
||
|
12.4238
|
||
|
>>> print(round(transit, 3))
|
||
|
19.675
|
||
|
>>> print(round(setting, 3))
|
||
|
2.911
|
||
|
"""
|
||
|
|
||
|
def check_value(m):
|
||
|
while m < 0 or m > 1.0:
|
||
|
if m < 0.0:
|
||
|
m += 1
|
||
|
elif m > 1.0:
|
||
|
m -= 1
|
||
|
return m
|
||
|
|
||
|
def interpol(n, y1, y2, y3):
|
||
|
a = y2 - y1
|
||
|
b = y3 - y2
|
||
|
c = b - a
|
||
|
return y2 + n * (a + b + n * c) / 2.0
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(longitude, Angle)
|
||
|
and isinstance(latitude, Angle)
|
||
|
and isinstance(alpha1, Angle)
|
||
|
and isinstance(delta1, Angle)
|
||
|
and isinstance(alpha2, Angle)
|
||
|
and isinstance(delta2, Angle)
|
||
|
and isinstance(alpha3, Angle)
|
||
|
and isinstance(delta3, Angle)
|
||
|
and isinstance(h0, Angle)
|
||
|
and isinstance(theta0, Angle)
|
||
|
and isinstance(delta_t, (int, float))
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Let's start computing approximate times
|
||
|
h = h0.rad()
|
||
|
lat = latitude.rad()
|
||
|
d2 = delta2.rad()
|
||
|
hh0 = (sin(h) - sin(lat) * sin(d2)) / (cos(lat) * cos(d2))
|
||
|
# Check if the body is circumpolar. In such case, there are no rising,
|
||
|
# transit nor setting times, and a tuple with None's is returned
|
||
|
if abs(hh0) > 1.0:
|
||
|
return (None, None, None)
|
||
|
hh0 = acos(hh0)
|
||
|
hh0 = Angle(hh0, radians=True)
|
||
|
hh0.to_positive()
|
||
|
m0 = (alpha2 + longitude - theta0) / 360.0
|
||
|
m0 = m0() # m0 is an Angle. Convert to float
|
||
|
m1 = m0 - hh0() / 360.0
|
||
|
m2 = m0 + hh0() / 360.0
|
||
|
m0 = check_value(m0)
|
||
|
m1 = check_value(m1)
|
||
|
m2 = check_value(m2)
|
||
|
# Carry out this procedure twice
|
||
|
for _ in range(2):
|
||
|
# Interpolate alpha and delta values for each (m0, m1, m2)
|
||
|
n = m0 + delta_t / 86400.0
|
||
|
transit_alpha = interpol(n, alpha1, alpha2, alpha3)
|
||
|
n = m1 + delta_t / 86400.0
|
||
|
rise_alpha = interpol(n, alpha1, alpha2, alpha3)
|
||
|
rise_delta = interpol(n, delta1, delta2, delta3)
|
||
|
n = m2 + delta_t / 86400.0
|
||
|
set_alpha = interpol(n, alpha1, alpha2, alpha3)
|
||
|
set_delta = interpol(n, delta1, delta2, delta3)
|
||
|
# Compute the hour angles
|
||
|
theta = theta0 + 360.985647 * m0
|
||
|
transit_ha = theta - longitude - transit_alpha
|
||
|
delta_transit = transit_ha / (-360.0)
|
||
|
theta = theta0 + 360.985647 * m1
|
||
|
rise_ha = theta - longitude - rise_alpha
|
||
|
theta = theta0 + 360.985647 * m2
|
||
|
set_ha = theta - longitude - set_alpha
|
||
|
# We need the elevations
|
||
|
azi, rise_ele = equatorial2horizontal(rise_ha, rise_delta, latitude)
|
||
|
azi, set_ele = equatorial2horizontal(set_ha, set_delta, latitude)
|
||
|
delta_rise = (rise_ele - h0) / (
|
||
|
360.0 * cos(rise_delta.rad()) * cos(lat) * sin(rise_ha.rad())
|
||
|
)
|
||
|
delta_set = (set_ele - h0) / (
|
||
|
360.0 * cos(set_delta.rad()) * cos(lat) * sin(set_ha.rad())
|
||
|
)
|
||
|
m0 += delta_transit()
|
||
|
m1 += delta_rise()
|
||
|
m2 += delta_set()
|
||
|
return (m1 * 24.0, m0 * 24.0, m2 * 24.0)
|
||
|
|
||
|
|
||
|
def refraction_apparent2true(apparent_elevation, pressure=1010.0,
|
||
|
temperature=10.0):
|
||
|
"""This function computes the atmospheric refraction converting from the
|
||
|
apparent elevation (i.e., the observed elevation through the air) to the
|
||
|
true, 'airless', elevation.
|
||
|
|
||
|
.. note:: This function, by default, assumes that the atmospheric pressure
|
||
|
is 1010 milibars, and the air temperature is 10 Celsius.
|
||
|
|
||
|
.. note:: Due to the numerous factors that may affect the atmospheric
|
||
|
refraction, especially near the horizon, the values given by this
|
||
|
function are approximate values.
|
||
|
|
||
|
:param apparent_elevation: The elevation, in degrees and as an Angle
|
||
|
object, of a given celestial object when observed through the
|
||
|
normal atmosphere
|
||
|
:type apparent_elevation: :py:class:`Angle`
|
||
|
:param pressure: Atmospheric pressure at the observation point, in milibars
|
||
|
:type pressure: float
|
||
|
:param temperature: Atmospheric temperature at the observation point, in
|
||
|
degrees Celsius
|
||
|
:type temperature: :float
|
||
|
|
||
|
:returns: An Angle object with the true, 'airless' elevation of the
|
||
|
celestial object
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> apparent_elevation = Angle(0, 30, 0.0)
|
||
|
>>> true_elevation = refraction_apparent2true(apparent_elevation)
|
||
|
>>> print(true_elevation.dms_str(n_dec=1))
|
||
|
1' 14.7''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(apparent_elevation, Angle)
|
||
|
and isinstance(pressure, (int, float))
|
||
|
and isinstance(temperature, (int, float))
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
x = apparent_elevation + 7.31 / (apparent_elevation + 4.4)
|
||
|
r = 1.0 / tan(x.rad()) + 0.0013515
|
||
|
r = Angle(r / 60.0) # The 'r' value is in minutes of arc
|
||
|
if pressure != 1010.0 or temperature != 10.0:
|
||
|
r = r * pressure / 1010.0 * 283.0 / (273.0 + temperature)
|
||
|
return apparent_elevation - r
|
||
|
|
||
|
|
||
|
def refraction_true2apparent(true_elevation, pressure=1010.0,
|
||
|
temperature=10.0):
|
||
|
"""This function computes the atmospheric refraction converting from the
|
||
|
true, 'airless', elevation (i.e., the one computed from celestial
|
||
|
coordinates) to the apparent elevation (the observed elevation through the
|
||
|
air)
|
||
|
|
||
|
.. note:: This function, by default, assumes that the atmospheric pressure
|
||
|
is 1010 milibars, and the air temperature is 10 Celsius.
|
||
|
|
||
|
.. note:: Due to the numerous factors that may affect the atmospheric
|
||
|
refraction, especially near the horizon, the values given by this
|
||
|
function are approximate values.
|
||
|
|
||
|
:param true_elevation: The elevation, in degrees and as an Angle
|
||
|
object, of a given celestial object when computed from celestial
|
||
|
coordinates, and assuming there is no atmospheric refraction due to the
|
||
|
air
|
||
|
:type true_elevation: :py:class:`Angle`
|
||
|
:param pressure: Atmospheric pressure at the observation point, in milibars
|
||
|
:type pressure: float
|
||
|
:param temperature: Atmospheric temperature at the observation point, in
|
||
|
degrees Celsius
|
||
|
:type temperature: :float
|
||
|
|
||
|
:returns: An Angle object with the aparent, 'with air' elevation of the
|
||
|
celestial object
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> true_elevation = Angle(0, 33, 14.76)
|
||
|
>>> apparent_elevation = refraction_true2apparent(true_elevation)
|
||
|
>>> print(apparent_elevation.dms_str(n_dec=2))
|
||
|
57' 51.96''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(true_elevation, Angle)
|
||
|
and isinstance(pressure, (int, float))
|
||
|
and isinstance(temperature, (int, float))
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
x = true_elevation + 10.3 / (true_elevation + 5.11)
|
||
|
r = 1.02 / tan(x.rad()) + 0.0019279
|
||
|
r = Angle(r / 60.0) # The 'r' value is in minutes of arc
|
||
|
if pressure != 1010.0 or temperature != 10.0:
|
||
|
r = r * pressure / 1010.0 * 283.0 / (273.0 + temperature)
|
||
|
return true_elevation + r
|
||
|
|
||
|
|
||
|
def angular_separation(alpha1, delta1, alpha2, delta2):
|
||
|
"""This function computes the angular distance between two celestial bodies
|
||
|
whose right ascensions and declinations are given.
|
||
|
|
||
|
.. note:: It is possible to use this formula with ecliptial (celestial)
|
||
|
longitudes and latitudes instead of right ascensions and declinations,
|
||
|
respectively.
|
||
|
|
||
|
:param alpha1: Right ascension of celestial body #1, as an Angle object
|
||
|
:type alpha1: :py:class:`Angle`
|
||
|
:param delta1: Declination of celestial body #1, as an Angle object
|
||
|
:type delta1: :py:class:`Angle`
|
||
|
:param alpha2: Right ascension of celestial body #2, as an Angle object
|
||
|
:type alpha2: :py:class:`Angle`
|
||
|
:param delta2: Declination of celestial body #2, as an Angle object
|
||
|
:type delta2: :py:class:`Angle`
|
||
|
|
||
|
:returns: An Angle object with the angular separation between the given
|
||
|
celestial objects
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha1 = Angle(14, 15, 39.7, ra=True)
|
||
|
>>> delta1 = Angle(19, 10, 57.0)
|
||
|
>>> alpha2 = Angle(13, 25, 11.6, ra=True)
|
||
|
>>> delta2 = Angle(-11, 9, 41.0)
|
||
|
>>> sep_ang = angular_separation(alpha1, delta1, alpha2, delta2)
|
||
|
>>> print(round(sep_ang, 3))
|
||
|
32.793
|
||
|
"""
|
||
|
|
||
|
# Let's define an auxiliary function
|
||
|
def hav(theta):
|
||
|
"""Function to compute the haversine (hav)"""
|
||
|
return (1.0 - cos(theta)) / 2.0
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha1, Angle)
|
||
|
and isinstance(delta1, Angle)
|
||
|
and isinstance(alpha2, Angle)
|
||
|
and isinstance(delta2, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
dalpha = alpha1 - alpha2
|
||
|
dalpha = dalpha.rad()
|
||
|
ddelta = delta1 - delta2
|
||
|
ddelta = ddelta.rad()
|
||
|
d1 = delta1.rad()
|
||
|
d2 = delta2.rad()
|
||
|
theta = 2.0 * asin(sqrt(hav(ddelta) + cos(d1) * cos(d2) * hav(dalpha)))
|
||
|
theta = Angle(theta, radians=True)
|
||
|
return theta
|
||
|
|
||
|
|
||
|
def minimum_angular_separation(
|
||
|
alpha1_1,
|
||
|
delta1_1,
|
||
|
alpha1_2,
|
||
|
delta1_2,
|
||
|
alpha1_3,
|
||
|
delta1_3,
|
||
|
alpha2_1,
|
||
|
delta2_1,
|
||
|
alpha2_2,
|
||
|
delta2_2,
|
||
|
alpha2_3,
|
||
|
delta2_3,
|
||
|
):
|
||
|
"""Given the positions at three different instants of times (equidistant)
|
||
|
of two celestial objects, this function computes the minimum angular
|
||
|
distance that will be achieved within that interval of time.
|
||
|
|
||
|
.. note:: Suffix '1 _' is for the first celestial object, and '2 _' is for
|
||
|
the second one.
|
||
|
|
||
|
.. note:: This function provides as output the 'n' fraction of time when
|
||
|
the minimum angular separation is achieved. For that, the epoch in the
|
||
|
middle is assigned the value "n = 0". Therefore, n < 0 is for times
|
||
|
**before** the middle epoch, and n > 0 is for times **after** the
|
||
|
middle epoch.
|
||
|
|
||
|
:param alpha1_1: First right ascension of celestial body #1, as an Angle
|
||
|
object
|
||
|
:type alpha1_1: :py:class:`Angle`
|
||
|
:param delta1_1: First declination of celestial body #1, as an Angle object
|
||
|
:type delta1_1: :py:class:`Angle`
|
||
|
:param alpha1_2: Second right ascension of celestial body #1, as an Angle
|
||
|
object
|
||
|
:type alpha1_2: :py:class:`Angle`
|
||
|
:param delta1_2: Second declination of celestial body #1, as Angle object
|
||
|
:type delta1_2: :py:class:`Angle`
|
||
|
:param alpha1_3: Third right ascension of celestial body #1, as an Angle
|
||
|
object
|
||
|
:type alpha1_3: :py:class:`Angle`
|
||
|
:param delta1_3: Third declination of celestial body #1, as an Angle object
|
||
|
:type delta1_3: :py:class:`Angle`
|
||
|
:param alpha2_1: First right ascension of celestial body #2, as an Angle
|
||
|
object
|
||
|
:type alpha2_1: :py:class:`Angle`
|
||
|
:param delta2_1: First declination of celestial body #2, as an Angle object
|
||
|
:type delta2_1: :py:class:`Angle`
|
||
|
:param alpha2_2: Second right ascension of celestial body #2, as an Angle
|
||
|
object
|
||
|
:type alpha2_2: :py:class:`Angle`
|
||
|
:param delta2_2: Second declination of celestial body #2, as Angle object
|
||
|
:type delta2_2: :py:class:`Angle`
|
||
|
:param alpha2_3: Third right ascension of celestial body #2, as an Angle
|
||
|
object
|
||
|
:type alpha2_3: :py:class:`Angle`
|
||
|
:param delta2_3: Third declination of celestial body #2, as an Angle object
|
||
|
:type delta2_3: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with two components: The first component is a float
|
||
|
containing the 'n' fraction of time when the minimum angular separation
|
||
|
is achieved. The second component is an Angle object containing the
|
||
|
minimum angular separation between the given celestial objects
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha1_1 = Angle(10, 29, 44.27, ra=True)
|
||
|
>>> delta1_1 = Angle(11, 2, 5.9)
|
||
|
>>> alpha2_1 = Angle(10, 33, 29.64, ra=True)
|
||
|
>>> delta2_1 = Angle(10, 40, 13.2)
|
||
|
>>> alpha1_2 = Angle(10, 36, 19.63, ra=True)
|
||
|
>>> delta1_2 = Angle(10, 29, 51.7)
|
||
|
>>> alpha2_2 = Angle(10, 33, 57.97, ra=True)
|
||
|
>>> delta2_2 = Angle(10, 37, 33.4)
|
||
|
>>> alpha1_3 = Angle(10, 43, 1.75, ra=True)
|
||
|
>>> delta1_3 = Angle(9, 55, 16.7)
|
||
|
>>> alpha2_3 = Angle(10, 34, 26.22, ra=True)
|
||
|
>>> delta2_3 = Angle(10, 34, 53.9)
|
||
|
>>> a = minimum_angular_separation(alpha1_1, delta1_1, alpha1_2, delta1_2,\
|
||
|
alpha1_3, delta1_3, alpha2_1, delta2_1,\
|
||
|
alpha2_2, delta2_2, alpha2_3, delta2_3)
|
||
|
>>> print(round(a[0], 6))
|
||
|
-0.370726
|
||
|
>>> print(a[1].dms_str(n_dec=0))
|
||
|
3' 44.0''
|
||
|
"""
|
||
|
|
||
|
# Let's define some auxiliary functions
|
||
|
def k_factor(d1, d_a):
|
||
|
"""This auxiliary function returns arcseconds, input is in radians"""
|
||
|
return (206264.8062 / (1.0 + sin(d1) * sin(d1) *
|
||
|
tan(d_a) * tan(d_a / 2.0)))
|
||
|
|
||
|
def u_factor(k, d1, d_a, d_d):
|
||
|
"""Input is in radians, except for k (arcseconds)"""
|
||
|
return -k * (1.0 - tan(d1) * sin(d_d)) * cos(d1) * tan(d_a)
|
||
|
|
||
|
def v_factor(k, d1, d_a, d_d):
|
||
|
"""Input is in radians, except for k (arcseconds)"""
|
||
|
return k * (sin(d_d) + sin(d1) * cos(d1) * tan(d_a) * tan(d_a / 2.0))
|
||
|
|
||
|
def u_prime(n, u1, u2, u3):
|
||
|
return (u3 - u1) / 2.0 + n * (u1 + u3 - 2.0 * u2)
|
||
|
|
||
|
def delta_n(u, u_p, v, v_p):
|
||
|
return -(u * u_p + v * v_p) / (u_p * u_p + v_p * v_p)
|
||
|
|
||
|
def interpol(n, y1, y2, y3):
|
||
|
"""This is formula 3.3 from Meeus book"""
|
||
|
a = y2 - y1
|
||
|
b = y3 - y2
|
||
|
c = b - a
|
||
|
return y2 + n * (a + b + n * c) / 2.0
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha1_1, Angle)
|
||
|
and isinstance(delta1_1, Angle)
|
||
|
and isinstance(alpha1_2, Angle)
|
||
|
and isinstance(delta1_2, Angle)
|
||
|
and isinstance(alpha1_3, Angle)
|
||
|
and isinstance(delta1_3, Angle)
|
||
|
and isinstance(alpha2_1, Angle)
|
||
|
and isinstance(delta2_1, Angle)
|
||
|
and isinstance(alpha2_2, Angle)
|
||
|
and isinstance(delta2_2, Angle)
|
||
|
and isinstance(alpha2_3, Angle)
|
||
|
and isinstance(delta2_3, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Let's define two dictionaries to store the intermediate results
|
||
|
u = {}
|
||
|
v = {}
|
||
|
# Compute intermediate results for first epoch
|
||
|
d1 = delta1_1.rad()
|
||
|
d_a = alpha2_1.rad() - alpha1_1.rad()
|
||
|
d_d = delta2_1.rad() - delta1_1.rad()
|
||
|
k = k_factor(d1, d_a)
|
||
|
u[1] = u_factor(k, d1, d_a, d_d)
|
||
|
v[1] = v_factor(k, d1, d_a, d_d)
|
||
|
# Compute intermediate results for second epoch
|
||
|
d1 = delta1_2.rad()
|
||
|
d_a = alpha2_2.rad() - alpha1_2.rad()
|
||
|
d_d = delta2_2.rad() - delta1_2.rad()
|
||
|
k = k_factor(d1, d_a)
|
||
|
u[2] = u_factor(k, d1, d_a, d_d)
|
||
|
v[2] = v_factor(k, d1, d_a, d_d)
|
||
|
# Compute intermediate results for third epoch
|
||
|
d1 = delta1_3.rad()
|
||
|
d_a = alpha2_3.rad() - alpha1_3.rad()
|
||
|
d_d = delta2_3.rad() - delta1_3.rad()
|
||
|
k = k_factor(d1, d_a)
|
||
|
u[3] = u_factor(k, d1, d_a, d_d)
|
||
|
v[3] = v_factor(k, d1, d_a, d_d)
|
||
|
# Iterate to find the solution
|
||
|
n = 0.0
|
||
|
dn = 999999.9
|
||
|
while abs(dn) > 0.000001:
|
||
|
uu = interpol(n, u[1], u[2], u[3])
|
||
|
vv = interpol(n, v[1], v[2], v[3])
|
||
|
up = u_prime(n, u[1], u[2], u[3])
|
||
|
vp = u_prime(n, v[1], v[2], v[3])
|
||
|
dn = delta_n(uu, up, vv, vp)
|
||
|
n += dn
|
||
|
# Let's compute the minimum distance, in arcseconds
|
||
|
arcsec = sqrt(uu * uu + vv * vv)
|
||
|
d = Angle(0, 0, arcsec) # Convert to an Angle object
|
||
|
return n, d
|
||
|
|
||
|
|
||
|
def relative_position_angle(alpha1, delta1, alpha2, delta2):
|
||
|
"""This function computes the position angle P of a body with respect to
|
||
|
another body.
|
||
|
|
||
|
:param alpha1: Right ascension of celestial body #1, as an Angle object
|
||
|
:type alpha1: :py:class:`Angle`
|
||
|
:param delta1: Declination of celestial body #1, as an Angle object
|
||
|
:type delta1: :py:class:`Angle`
|
||
|
:param alpha2: Right ascension of celestial body #2, as an Angle object
|
||
|
:type alpha2: :py:class:`Angle`
|
||
|
:param delta2: Declination of celestial body #2, as an Angle object
|
||
|
:type delta2: :py:class:`Angle`
|
||
|
|
||
|
:returns: An Angle object with the relative position angle between the
|
||
|
given celestial objects
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha1 = Angle(14, 15, 39.7, ra=True)
|
||
|
>>> delta1 = Angle(19, 10, 57.0)
|
||
|
>>> alpha2 = Angle(14, 15, 39.7, ra=True)
|
||
|
>>> delta2 = Angle(-11, 9, 41.0)
|
||
|
>>> pos_ang = relative_position_angle(alpha1, delta1, alpha2, delta2)
|
||
|
>>> print(round(pos_ang, 1))
|
||
|
0.0
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha1, Angle)
|
||
|
and isinstance(delta1, Angle)
|
||
|
and isinstance(alpha2, Angle)
|
||
|
and isinstance(delta2, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
da = alpha1 - alpha2
|
||
|
da = da.rad()
|
||
|
d1 = delta1.rad()
|
||
|
d2 = delta2.rad()
|
||
|
p = atan2(sin(da), (cos(d2) * tan(d1) - sin(d2) * cos(da)))
|
||
|
p = Angle(p, radians=True)
|
||
|
return p
|
||
|
|
||
|
|
||
|
def planetary_conjunction(alpha1_list, delta1_list, alpha2_list, delta2_list):
|
||
|
"""Given the positions of two planets passing near each other, this
|
||
|
function computes the time of conjunction in right ascension, and the
|
||
|
difference in declination of the two bodies at that time.
|
||
|
|
||
|
.. note:: This function provides as output the 'n' fraction of time when
|
||
|
the minimum angular separation is achieved. For that, the epoch in the
|
||
|
middle is assigned the value "n = 0". Therefore, n < 0 is for times
|
||
|
**before** the middle epoch, and n > 0 is for times **after** the
|
||
|
middle epoch.
|
||
|
|
||
|
.. note:: When the entries in the input values are more than three and
|
||
|
even, the last entry is discarted and an odd number of entries will be
|
||
|
used.
|
||
|
|
||
|
:param alpha1_list: List (or tuple) containing the right ascensions (as
|
||
|
Angle objects) for object #1 (minimum 3 entries)
|
||
|
:type alpha1_list: list, tuple of :py:class:`Angle`
|
||
|
:param delta1_list: List (or tuple) containing the declinations (as Angle
|
||
|
objects) for object #1 (minimum 3 entries)
|
||
|
:type delta1_list: list, tuple of :py:class:`Angle`
|
||
|
:param alpha2_list: List (or tuple) containing the right ascensions (as
|
||
|
Angle objects) for object #2 (minimum 3 entries)
|
||
|
:type alpha2_list: list, tuple of :py:class:`Angle`
|
||
|
:param delta2_list: List (or tuple) containing the declinations (as Angle
|
||
|
objects) for object #2 (minimum 3 entries)
|
||
|
:type delta2_list: list, tuple of :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with two components: The first component is a float
|
||
|
containing the 'n' fraction of time when the conjunction occurs. The
|
||
|
second component is an Angle object containing the declination
|
||
|
separation between the given objects at conjunction epoch
|
||
|
:rtype: tuple
|
||
|
:raises: ValueError if input values have less than three entries or they
|
||
|
don't have the same number of entries.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha1_1 = Angle(10, 24, 30.125, ra=True)
|
||
|
>>> delta1_1 = Angle( 6, 26, 32.05)
|
||
|
>>> alpha1_2 = Angle(10, 25, 0.342, ra=True)
|
||
|
>>> delta1_2 = Angle( 6, 10, 57.72)
|
||
|
>>> alpha1_3 = Angle(10, 25, 12.515, ra=True)
|
||
|
>>> delta1_3 = Angle( 5, 57, 33.08)
|
||
|
>>> alpha1_4 = Angle(10, 25, 6.235, ra=True)
|
||
|
>>> delta1_4 = Angle( 5, 46, 27.07)
|
||
|
>>> alpha1_5 = Angle(10, 24, 41.185, ra=True)
|
||
|
>>> delta1_5 = Angle( 5, 37, 48.45)
|
||
|
>>> alpha2_1 = Angle(10, 27, 27.175, ra=True)
|
||
|
>>> delta2_1 = Angle( 4, 4, 41.83)
|
||
|
>>> alpha2_2 = Angle(10, 26, 32.410, ra=True)
|
||
|
>>> delta2_2 = Angle( 3, 55, 54.66)
|
||
|
>>> alpha2_3 = Angle(10, 25, 29.042, ra=True)
|
||
|
>>> delta2_3 = Angle( 3, 48, 3.51)
|
||
|
>>> alpha2_4 = Angle(10, 24, 17.191, ra=True)
|
||
|
>>> delta2_4 = Angle( 3, 41, 10.25)
|
||
|
>>> alpha2_5 = Angle(10, 22, 57.024, ra=True)
|
||
|
>>> delta2_5 = Angle( 3, 35, 16.61)
|
||
|
>>> alpha1_list = [alpha1_1, alpha1_2, alpha1_3, alpha1_4, alpha1_5]
|
||
|
>>> delta1_list = [delta1_1, delta1_2, delta1_3, delta1_4, delta1_5]
|
||
|
>>> alpha2_list = [alpha2_1, alpha2_2, alpha2_3, alpha2_4, alpha2_5]
|
||
|
>>> delta2_list = [delta2_1, delta2_2, delta2_3, delta2_4, delta2_5]
|
||
|
>>> pc = planetary_conjunction(alpha1_list, delta1_list, \
|
||
|
alpha2_list, delta2_list)
|
||
|
>>> print(round(pc[0], 5))
|
||
|
0.23797
|
||
|
>>> print(pc[1].dms_str(n_dec=1))
|
||
|
2d 8' 21.8''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha1_list, (list, tuple))
|
||
|
and isinstance(delta1_list, (list, tuple))
|
||
|
and isinstance(alpha2_list, (list, tuple))
|
||
|
and isinstance(delta2_list, (list, tuple))
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if (
|
||
|
len(alpha1_list) < 3
|
||
|
or len(delta1_list) < 3
|
||
|
or len(alpha2_list) < 3
|
||
|
or len(delta2_list) < 3
|
||
|
):
|
||
|
raise ValueError("Invalid number of entries")
|
||
|
if (
|
||
|
len(alpha1_list) != len(delta1_list)
|
||
|
or len(alpha1_list) != len(alpha2_list)
|
||
|
or len(alpha1_list) != len(delta2_list)
|
||
|
):
|
||
|
raise ValueError("Uneven number of entries")
|
||
|
n_entries = len(alpha1_list)
|
||
|
if n_entries % 2 != 1: # Check if number of entries is odd
|
||
|
alpha1_list = alpha1_list[:-1] # Drop the last entry
|
||
|
delta1_list = delta1_list[:-1]
|
||
|
alpha2_list = alpha2_list[:-1]
|
||
|
delta2_list = delta2_list[:-1]
|
||
|
n_entries = len(alpha1_list)
|
||
|
half_entries = n_entries // 2
|
||
|
# Compute the list with the time ('n') entries
|
||
|
n_list = [i - half_entries for i in range(n_entries)]
|
||
|
# Compute lists with differences between right ascensions and declinations
|
||
|
# for objects #1 and #2
|
||
|
dalpha = [alpha1_list[i] - alpha2_list[i] for i in range(n_entries)]
|
||
|
ddelta = [delta1_list[i] - delta2_list[i] for i in range(n_entries)]
|
||
|
# Build the interpolation objects
|
||
|
i_alpha = Interpolation(n_list, dalpha)
|
||
|
i_delta = Interpolation(n_list, ddelta)
|
||
|
# Find when the dalphas are 0 (i.e., the 'root')
|
||
|
n_0 = i_alpha.root()
|
||
|
# Now, let's find the declination difference with the newly found 'n_0'
|
||
|
dd = i_delta(n_0)
|
||
|
# We are done, let's return
|
||
|
return n_0, dd
|
||
|
|
||
|
|
||
|
def planet_star_conjunction(alpha_list, delta_list, alpha_star, delta_star):
|
||
|
"""Given the positions of one planet passing near a star, this function
|
||
|
computes the time of conjunction in right ascension, and the difference in
|
||
|
declination of the two bodies at that time.
|
||
|
|
||
|
.. note:: This function provides as output the 'n' fraction of time when
|
||
|
the minimum angular separation is achieved. For that, the epoch in the
|
||
|
middle is assigned the value "n = 0". Therefore, n < 0 is for times
|
||
|
**before** the middle epoch, and n > 0 is for times **after** the
|
||
|
middle epoch.
|
||
|
|
||
|
.. note:: When the entries in the input values for the planet are more than
|
||
|
three and pair, the last entry is discarted and an odd number of
|
||
|
entries will be used.
|
||
|
|
||
|
:param alpha_list: List (or tuple) containing the right ascensions (as
|
||
|
Angle objects) for the planet (minimum 3 entries)
|
||
|
:type alpha_list: list, tuple of :py:class:`Angle`
|
||
|
:param delta_list: List (or tuple) containing the declinations (as Angle
|
||
|
objects) for the planet (minimum 3 entries)
|
||
|
:type delta_list: list, tuple of :py:class:`Angle`
|
||
|
:param alpha_star: Right ascension, as an Angle object, of the star
|
||
|
:type alpha_star: :py:class:`Angle`
|
||
|
:param delta_star: Declination, as an Angle object, of the star
|
||
|
:type delta_star: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with two components: The first component is a float
|
||
|
containing the 'n' fraction of time when the conjunction occurs. The
|
||
|
second component is an Angle object containing the declination
|
||
|
separation between the given objects at conjunction epoch
|
||
|
:rtype: tuple
|
||
|
:raises: ValueError if input values for planet have less than three entries
|
||
|
or they don't have the same number of entries.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha_1 = Angle(15, 3, 51.937, ra=True)
|
||
|
>>> delta_1 = Angle(-8, 57, 34.51)
|
||
|
>>> alpha_2 = Angle(15, 9, 57.327, ra=True)
|
||
|
>>> delta_2 = Angle(-9, 9, 3.88)
|
||
|
>>> alpha_3 = Angle(15, 15, 37.898, ra=True)
|
||
|
>>> delta_3 = Angle(-9, 17, 37.94)
|
||
|
>>> alpha_4 = Angle(15, 20, 50.632, ra=True)
|
||
|
>>> delta_4 = Angle(-9, 23, 16.25)
|
||
|
>>> alpha_5 = Angle(15, 25, 32.695, ra=True)
|
||
|
>>> delta_5 = Angle(-9, 26, 1.01)
|
||
|
>>> alpha_star = Angle(15, 17, 0.446, ra=True)
|
||
|
>>> delta_star = Angle(-9, 22, 58.47)
|
||
|
>>> alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5]
|
||
|
>>> delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5]
|
||
|
>>> pc = planet_star_conjunction(alpha_list, delta_list, \
|
||
|
alpha_star, delta_star)
|
||
|
>>> print(round(pc[0], 4))
|
||
|
0.2551
|
||
|
>>> print(pc[1].dms_str(n_dec=0))
|
||
|
3' 38.0''
|
||
|
"""
|
||
|
|
||
|
# Build the corresponding lists for the star
|
||
|
n_entries = len(alpha_list)
|
||
|
alpha_star_list = [alpha_star for _ in range(n_entries)]
|
||
|
delta_star_list = [delta_star for _ in range(n_entries)]
|
||
|
# Call the 'planetary_conjunction()' function. It handles everything else
|
||
|
return planetary_conjunction(
|
||
|
alpha_list, delta_list, alpha_star_list, delta_star_list
|
||
|
)
|
||
|
|
||
|
|
||
|
def planet_stars_in_line(
|
||
|
alpha_list, delta_list, alpha_star1, delta_star1, alpha_star2, delta_star2
|
||
|
):
|
||
|
"""Given the positions of one planet, this function computes the time when
|
||
|
it is in a straight line with two other stars.
|
||
|
|
||
|
.. note:: This function provides as output the 'n' fraction of time when
|
||
|
the minimum angular separation is achieved. For that, the epoch in the
|
||
|
middle is assigned the value "n = 0". Therefore, n < 0 is for times
|
||
|
**before** the middle epoch, and n > 0 is for times **after** the
|
||
|
middle epoch.
|
||
|
|
||
|
.. note:: When the entries in the input values for the planet are more than
|
||
|
three and pair, the last entry is discarted and an odd number of
|
||
|
entries will be used.
|
||
|
|
||
|
:param alpha_list: List (or tuple) containing the right ascensions (as
|
||
|
Angle objects) for the planet (minimum 3 entries)
|
||
|
:type alpha_list: list, tuple of :py:class:`Angle`
|
||
|
:param delta_list: List (or tuple) containing the declinations (as Angle
|
||
|
objects) for the planet (minimum 3 entries)
|
||
|
:type delta_list: list, tuple of :py:class:`Angle`
|
||
|
:param alpha_star1: Right ascension, as an Angle object, of star #1
|
||
|
:type alpha_star1: :py:class:`Angle`
|
||
|
:param delta_star1: Declination, as an Angle object, of star #1
|
||
|
:type delta_star1: :py:class:`Angle`
|
||
|
:param alpha_star2: Right ascension, as an Angle object, of star #2
|
||
|
:type alpha_star2: :py:class:`Angle`
|
||
|
:param delta_star2: Declination, as an Angle object, of star #2
|
||
|
:type delta_star2: :py:class:`Angle`
|
||
|
|
||
|
:returns: A float containing the 'n' fraction of time when the alignment
|
||
|
occurs.
|
||
|
:rtype: float
|
||
|
:raises: ValueError if input values for planet have less than three entries
|
||
|
or they don't have the same number of entries.
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha_1 = Angle( 7, 55, 55.36, ra=True)
|
||
|
>>> delta_1 = Angle(21, 41, 3.0)
|
||
|
>>> alpha_2 = Angle( 7, 58, 22.55, ra=True)
|
||
|
>>> delta_2 = Angle(21, 35, 23.4)
|
||
|
>>> alpha_3 = Angle( 8, 0, 48.99, ra=True)
|
||
|
>>> delta_3 = Angle(21, 29, 38.2)
|
||
|
>>> alpha_4 = Angle( 8, 3, 14.66, ra=True)
|
||
|
>>> delta_4 = Angle(21, 23, 47.5)
|
||
|
>>> alpha_5 = Angle( 8, 5, 39.54, ra=True)
|
||
|
>>> delta_5 = Angle(21, 17, 51.4)
|
||
|
>>> alpha_star1 = Angle( 7, 34, 16.40, ra=True)
|
||
|
>>> delta_star1 = Angle(31, 53, 51.2)
|
||
|
>>> alpha_star2 = Angle( 7, 45, 0.10, ra=True)
|
||
|
>>> delta_star2 = Angle(28, 2, 12.5)
|
||
|
>>> alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5]
|
||
|
>>> delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5]
|
||
|
>>> n = planet_stars_in_line(alpha_list, delta_list, alpha_star1, \
|
||
|
delta_star1, alpha_star2, delta_star2)
|
||
|
>>> print(round(n, 4))
|
||
|
0.2233
|
||
|
"""
|
||
|
|
||
|
# Define an auxiliary function
|
||
|
def straight(alpha1, delta1, alpha2, delta2, alpha3, delta3):
|
||
|
a1 = alpha1.rad()
|
||
|
d1 = delta1.rad()
|
||
|
a2 = alpha2.rad()
|
||
|
d2 = delta2.rad()
|
||
|
a3 = alpha3.rad()
|
||
|
d3 = delta3.rad()
|
||
|
return (tan(d1) * sin(a2 - a3) + tan(d2) * sin(a3 - a1) +
|
||
|
tan(d3) * sin(a1 - a2))
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha_list, (list, tuple))
|
||
|
and isinstance(delta_list, (list, tuple))
|
||
|
and isinstance(alpha_star1, Angle)
|
||
|
and isinstance(delta_star1, Angle)
|
||
|
and isinstance(alpha_star2, Angle)
|
||
|
and isinstance(delta_star2, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
if len(alpha_list) < 3 or len(delta_list) < 3:
|
||
|
raise ValueError("Invalid number of entries")
|
||
|
if len(alpha_list) != len(delta_list):
|
||
|
raise ValueError("Uneven number of entries")
|
||
|
n_entries = len(alpha_list)
|
||
|
if n_entries % 2 != 1: # Check if number of entries is odd
|
||
|
alpha_list = alpha_list[:-1] # Drop the last entry
|
||
|
delta_list = delta_list[:-1]
|
||
|
n_entries = len(alpha_list)
|
||
|
half_entries = n_entries // 2
|
||
|
# Compute the list with the time ('n') entries
|
||
|
n_list = [i - half_entries for i in range(n_entries)]
|
||
|
# Use auxiliary function 'straight()' to compute the values to interpolate
|
||
|
dx = [
|
||
|
straight(
|
||
|
alpha_list[i],
|
||
|
delta_list[i],
|
||
|
alpha_star1,
|
||
|
delta_star1,
|
||
|
alpha_star2,
|
||
|
delta_star2,
|
||
|
)
|
||
|
for i in range(n_entries)
|
||
|
]
|
||
|
# Build the interpolation objects
|
||
|
i = Interpolation(n_list, dx)
|
||
|
# Find when the dx's are 0 (i.e., the 'root')
|
||
|
n_0 = i.root()
|
||
|
return n_0
|
||
|
|
||
|
|
||
|
def straight_line(alpha1, delta1, alpha2, delta2, alpha3, delta3):
|
||
|
"""This function computes if three celestial bodies are in a straight line,
|
||
|
providing the angle with which the bodies differ from a great circle.
|
||
|
|
||
|
:param alpha1: Right ascension, as an Angle object, of celestial body #1
|
||
|
:type alpha1: :py:class:`Angle`
|
||
|
:param delta1: Declination, as an Angle object, of celestial body #1
|
||
|
:type delta1: :py:class:`Angle`
|
||
|
:param alpha2: Right ascension, as an Angle object, of celestial body #2
|
||
|
:type alpha2: :py:class:`Angle`
|
||
|
:param delta2: Declination, as an Angle object, of celestial body #2
|
||
|
:type delta2: :py:class:`Angle`
|
||
|
:param alpha3: Right ascension, as an Angle object, of celestial body #3
|
||
|
:type alpha3: :py:class:`Angle`
|
||
|
:param delta3: Declination, as an Angle object, of celestial body #3
|
||
|
:type delta3: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with two components. The first element is an angle (as
|
||
|
Angle object) with which the bodies differ from a great circle. The
|
||
|
second element is the Angular distance of central point to the straight
|
||
|
line (also as Angle object).
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha1 = Angle( 5, 32, 0.40, ra=True)
|
||
|
>>> delta1 = Angle(0, -17, 56.9)
|
||
|
>>> alpha2 = Angle( 5, 36, 12.81, ra=True)
|
||
|
>>> delta2 = Angle(-1, 12, 7.0)
|
||
|
>>> alpha3 = Angle( 5, 40, 45.52, ra=True)
|
||
|
>>> delta3 = Angle(-1, 56, 33.3)
|
||
|
>>> psi, om = straight_line(alpha1, delta1, alpha2, delta2, alpha3, delta3)
|
||
|
>>> print(psi.dms_str(n_dec=0))
|
||
|
7d 31' 1.0''
|
||
|
>>> print(om.dms_str(n_dec=0))
|
||
|
-5' 24.0''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha1, Angle)
|
||
|
and isinstance(delta1, Angle)
|
||
|
and isinstance(alpha2, Angle)
|
||
|
and isinstance(delta2, Angle)
|
||
|
and isinstance(alpha3, Angle)
|
||
|
and isinstance(delta3, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# We need to order the input according to right ascension
|
||
|
a = [alpha1.rad(), alpha2.rad(), alpha3.rad()]
|
||
|
d = [delta1.rad(), delta2.rad(), delta3.rad()]
|
||
|
anew = []
|
||
|
dnew = []
|
||
|
amax = max(a) + 1.0
|
||
|
for _ in range(len(a)):
|
||
|
# Get the index of the minimum value
|
||
|
imin = a.index(min(a))
|
||
|
# Append the current minimum value to the new 'a' list
|
||
|
anew.append(a[imin])
|
||
|
# Store the *position* of the current minimum value to new 'd' list
|
||
|
dnew.append(imin)
|
||
|
# The current minimum value will no longer be the minimum
|
||
|
a[imin] = amax
|
||
|
|
||
|
# In the new 'd' list, substitute the positions by the real values
|
||
|
for i in range(len(a)):
|
||
|
dnew[i] = d[dnew[i]]
|
||
|
# Substitute the new values in the original list
|
||
|
a = anew
|
||
|
d = dnew
|
||
|
# Compute the parameters
|
||
|
a1 = cos(d[0]) * cos(a[0])
|
||
|
a2 = cos(d[1]) * cos(a[1])
|
||
|
a3 = cos(d[2]) * cos(a[2])
|
||
|
b1 = cos(d[0]) * sin(a[0])
|
||
|
b2 = cos(d[1]) * sin(a[1])
|
||
|
b3 = cos(d[2]) * sin(a[2])
|
||
|
c1 = sin(d[0])
|
||
|
c2 = sin(d[1])
|
||
|
c3 = sin(d[2])
|
||
|
l1 = b1 * c2 - b2 * c1
|
||
|
l2 = b2 * c3 - b3 * c2
|
||
|
l3 = b1 * c3 - b3 * c1
|
||
|
m1 = c1 * a2 - c2 * a1
|
||
|
m2 = c2 * a3 - c3 * a2
|
||
|
m3 = c1 * a3 - c3 * a1
|
||
|
n1 = a1 * b2 - a2 * b1
|
||
|
n2 = a2 * b3 - a3 * b2
|
||
|
n3 = a1 * b3 - a3 * b1
|
||
|
psi = acos(
|
||
|
(l1 * l2 + m1 * m2 + n1 * n2)
|
||
|
/ (sqrt(l1 * l1 + m1 * m1 + n1 * n1) *
|
||
|
sqrt(l2 * l2 + m2 * m2 + n2 * n2)))
|
||
|
omega = asin(
|
||
|
(a2 * l3 + b2 * m3 + c2 * n3)
|
||
|
/ (sqrt(a2 * a2 + b2 * b2 + c2 * c2) *
|
||
|
sqrt(l3 * l3 + m3 * m3 + n3 * n3)))
|
||
|
return Angle(psi, radians=True), Angle(omega, radians=True)
|
||
|
|
||
|
|
||
|
def circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3):
|
||
|
"""This function computes the diameter of the smallest circle that contains
|
||
|
three celestial bodies.
|
||
|
|
||
|
:param alpha1: Right ascension, as an Angle object, of celestial body #1
|
||
|
:type alpha1: :py:class:`Angle`
|
||
|
:param delta1: Declination, as an Angle object, of celestial body #1
|
||
|
:type delta1: :py:class:`Angle`
|
||
|
:param alpha2: Right ascension, as an Angle object, of celestial body #2
|
||
|
:type alpha2: :py:class:`Angle`
|
||
|
:param delta2: Declination, as an Angle object, of celestial body #2
|
||
|
:type delta2: :py:class:`Angle`
|
||
|
:param alpha3: Right ascension, as an Angle object, of celestial body #3
|
||
|
:type alpha3: :py:class:`Angle`
|
||
|
:param delta3: Declination, as an Angle object, of celestial body #3
|
||
|
:type delta3: :py:class:`Angle`
|
||
|
|
||
|
:returns: The diameter (as an Angle object) of the smallest circle
|
||
|
containing the three bodies.
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> alpha1 = Angle(12, 41, 8.63, ra=True)
|
||
|
>>> delta1 = Angle(-5, 37, 54.2)
|
||
|
>>> alpha2 = Angle(12, 52, 5.21, ra=True)
|
||
|
>>> delta2 = Angle(-4, 22, 26.2)
|
||
|
>>> alpha3 = Angle(12, 39, 28.11, ra=True)
|
||
|
>>> delta3 = Angle(-1, 50, 3.7)
|
||
|
>>> d = circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3)
|
||
|
>>> print(d.dms_str(n_dec=0))
|
||
|
4d 15' 49.0''
|
||
|
>>> alpha1 = Angle(9, 5, 41.44, ra=True)
|
||
|
>>> delta1 = Angle(18, 30, 30.0)
|
||
|
>>> alpha2 = Angle(9, 9, 29.0, ra=True)
|
||
|
>>> delta2 = Angle(17, 43, 56.7)
|
||
|
>>> alpha3 = Angle(8, 59, 47.14, ra=True)
|
||
|
>>> delta3 = Angle(17, 49, 36.8)
|
||
|
>>> d = circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3)
|
||
|
>>> print(d.dms_str(n_dec=0))
|
||
|
2d 18' 38.0''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(alpha1, Angle)
|
||
|
and isinstance(delta1, Angle)
|
||
|
and isinstance(alpha2, Angle)
|
||
|
and isinstance(delta2, Angle)
|
||
|
and isinstance(alpha3, Angle)
|
||
|
and isinstance(delta3, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
d12 = angular_separation(alpha1, delta1, alpha2, delta2)
|
||
|
d13 = angular_separation(alpha1, delta1, alpha3, delta3)
|
||
|
d23 = angular_separation(alpha2, delta2, alpha3, delta3)
|
||
|
if d12 >= d13 and d12 >= d23:
|
||
|
a = d12()
|
||
|
b = d13()
|
||
|
c = d23()
|
||
|
elif d13 >= d12 and d13 >= d23:
|
||
|
a = d13()
|
||
|
b = d12()
|
||
|
c = d23()
|
||
|
else:
|
||
|
a = d23()
|
||
|
b = d12()
|
||
|
c = d13()
|
||
|
if a >= sqrt(b * b + c * c):
|
||
|
d = a
|
||
|
else:
|
||
|
d = (2.0 * a * b * c) / sqrt(
|
||
|
(a + b + c) * (a + b - c) * (b + c - a) * (a + c - b)
|
||
|
)
|
||
|
return Angle(d)
|
||
|
|
||
|
|
||
|
def vsop_pos(epoch, vsop_l, vsop_b, vsop_r):
|
||
|
"""This function computes the position of a celestial body at a given epoch
|
||
|
when its VSOP87 periodic term tables are provided.
|
||
|
|
||
|
:param epoch: Epoch to compute the position, given as an :class:`Epoch`
|
||
|
object
|
||
|
:type epoch: :py:class:`Epoch`
|
||
|
:param vsop_l: Table of VSOP87 terms for the heliocentric longitude
|
||
|
:type vsop_l: list
|
||
|
:param vsop_b: Table of VSOP87 terms for the heliocentric latitude
|
||
|
:type vsop_b: list
|
||
|
:param vsop_r: Table of VSOP87 terms for the radius vector
|
||
|
:type vsop_r: list
|
||
|
|
||
|
:returns: A tuple with the heliocentric longitude and latitude (as
|
||
|
:py:class:`Angle` objects), and the radius vector (as a float,
|
||
|
in astronomical units), in that order
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(epoch, Epoch)
|
||
|
and isinstance(vsop_l, list)
|
||
|
and isinstance(vsop_b, list)
|
||
|
and isinstance(vsop_r, list)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Let's redefine u in units of 100 Julian centuries from Epoch J2000.0
|
||
|
t = (epoch.jde() - 2451545.0) / 365250.0
|
||
|
sum_list = []
|
||
|
for i in range(len(vsop_l)):
|
||
|
s = 0.0
|
||
|
for k in range(len(vsop_l[i])):
|
||
|
s += vsop_l[i][k][0] * cos(vsop_l[i][k][1] + vsop_l[i][k][2] * t)
|
||
|
sum_list.append(s)
|
||
|
lon = 0.0
|
||
|
# Sum the longitude terms, while multiplying by 't' at the same time
|
||
|
for i in range(len(sum_list) - 1, 0, -1):
|
||
|
lon = (lon + sum_list[i]) * t
|
||
|
# Add the L0 term, which is NOT multiplied by 't'
|
||
|
lon += sum_list[0]
|
||
|
lon /= 1e8
|
||
|
lon = Angle(lon, radians=True)
|
||
|
lon = lon.to_positive()
|
||
|
sum_list = []
|
||
|
for i in range(len(vsop_b)):
|
||
|
s = 0.0
|
||
|
for k in range(len(vsop_b[i])):
|
||
|
s += vsop_b[i][k][0] * cos(vsop_b[i][k][1] + vsop_b[i][k][2] * t)
|
||
|
sum_list.append(s)
|
||
|
lat = 0.0
|
||
|
# Sum the latitude terms, while multiplying by 't' at the same time
|
||
|
for i in range(len(sum_list) - 1, 0, -1):
|
||
|
lat = (lat + sum_list[i]) * t
|
||
|
# Add the B0 term, which is NOT multiplied by 't'
|
||
|
lat += sum_list[0]
|
||
|
lat /= 1e8
|
||
|
lat = Angle(lat, radians=True)
|
||
|
sum_list = []
|
||
|
for i in range(len(vsop_r)):
|
||
|
s = 0.0
|
||
|
for k in range(len(vsop_r[i])):
|
||
|
s += vsop_r[i][k][0] * cos(vsop_r[i][k][1] + vsop_r[i][k][2] * t)
|
||
|
sum_list.append(s)
|
||
|
r = 0.0
|
||
|
# Sum the radius vector terms, while multiplying by 't' at the same time
|
||
|
for i in range(len(sum_list) - 1, 0, -1):
|
||
|
r = (r + sum_list[i]) * t
|
||
|
# Add the R0 term, which is NOT multiplied by 't'
|
||
|
r += sum_list[0]
|
||
|
r /= 1e8
|
||
|
return (lon, lat, r)
|
||
|
|
||
|
|
||
|
def geometric_vsop_pos(epoch, vsop_l, vsop_b, vsop_r, tofk5=True):
|
||
|
"""This function computes the geometric position of a celestial body at a
|
||
|
given epoch when its VSOP87 periodic term tables are provided. The small
|
||
|
correction to convert to the FK5 system may or not be included.
|
||
|
|
||
|
:param epoch: Epoch to compute the position, given as an :class:`Epoch`
|
||
|
object
|
||
|
:type epoch: :py:class:`Epoch`
|
||
|
:param vsop_l: Table of VSOP87 terms for the heliocentric longitude
|
||
|
:type vsop_l: list
|
||
|
:param vsop_b: Table of VSOP87 terms for the heliocentric latitude
|
||
|
:type vsop_b: list
|
||
|
:param vsop_r: Table of VSOP87 terms for the radius vector
|
||
|
:type vsop_r: list
|
||
|
:param tofk5: Whether or not the small correction to convert to the FK5
|
||
|
system will be applied
|
||
|
:type tofk5: bool
|
||
|
|
||
|
:returns: A tuple with the geometric heliocentric longitude and latitude
|
||
|
(as :py:class:`Angle` objects), and the radius vector (as a float,
|
||
|
in astronomical units), in that order
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not isinstance(epoch, Epoch):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Second, call the auxiliary function in charge of computations
|
||
|
lon, lat, r = vsop_pos(epoch, vsop_l, vsop_b, vsop_r)
|
||
|
if tofk5:
|
||
|
# Apply the small correction for conversion to the FK5 system
|
||
|
t = (epoch.jde() - 2451545.0) / 36525.0
|
||
|
lambda_p = lon - t * (1.397 + 0.00031 * t)
|
||
|
delta_lon = Angle(0, 0, -0.09033)
|
||
|
a = 0.03916 * (cos(lambda_p.rad()) + sin(lambda_p.rad()))
|
||
|
a = a * tan(lat.rad())
|
||
|
delta_lon += Angle(0, 0, a)
|
||
|
delta_beta = 0.03916 * (cos(lambda_p.rad()) - sin(lambda_p.rad()))
|
||
|
delta_beta = Angle(0, 0, delta_beta)
|
||
|
lon += delta_lon
|
||
|
lat += delta_beta
|
||
|
return lon, lat, r
|
||
|
|
||
|
|
||
|
def apparent_vsop_pos(epoch, vsop_l, vsop_b, vsop_r, nutation=True):
|
||
|
"""This function computes the apparent position of a celestial body at a
|
||
|
given epoch when its VSOP87 periodic term tables are provided. The small
|
||
|
correction to convert to the FK5 system is always included.
|
||
|
|
||
|
:param epoch: Epoch to compute the position, given as an :class:`Epoch`
|
||
|
object
|
||
|
:type epoch: :py:class:`Epoch`
|
||
|
:param vsop_l: Table of VSOP87 terms for the heliocentric longitude
|
||
|
:type vsop_l: list
|
||
|
:param vsop_b: Table of VSOP87 terms for the heliocentric latitude
|
||
|
:type vsop_b: list
|
||
|
:param vsop_r: Table of VSOP87 terms for the radius vector
|
||
|
:type vsop_r: list
|
||
|
:param nutation: Whether the nutation correction will be applied
|
||
|
:type tofk5: bool
|
||
|
|
||
|
:returns: A tuple with the geometric heliocentric longitude and latitude
|
||
|
(as :py:class:`Angle` objects), and the radius vector (as a float,
|
||
|
in astronomical units), in that order
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not isinstance(epoch, Epoch):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Second, call auxiliary function in charge of computations
|
||
|
lon, lat, r = geometric_vsop_pos(epoch, vsop_l, vsop_b, vsop_r)
|
||
|
if nutation:
|
||
|
lon += nutation_longitude(epoch)
|
||
|
delta = -20.4898 / r
|
||
|
delta = Angle(0, 0, delta)
|
||
|
lon += delta
|
||
|
return lon, lat, r
|
||
|
|
||
|
|
||
|
def apparent_position(epoch, alpha, delta, sun_lon):
|
||
|
"""This function computes the apparent position of a star, correcting by
|
||
|
nutation and aberration effects.
|
||
|
|
||
|
:param epoch: Epoch to compute the apparent position for
|
||
|
:type epoch: :py:class:`Epoch`
|
||
|
:param alpha: Right ascension of the star, as an Angle object
|
||
|
:type alpha: :py:class:`Angle`
|
||
|
:param delta: Declination of the star, as an Angle object
|
||
|
:type delta: :py:class:`Angle`
|
||
|
:param sun_lon: True (geometric) longitude of the Sun
|
||
|
:type sun_lon: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with two Angle objects: Apparent right ascension, and
|
||
|
aparent declination
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> epoch = Epoch(2028, 11, 13.19)
|
||
|
>>> alpha = Angle(2, 46, 11.331, ra=True)
|
||
|
>>> delta = Angle(49, 20, 54.54)
|
||
|
>>> sun_lon = Angle(231.328)
|
||
|
>>> app_alpha, app_delta = apparent_position(epoch, alpha, delta, sun_lon)
|
||
|
>>> print(app_alpha.ra_str(n_dec=2))
|
||
|
2h 46' 14.39''
|
||
|
>>> print(app_delta.dms_str(n_dec=2))
|
||
|
49d 21' 7.45''
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(epoch, Epoch)
|
||
|
and isinstance(alpha, Angle)
|
||
|
and isinstance(delta, Angle)
|
||
|
and isinstance(sun_lon, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Proceed to compute the true obliquity, nutation in longitude and nutation
|
||
|
# in obliquity
|
||
|
epsilon = true_obliquity(epoch)
|
||
|
dpsi = nutation_longitude(epoch)
|
||
|
depsilon = nutation_obliquity(epoch)
|
||
|
# Convert the angles to radians
|
||
|
a = alpha.rad()
|
||
|
d = delta.rad()
|
||
|
eps = epsilon.rad()
|
||
|
# Compute corrections due to nutation
|
||
|
dalpha1 = ((cos(eps) + sin(eps) * sin(a) * tan(d)) * dpsi -
|
||
|
(cos(a) * tan(d)) * depsilon)
|
||
|
ddelta1 = (sin(eps) * cos(a)) * dpsi + sin(a) * depsilon
|
||
|
dalpha1 = Angle(dalpha1)
|
||
|
ddelta1 = Angle(ddelta1)
|
||
|
# Now, let's compute the aberration effect
|
||
|
t = (epoch - JDE2000) / 36525
|
||
|
e = 0.016708634 + t * (-0.000042037 - t * 0.0000001267)
|
||
|
pie = 102.93735 + t * (1.71946 + t * 0.00046)
|
||
|
pie = radians(pie)
|
||
|
lon = sun_lon.rad()
|
||
|
k = 20.49552 # The constant of aberration
|
||
|
dalpha2 = k * (-(cos(a) * cos(lon) * cos(eps) + sin(a) * sin(lon)) / cos(d)
|
||
|
+ e * (cos(a) * cos(pie) * cos(eps) +
|
||
|
sin(a) * sin(pie)) / cos(d))
|
||
|
ddelta2 = k * (-(cos(lon) * cos(eps) *
|
||
|
(tan(eps) * cos(d) - sin(a) * sin(d)) +
|
||
|
cos(a) * sin(d) * sin(lon)) +
|
||
|
e * (cos(pie) * cos(eps) * (tan(eps) * cos(d) -
|
||
|
sin(a) * sin(d))
|
||
|
+ cos(a) * sin(d) * sin(pie)))
|
||
|
dalpha2 = Angle(0, 0, dalpha2)
|
||
|
ddelta2 = Angle(0, 0, ddelta2)
|
||
|
# Add the two corrections to the original values
|
||
|
r_alpha = alpha + dalpha1 + dalpha2
|
||
|
r_delta = delta + ddelta1 + ddelta2
|
||
|
return r_alpha, r_delta
|
||
|
|
||
|
|
||
|
def orbital_equinox2equinox(epoch0, epoch, i0, arg0, lon0):
|
||
|
"""This function reduces the orbital elements of a celestial object from
|
||
|
one equinox to another.
|
||
|
|
||
|
:param epoch0: Initial epoch
|
||
|
:type epoch0: :py:class:`Epoch`
|
||
|
:param epoch: Final epoch
|
||
|
:type epoch: :py:class:`Epoch`
|
||
|
:param i0: Initial inclination, as an Angle object
|
||
|
:type i0: :py:class:`Angle`
|
||
|
:param arg0: Initial argument of perihelion, as an Angle object
|
||
|
:type arg0: :py:class:`Angle`
|
||
|
:param lon0: Initial longitude of ascending node, as an Angle object
|
||
|
:type lon0: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with three Angle objects: Final inclination, argument of
|
||
|
perihelion and longitude of ascending node, in that order
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> epoch0 = Epoch(2358042.5305)
|
||
|
>>> epoch = Epoch(2433282.4235)
|
||
|
>>> i0 = Angle(47.122)
|
||
|
>>> arg0 = Angle(151.4486)
|
||
|
>>> lon0 = Angle(45.7481)
|
||
|
>>> i1, arg1, lon1 = orbital_equinox2equinox(epoch0, epoch, i0, arg0, lon0)
|
||
|
>>> print(round(i1(), 3))
|
||
|
47.138
|
||
|
>>> print(round(arg1(), 4))
|
||
|
151.4782
|
||
|
>>> print(round(lon1(), 4))
|
||
|
48.6037
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(epoch0, Epoch)
|
||
|
and isinstance(epoch, Epoch)
|
||
|
and isinstance(i0, Angle)
|
||
|
and isinstance(arg0, Angle)
|
||
|
and isinstance(lon0, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Compute the auxiliary angles
|
||
|
tt = (epoch0 - JDE2000) / 36525.0
|
||
|
t = (epoch - epoch0) / 36525.0
|
||
|
# Compute the conversion parameters
|
||
|
eta = t * (
|
||
|
(47.0029 + tt * (-0.06603 + 0.000598 * tt))
|
||
|
+ t * ((-0.03302 + 0.000598 * tt) + 0.00006 * t)
|
||
|
)
|
||
|
pie = tt * (3289.4789 + 0.60622 * tt) + t * (
|
||
|
-(869.8089 + 0.50491 * tt) + 0.03536 * t
|
||
|
)
|
||
|
p = t * (
|
||
|
5029.0966
|
||
|
+ tt * (2.22226 - 0.000042 * tt)
|
||
|
+ t * (1.11113 - 0.000042 * tt - 0.000006 * t)
|
||
|
)
|
||
|
eta = Angle(0, 0, eta)
|
||
|
pie = Angle(0, 0, pie)
|
||
|
# But beware!: There is still a missing constant for pie. We didn't add
|
||
|
# it before because of the mismatch between degrees and seconds
|
||
|
pie += 174.876384
|
||
|
p = Angle(0, 0, p)
|
||
|
i0r = i0.rad()
|
||
|
etar = eta.rad()
|
||
|
lon0r = lon0.rad()
|
||
|
pir = pie.rad()
|
||
|
# If i0 is very small, the procedure is different
|
||
|
if i0 < 1.0:
|
||
|
i1 = eta
|
||
|
lon1 = pie + p + 180.0
|
||
|
else:
|
||
|
a = sin(i0r) * sin(lon0r - pir)
|
||
|
b = -sin(etar) * cos(i0r) + cos(etar) * sin(i0r) * cos(lon0r - pir)
|
||
|
i1 = asin(sqrt(a*a + b*b))
|
||
|
i1 = Angle(i1, radians=True)
|
||
|
omegapsi = atan2(a, b)
|
||
|
omegapsi = Angle(omegapsi, radians=True)
|
||
|
lon1 = omegapsi + pie + p
|
||
|
domega = atan2(-sin(etar) * sin(lon0r - pir),
|
||
|
sin(i0r) * cos(etar) -
|
||
|
cos(i0r) * sin(etar) * cos(lon0r - pir))
|
||
|
domega = Angle(domega, radians=True)
|
||
|
arg1 = arg0 + domega
|
||
|
return i1, arg1, lon1
|
||
|
|
||
|
|
||
|
def kepler_equation(eccentricity, mean_anomaly):
|
||
|
"""This function computes the eccentric and true anomalies taking as input
|
||
|
the mean anomaly and the eccentricity.
|
||
|
|
||
|
:param eccentricity: Orbit's eccentricity
|
||
|
:type eccentricity: int, float
|
||
|
:param mean_anomaly: Mean anomaly, as an Angle object
|
||
|
:type mean_anomaly: :py:class:`Angle`
|
||
|
|
||
|
:returns: A tuple with two Angle objects: Eccentric and true anomalies
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> eccentricity = 0.1
|
||
|
>>> mean_anomaly = Angle(5.0)
|
||
|
>>> e, v = kepler_equation(eccentricity, mean_anomaly)
|
||
|
>>> print(round(e(), 6))
|
||
|
5.554589
|
||
|
>>> print(round(v(), 6))
|
||
|
6.139762
|
||
|
>>> eccentricity = 0.99
|
||
|
>>> mean_anomaly = Angle(2.0)
|
||
|
>>> e, v = kepler_equation(eccentricity, mean_anomaly)
|
||
|
>>> print(round(e(), 6))
|
||
|
32.361007
|
||
|
>>> print(round(v(), 6))
|
||
|
152.542134
|
||
|
>>> eccentricity = 0.99
|
||
|
>>> mean_anomaly = Angle(5.0)
|
||
|
>>> e, v = kepler_equation(eccentricity, mean_anomaly)
|
||
|
>>> print(round(e(), 6))
|
||
|
45.361023
|
||
|
>>> print(round(v(), 6))
|
||
|
160.745616
|
||
|
>>> eccentricity = 0.99
|
||
|
>>> mean_anomaly = Angle(1.0)
|
||
|
>>> e, v = kepler_equation(eccentricity, mean_anomaly)
|
||
|
>>> print(round(e(), 6))
|
||
|
24.725822
|
||
|
>>> print(round(v(), 6))
|
||
|
144.155952
|
||
|
>>> e, v = kepler_equation(0.999, Angle(7.0))
|
||
|
>>> print(round(e(), 7))
|
||
|
52.2702615
|
||
|
>>> print(round(v(), 6))
|
||
|
174.780018
|
||
|
>>> e, v = kepler_equation(0.99, Angle(0.2, radians=True))
|
||
|
>>> print(round(e(), 8))
|
||
|
61.13444578
|
||
|
>>> print(round(v(), 6))
|
||
|
166.311977
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (
|
||
|
isinstance(eccentricity, (int, float))
|
||
|
and isinstance(mean_anomaly, Angle)
|
||
|
):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Let's implement the third method (from Roger Sinnot), page 206
|
||
|
# First, compute the eccentric anomaly
|
||
|
m = mean_anomaly.rad()
|
||
|
ecc = eccentricity
|
||
|
f = copysign(1.0, m)
|
||
|
m = abs(m) / (2.0 * pi)
|
||
|
m = (m - iint(m)) * 2.0 * pi * f
|
||
|
if m < 0.0:
|
||
|
m += 2.0 * pi
|
||
|
f = 1.0
|
||
|
if m > pi:
|
||
|
f = -1
|
||
|
m = 2.0 * pi - m
|
||
|
e0 = pi / 2.0
|
||
|
d = pi / 4.0
|
||
|
ef = 0.0
|
||
|
while abs(e0 - ef) > TOL:
|
||
|
ef = e0
|
||
|
m1 = e0 - ecc * sin(e0)
|
||
|
s = copysign(1.0, m - m1)
|
||
|
e0 += d * s
|
||
|
d /= 2.0
|
||
|
e = Angle(e0 * f, radians=True)
|
||
|
# Now, compute the true anomaly
|
||
|
er = e.rad()
|
||
|
v = 2.0 * atan(sqrt((1.0 + ecc) / (1.0 - ecc)) * tan(er / 2.0))
|
||
|
return e, Angle(v, radians=True)
|
||
|
|
||
|
|
||
|
def orbital_elements(epoch, parameters1, parameters2):
|
||
|
"""This function computes the orbital elements for a given epoch, according
|
||
|
to the parameters beeing passed as arguments.
|
||
|
|
||
|
:param epoch: Epoch to compute orbital elements, as an Epoch object
|
||
|
:type epoch: :py:class:`Epoch`
|
||
|
:param parameters1: First set of parameters
|
||
|
:type parameters1: list
|
||
|
:param parameters2: Second set of parameters
|
||
|
:type parameters2: list
|
||
|
|
||
|
:returns: A tuple containing the following six orbital elements:
|
||
|
- Mean longitude of the planet (Angle)
|
||
|
- Semimajor axis of the orbit (float, astronomical units)
|
||
|
- eccentricity of the orbit (float)
|
||
|
- inclination on the plane of the ecliptic (Angle)
|
||
|
- longitude of the ascending node (Angle)
|
||
|
- argument of the perihelion (Angle)
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
"""
|
||
|
|
||
|
# First check that input values are of correct types
|
||
|
if not (isinstance(epoch, Epoch) and isinstance(parameters1, list) and
|
||
|
isinstance(parameters2, list)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
|
||
|
# Define an auxiliary function
|
||
|
def compute_element(t, param):
|
||
|
return param[0] + t * (param[1] + t * (param[2] + t * param[3]))
|
||
|
|
||
|
# Compute the time parameter
|
||
|
t = (epoch - JDE2000) / 36525.0
|
||
|
# Compute the orbital elements
|
||
|
ll = compute_element(t, parameters2[0])
|
||
|
a = compute_element(t, parameters1[1])
|
||
|
e = compute_element(t, parameters1[2])
|
||
|
if len(parameters2) == 4:
|
||
|
i = compute_element(t, parameters2[1])
|
||
|
omega = compute_element(t, parameters2[2])
|
||
|
pie = compute_element(t, parameters2[3])
|
||
|
else:
|
||
|
i = compute_element(t, parameters2[3])
|
||
|
omega = compute_element(t, parameters2[4])
|
||
|
pie = compute_element(t, parameters2[5])
|
||
|
arg = pie - omega
|
||
|
ll = Angle(ll)
|
||
|
i = Angle(i)
|
||
|
omega = Angle(omega)
|
||
|
arg = Angle(arg)
|
||
|
return ll, a, e, i, omega, arg
|
||
|
|
||
|
|
||
|
def velocity(r, a):
|
||
|
"""This function computes the instantaneous velocity of the moving body, in
|
||
|
kilometers per second, for an unperturbed elliptic orbit.
|
||
|
|
||
|
:param r: Distance of the body to the Sun, in Astronomical Units
|
||
|
:type r: float
|
||
|
:param a: Semimajor axis of the orbit, in Astronomical Units
|
||
|
:type a: float
|
||
|
|
||
|
:returns: Velocity of the body, in kilometers per second
|
||
|
:rtype: float
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> r = 1.0
|
||
|
>>> a = 17.9400782
|
||
|
>>> v = velocity(r, a)
|
||
|
>>> print(round(v, 2))
|
||
|
41.53
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(r, float) and isinstance(a, float)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
return 42.1218 * sqrt((1.0 / r) - (1.0 / (2.0 * a)))
|
||
|
|
||
|
|
||
|
def velocity_perihelion(e, a):
|
||
|
"""This function computes the velocity of the moving body at perihelion, in
|
||
|
kilometers per second, for an unperturbed elliptic orbit.
|
||
|
|
||
|
:param e: Orbital eccentricity
|
||
|
:type e: float
|
||
|
:param a: Semimajor axis of the orbit, in Astronomical Units
|
||
|
:type a: float
|
||
|
|
||
|
:returns: Velocity of the body at perihelion, in kilometers per second
|
||
|
:rtype: float
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> a = 17.9400782
|
||
|
>>> e = 0.96727426
|
||
|
>>> vp = velocity_perihelion(e, a)
|
||
|
>>> print(round(vp, 2))
|
||
|
54.52
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(e, float) and isinstance(a, float)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
temp = sqrt((1.0 + e) / (1.0 - e))
|
||
|
return 29.7847 * temp / sqrt(a)
|
||
|
|
||
|
|
||
|
def velocity_aphelion(e, a):
|
||
|
"""This function computes the velocity of the moving body at aphelion, in
|
||
|
kilometers per second, for an unperturbed elliptic orbit.
|
||
|
|
||
|
:param e: Orbital eccentricity
|
||
|
:type e: float
|
||
|
:param a: Semimajor axis of the orbit, in Astronomical Units
|
||
|
:type a: float
|
||
|
|
||
|
:returns: Velocity of the body at aphelion, in kilometers per second
|
||
|
:rtype: float
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> a = 17.9400782
|
||
|
>>> e = 0.96727426
|
||
|
>>> va = velocity_aphelion(e, a)
|
||
|
>>> print(round(va, 2))
|
||
|
0.91
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(e, float) and isinstance(a, float)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
temp = sqrt((1.0 - e) / (1.0 + e))
|
||
|
return 29.7847 * temp / sqrt(a)
|
||
|
|
||
|
|
||
|
def length_orbit(e, a):
|
||
|
"""This function computes the length of an elliptic orbit given its
|
||
|
eccentricity and semimajor axis.
|
||
|
|
||
|
:param e: Orbital eccentricity
|
||
|
:type e: float
|
||
|
:param a: Semimajor axis of the orbit, in Astronomical Units
|
||
|
:type a: float
|
||
|
|
||
|
:returns: Length of the orbit in Astronomical Units
|
||
|
:rtype: float
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> a = 17.9400782
|
||
|
>>> e = 0.96727426
|
||
|
>>> length = length_orbit(e, a)
|
||
|
>>> print(round(length, 2))
|
||
|
77.06
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(e, float) and isinstance(a, float)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# Let's start computing the semi-minor axis
|
||
|
b = a * sqrt(1.0 - e * e)
|
||
|
# Use one formula or another depending on eccentricity
|
||
|
if e < 0.95:
|
||
|
aa = (a + b) / 2.0
|
||
|
gg = sqrt(a * b)
|
||
|
hh = (2.0 * a * b) / (a + b)
|
||
|
length = pi * (21.0 * aa - 2.0 * gg - 3.0 * hh) / 8.0
|
||
|
else:
|
||
|
length = pi * (3.0 * (a + b) - sqrt((a + 3.0 * b) * (3.0 * a + b)))
|
||
|
return length
|
||
|
|
||
|
|
||
|
def passage_nodes_elliptic(omega, e, a, t, ascending=True):
|
||
|
"""This function computes the time of passage by the nodes (ascending or
|
||
|
descending) of a given celestial object with an elliptic orbit.
|
||
|
|
||
|
:param omega: Argument of the perihelion
|
||
|
:type omega: :py:class:`Angle`
|
||
|
:param e: Orbital eccentricity
|
||
|
:type e: float
|
||
|
:param a: Semimajor axis of the orbit, in Astronomical Units
|
||
|
:type a: float
|
||
|
:param t: Time of perihelion passage
|
||
|
:type t: :py:class:`Epoch`
|
||
|
:param ascending: Whether the time of passage by the ascending (True) or
|
||
|
descending (False) node will be computed
|
||
|
:type ascending: bool
|
||
|
|
||
|
:returns: Tuple containing:
|
||
|
- Time of passage through the node (:py:class:`Epoch`)
|
||
|
- Radius vector when passing through the node (in AU, float)
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> omega = Angle(111.84644)
|
||
|
>>> e = 0.96727426
|
||
|
>>> a = 17.9400782
|
||
|
>>> t = Epoch(1986, 2, 9.45891)
|
||
|
>>> time, r = passage_nodes_elliptic(omega, e, a, t)
|
||
|
>>> year, month, day = time.get_date()
|
||
|
>>> print(year)
|
||
|
1985
|
||
|
>>> print(month)
|
||
|
11
|
||
|
>>> print(round(day, 2))
|
||
|
9.16
|
||
|
>>> print(round(r, 4))
|
||
|
1.8045
|
||
|
>>> time, r = passage_nodes_elliptic(omega, e, a, t, ascending=False)
|
||
|
>>> year, month, day = time.get_date()
|
||
|
>>> print(year)
|
||
|
1986
|
||
|
>>> print(month)
|
||
|
3
|
||
|
>>> print(round(day, 2))
|
||
|
10.37
|
||
|
>>> print(round(r, 4))
|
||
|
0.8493
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(omega, Angle) and isinstance(e, float) and
|
||
|
isinstance(a, float) and isinstance(t, Epoch)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# First, get the true anomaly
|
||
|
if ascending:
|
||
|
v = 360.0 - omega
|
||
|
else:
|
||
|
v = 180.0 - omega
|
||
|
# Compute the eccentric anomaly
|
||
|
ee = 2.0 * atan(sqrt((1.0 - e)/(1.0 + e)) * tan(v.rad() / 2.0))
|
||
|
# Now compute the mean anomaly
|
||
|
m = ee - e * sin(ee)
|
||
|
# We need the mean motion, in degrees/day
|
||
|
n = 0.9856076686/(a * sqrt(a))
|
||
|
# The time of passage will be
|
||
|
tt = t + degrees(m) / n
|
||
|
# And the corresponding radius vector is
|
||
|
r = a * (1.0 - e * cos(ee))
|
||
|
return tt, r
|
||
|
|
||
|
|
||
|
def passage_nodes_parabolic(omega, q, t, ascending=True):
|
||
|
"""This function computes the time of passage by the nodes (ascending or
|
||
|
descending) of a given celestial object with a parabolic orbit.
|
||
|
|
||
|
:param omega: Argument of the perihelion
|
||
|
:type omega: :py:class:`Angle`
|
||
|
:param q: Perihelion distance, in Astronomical Units
|
||
|
:type q: float
|
||
|
:param t: Time of perihelion passage
|
||
|
:type t: :py:class:`Epoch`
|
||
|
:param ascending: Whether the time of passage by the ascending (True) or
|
||
|
descending (False) node will be computed
|
||
|
:type ascending: bool
|
||
|
|
||
|
:returns: Tuple containing:
|
||
|
- Time of passage through the node (:py:class:`Epoch`)
|
||
|
- Radius vector when passing through the node (in AU, float)
|
||
|
:rtype: tuple
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> omega = Angle(154.9103)
|
||
|
>>> q = 1.324502
|
||
|
>>> t = Epoch(1989, 8, 20.291)
|
||
|
>>> time, r = passage_nodes_parabolic(omega, q, t)
|
||
|
>>> year, month, day = time.get_date()
|
||
|
>>> print(year)
|
||
|
1977
|
||
|
>>> print(month)
|
||
|
9
|
||
|
>>> print(round(day, 2))
|
||
|
17.64
|
||
|
>>> print(round(r, 4))
|
||
|
28.0749
|
||
|
>>> time, r = passage_nodes_parabolic(omega, q, t, ascending=False)
|
||
|
>>> year, month, day = time.get_date()
|
||
|
>>> print(year)
|
||
|
1989
|
||
|
>>> print(month)
|
||
|
9
|
||
|
>>> print(round(day, 3))
|
||
|
17.636
|
||
|
>>> print(round(r, 4))
|
||
|
1.3901
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(omega, Angle) and isinstance(q, float) and
|
||
|
isinstance(t, Epoch)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
# First, get the true anomaly
|
||
|
if ascending:
|
||
|
v = 360.0 - omega
|
||
|
else:
|
||
|
v = 180.0 - omega
|
||
|
# Compute an auxiliary value
|
||
|
s = tan(v.rad() / 2.0)
|
||
|
s2 = s * s
|
||
|
# Compute time of passage
|
||
|
tt = t + 27.403895 * s * (s2 + 3.0) * q * sqrt(q)
|
||
|
# Compute radius vector
|
||
|
r = q * (1.0 + s2)
|
||
|
return tt, r
|
||
|
|
||
|
|
||
|
def phase_angle(sun_dist, earth_dist, sun_earth_dist):
|
||
|
"""This function computes the phase angle, i.e., the angle Sun-planet-Earth
|
||
|
from the corresponding distances.
|
||
|
|
||
|
:param sun_dist: Planet's distance to the Sun, in Astronomical Units
|
||
|
:type sun_dist: float
|
||
|
:param earth_dist: Distance from planet to Earth, in Astronomical Units
|
||
|
:type earth_dist: float
|
||
|
:param sun_earth_dist: Distance Sun-Earth, in Astronomical Units
|
||
|
:type sun_earth_dist: float
|
||
|
|
||
|
:returns: The phase angle, as an Angle object
|
||
|
:rtype: :py:class:`Angle`
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> sun_dist = 0.724604
|
||
|
>>> earth_dist = 0.910947
|
||
|
>>> sun_earth_dist = 0.983824
|
||
|
>>> angle = phase_angle(sun_dist, earth_dist, sun_earth_dist)
|
||
|
>>> print(round(angle, 2))
|
||
|
72.96
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(sun_dist, float) and isinstance(earth_dist, float) and
|
||
|
isinstance(sun_earth_dist, float)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
angle = acos((sun_dist * sun_dist + earth_dist * earth_dist -
|
||
|
sun_earth_dist * sun_earth_dist) /
|
||
|
(2.0 * sun_dist * earth_dist))
|
||
|
angle = Angle(angle, radians=True)
|
||
|
return angle
|
||
|
|
||
|
|
||
|
def illuminated_fraction(sun_dist, earth_dist, sun_earth_dist):
|
||
|
"""This function computes the illuminated fraction of the disk of a planet,
|
||
|
as seen from the Earth.
|
||
|
|
||
|
:param sun_dist: Planet's distance to the Sun, in Astronomical Units
|
||
|
:type sun_dist: float
|
||
|
:param earth_dist: Distance from planet to Earth, in Astronomical Units
|
||
|
:type earth_dist: float
|
||
|
:param sun_earth_dist: Distance Sun-Earth, in Astronomical Units
|
||
|
:type sun_earth_dist: float
|
||
|
|
||
|
:returns: The illuminated fraction of the disc of a planet
|
||
|
:rtype: float
|
||
|
:raises: TypeError if input values are of wrong type.
|
||
|
|
||
|
>>> sun_dist = 0.724604
|
||
|
>>> earth_dist = 0.910947
|
||
|
>>> sun_earth_dist = 0.983824
|
||
|
>>> k = illuminated_fraction(sun_dist, earth_dist, sun_earth_dist)
|
||
|
>>> print(round(k, 3))
|
||
|
0.647
|
||
|
"""
|
||
|
|
||
|
if not (isinstance(sun_dist, float) and isinstance(earth_dist, float) and
|
||
|
isinstance(sun_earth_dist, float)):
|
||
|
raise TypeError("Invalid input types")
|
||
|
k = ((sun_dist + earth_dist) * (sun_dist + earth_dist) -
|
||
|
sun_earth_dist * sun_earth_dist) / (4.0 * sun_dist * earth_dist)
|
||
|
return k
|
||
|
|
||
|
|
||
|
def main():
|
||
|
|
||
|
# Let's define a small helper function
|
||
|
def print_me(msg, val):
|
||
|
print("{}: {}".format(msg, val))
|
||
|
|
||
|
# Let's show some uses of Coordinate functions
|
||
|
print("\n" + 35 * "*")
|
||
|
print("*** Use of Coordinate functions")
|
||
|
print(35 * "*" + "\n")
|
||
|
|
||
|
# Here follows a series of important parameters related to the angle
|
||
|
# between Earth's rotation axis and the ecliptic
|
||
|
e0 = mean_obliquity(1987, 4, 10)
|
||
|
print(
|
||
|
"The mean angle between Earth rotation axis and ecliptic axis for "
|
||
|
+ "1987/4/10 is:"
|
||
|
)
|
||
|
print_me("Mean obliquity", e0.dms_str(n_dec=3)) # 23d 26' 27.407''
|
||
|
epsilon = true_obliquity(1987, 4, 10)
|
||
|
print("'True' (instantaneous) angle between those axes for 1987/4/10 is:")
|
||
|
print_me("True obliquity", epsilon.dms_str(n_dec=3)) # 23d 26' 36.849''
|
||
|
epsilon = true_obliquity(2018, 7, 29)
|
||
|
print("'True' (instantaneous) angle between those axes for 2018/7/29 is:")
|
||
|
print_me("True obliquity", epsilon.dms_str(True, 4)) # 23d 26' 7.2157''
|
||
|
|
||
|
# The nutation effect is separated in two components: One parallel to the
|
||
|
# ecliptic (nutation in longitude) and other perpendicular to the ecliptic
|
||
|
# (nutation in obliquity)
|
||
|
print("Nutation correction in longitude for 1987/4/10:")
|
||
|
dpsi = nutation_longitude(1987, 4, 10)
|
||
|
print_me("Nutation in longitude", dpsi.dms_str(n_dec=3)) # 0d 0' -3.788''
|
||
|
print("Nutation correction in obliquity for 1987/4/10:")
|
||
|
depsilon = nutation_obliquity(1987, 4, 10) # 0d 0' 9.443''
|
||
|
print_me("Nutation in obliquity", depsilon.dms_str(n_dec=3))
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# We can compute the effects of precession on the equatorial coordinates of
|
||
|
# a given star, taking also into account its proper motion
|
||
|
|
||
|
start_epoch = JDE2000
|
||
|
final_epoch = Epoch(2028, 11, 13.19)
|
||
|
alpha0 = Angle(2, 44, 11.986, ra=True)
|
||
|
delta0 = Angle(49, 13, 42.48) # 2h 44' 11.986''
|
||
|
print_me("Initial right ascension", alpha0.ra_str(n_dec=3))
|
||
|
print_me("Initial declination", delta0.dms_str(n_dec=2)) # 49d 13' 42.48''
|
||
|
pm_ra = Angle(0, 0, 0.03425, ra=True)
|
||
|
pm_dec = Angle(0, 0, -0.0895)
|
||
|
alpha, delta = precession_equatorial(
|
||
|
start_epoch, final_epoch, alpha0, delta0, pm_ra, pm_dec
|
||
|
)
|
||
|
print_me("Final right ascension", alpha.ra_str(n_dec=3)) # 2h 46' 11.331''
|
||
|
print_me("Final declination", delta.dms_str(n_dec=2)) # 49d 20' 54.54''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Something similar can also be done with the ecliptical coordinates
|
||
|
start_epoch = JDE2000
|
||
|
final_epoch = Epoch(-214, 6, 30.0)
|
||
|
lon0 = Angle(149.48194)
|
||
|
lat0 = Angle(1.76549)
|
||
|
print_me("Initial ecliptical longitude", round(lon0(), 5)) # 149.48194
|
||
|
print_me("Initial ecliptical latitude", round(lat0(), 5)) # 1.76549
|
||
|
lon, lat = precession_ecliptical(start_epoch, final_epoch, lon0, lat0)
|
||
|
print_me("Final ecliptical longitude", round(lon(), 3)) # 118.704
|
||
|
print_me("Final ecliptical latitude", round(lat(), 3)) # 1.615
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# It is possible to compute with relative accuracy the proper motion of the
|
||
|
# stars, taking into account their distance to Sun and relative velocity
|
||
|
ra = Angle(6, 45, 8.871, ra=True)
|
||
|
dec = Angle(-16.716108)
|
||
|
pm_ra = Angle(0, 0, -0.03847, ra=True)
|
||
|
pm_dec = Angle(0, 0, -1.2053)
|
||
|
dist = 2.64
|
||
|
vel = -7.6
|
||
|
alpha, delta = motion_in_space(ra, dec, dist, vel, pm_ra, pm_dec, -1000.0)
|
||
|
|
||
|
print_me("Right ascension, year 2000", ra.ra_str(True, 2))
|
||
|
print_me("Right ascension, year 1000", alpha.ra_str(True, 2))
|
||
|
# 6h 45' 47.16''
|
||
|
print_me("Declination, year 2000", dec.dms_str(True, 1))
|
||
|
print_me("Declination, year 1000", delta.dms_str(True, 1))
|
||
|
# -16d 22' 56.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# This module provides a series of functions to convert between equatorial,
|
||
|
# ecliptical, horizontal and galactic coordinates
|
||
|
|
||
|
ra = Angle(7, 45, 18.946, ra=True)
|
||
|
dec = Angle(28, 1, 34.26)
|
||
|
epsilon = Angle(23.4392911)
|
||
|
lon, lat = equatorial2ecliptical(ra, dec, epsilon)
|
||
|
print_me("Equatorial to ecliptical. Longitude", round(lon(), 5))
|
||
|
# 113.21563
|
||
|
print_me("Equatorial to ecliptical. Latitude", round(lat(), 5))
|
||
|
# 6.68417
|
||
|
|
||
|
print("")
|
||
|
|
||
|
lon = Angle(113.21563)
|
||
|
lat = Angle(6.68417)
|
||
|
epsilon = Angle(23.4392911)
|
||
|
ra, dec = ecliptical2equatorial(lon, lat, epsilon)
|
||
|
print_me("Ecliptical to equatorial. Right ascension", ra.ra_str(n_dec=3))
|
||
|
# 7h 45' 18.946''
|
||
|
print_me("Ecliptical to equatorial. Declination", dec.dms_str(n_dec=2))
|
||
|
# 28d 1' 34.26''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
lon = Angle(77, 3, 56)
|
||
|
lat = Angle(38, 55, 17)
|
||
|
ra = Angle(23, 9, 16.641, ra=True)
|
||
|
dec = Angle(-6, 43, 11.61)
|
||
|
theta0 = Angle(8, 34, 57.0896, ra=True)
|
||
|
eps = Angle(23, 26, 36.87)
|
||
|
# Compute correction to convert from mean to apparent sidereal time
|
||
|
delta = Angle(0, 0, ((-3.868 * cos(eps.rad())) / 15.0), ra=True)
|
||
|
theta0 += delta
|
||
|
h = theta0 - lon - ra
|
||
|
azi, ele = equatorial2horizontal(h, dec, lat)
|
||
|
print_me("Equatorial to horizontal: Azimuth", round(azi, 3)) # 68.034
|
||
|
print_me("Equatorial to horizontal: Elevation", round(ele, 3)) # 15.125
|
||
|
|
||
|
print("")
|
||
|
|
||
|
azi = Angle(68.0337)
|
||
|
ele = Angle(15.1249)
|
||
|
lat = Angle(38, 55, 17)
|
||
|
h, dec = horizontal2equatorial(azi, ele, lat)
|
||
|
print_me("Horizontal to equatorial. Hour angle", round(h, 4)) # 64.3521
|
||
|
print_me("Horizontal to equatorial. Declination", dec.dms_str(n_dec=0))
|
||
|
# -6d 43' 12.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
ra = Angle(17, 48, 59.74, ra=True)
|
||
|
dec = Angle(-14, 43, 8.2)
|
||
|
lon, lat = equatorial2galactic(ra, dec)
|
||
|
print_me("Equatorial to galactic. Longitude", round(lon, 4)) # 12.9593
|
||
|
print_me("Equatorial to galactic. Latitude", round(lat, 4)) # 6.0463
|
||
|
|
||
|
print("")
|
||
|
|
||
|
lon = Angle(12.9593)
|
||
|
lat = Angle(6.0463)
|
||
|
ra, dec = galactic2equatorial(lon, lat)
|
||
|
print_me("Galactic to equatorial. Right ascension", ra.ra_str(n_dec=1))
|
||
|
# 17h 48' 59.7''
|
||
|
print_me("Galactic to equatorial. Declination", dec.dms_str(n_dec=0))
|
||
|
# -14d 43' 8.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Get the ecliptic longitudes of the two points of the ecliptic which are
|
||
|
# on the horizon, as well as the angle between the ecliptic and the horizon
|
||
|
sidereal_time = Angle(5.0, ra=True)
|
||
|
lat = Angle(51.0)
|
||
|
epsilon = Angle(23.44)
|
||
|
lon1, lon2, i = ecliptic_horizon(sidereal_time, lat, epsilon)
|
||
|
print_me(
|
||
|
"Longitude of ecliptic point #1 on the horizon", lon1.dms_str(n_dec=1)
|
||
|
) # 169d 21' 29.9''
|
||
|
print_me(
|
||
|
"Longitude of ecliptic point #2 on the horizon", lon2.dms_str(n_dec=1)
|
||
|
) # 349d 21' 29.9''
|
||
|
print_me("Angle between the ecliptic and the horizon", round(i, 0)) # 62.0
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Let's compute the angle of the diurnal path of a celestial body relative
|
||
|
# to the horizon at the time of rising and setting
|
||
|
dec = Angle(23.44)
|
||
|
lat = Angle(40.0)
|
||
|
j = diurnal_path_horizon(dec, lat)
|
||
|
print_me(
|
||
|
"Diurnal path vs. horizon angle at time of rising and setting",
|
||
|
j.dms_str(n_dec=1),
|
||
|
) # 45d 31' 28.4''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# There is a function to compute the times (in hours of the day) of rising,
|
||
|
# transit and setting of a given celestial body
|
||
|
longitude = Angle(71, 5, 0.0)
|
||
|
latitude = Angle(42, 20, 0.0)
|
||
|
alpha1 = Angle(2, 42, 43.25, ra=True)
|
||
|
delta1 = Angle(18, 2, 51.4)
|
||
|
alpha2 = Angle(2, 46, 55.51, ra=True)
|
||
|
delta2 = Angle(18, 26, 27.3)
|
||
|
alpha3 = Angle(2, 51, 7.69, ra=True)
|
||
|
delta3 = Angle(18, 49, 38.7)
|
||
|
h0 = Angle(-0.5667)
|
||
|
delta_t = 56.0
|
||
|
theta0 = Angle(11, 50, 58.1, ra=True)
|
||
|
rising, transit, setting = times_rise_transit_set(
|
||
|
longitude,
|
||
|
latitude,
|
||
|
alpha1,
|
||
|
delta1,
|
||
|
alpha2,
|
||
|
delta2,
|
||
|
alpha3,
|
||
|
delta3,
|
||
|
h0,
|
||
|
delta_t,
|
||
|
theta0,
|
||
|
)
|
||
|
|
||
|
print_me("Time of rising (hours of day)", round(rising, 4)) # 12.4238
|
||
|
print_me("Time of transit (hours of day)", round(transit, 3)) # 19.675
|
||
|
print_me("Time of setting (hours of day, next day)", round(setting, 3))
|
||
|
# 2.911
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# The air in the atmosphere introduces an error in the elevation due to the
|
||
|
# refraction. We can compute the true (airless) elevation from the apparent
|
||
|
# elevation, and viceversa
|
||
|
apparent_elevation = Angle(0, 30, 0.0)
|
||
|
true_elevation = refraction_apparent2true(apparent_elevation)
|
||
|
print_me(
|
||
|
"True elevation for an apparent elevation of 30'",
|
||
|
true_elevation.dms_str(n_dec=1),
|
||
|
) # 1' 14.7''
|
||
|
|
||
|
true_elevation = Angle(0, 33, 14.76)
|
||
|
apparent_elevation = refraction_true2apparent(true_elevation)
|
||
|
print_me(
|
||
|
"Apparent elevation for a true elevation of 33' 14.76''",
|
||
|
apparent_elevation.dms_str(n_dec=2),
|
||
|
) # 57' 51.96''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# The angular separation between two celestial objects can be easily
|
||
|
# computed with the 'angular_separation()' function
|
||
|
|
||
|
alpha1 = Angle(14, 15, 39.7, ra=True)
|
||
|
delta1 = Angle(19, 10, 57.0)
|
||
|
alpha2 = Angle(13, 25, 11.6, ra=True)
|
||
|
delta2 = Angle(-11, 9, 41.0)
|
||
|
sep_ang = angular_separation(alpha1, delta1, alpha2, delta2)
|
||
|
print_me(
|
||
|
"Angular separation between two given celestial bodies (degrees)",
|
||
|
round(sep_ang, 3),
|
||
|
) # 32.793
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# We can compute the minimum angular separation achieved between two
|
||
|
# celestial objects. For that, we must provide the positions at three
|
||
|
# equidistant epochs:
|
||
|
|
||
|
# EPOCH: Sep 13th, 1978, 0h TT:
|
||
|
alpha1_1 = Angle(10, 29, 44.27, ra=True)
|
||
|
delta1_1 = Angle(11, 2, 5.9)
|
||
|
alpha2_1 = Angle(10, 33, 29.64, ra=True)
|
||
|
delta2_1 = Angle(10, 40, 13.2)
|
||
|
# EPOCH: Sep 14th, 1978, 0h TT:
|
||
|
alpha1_2 = Angle(10, 36, 19.63, ra=True)
|
||
|
delta1_2 = Angle(10, 29, 51.7)
|
||
|
alpha2_2 = Angle(10, 33, 57.97, ra=True)
|
||
|
delta2_2 = Angle(10, 37, 33.4)
|
||
|
# EPOCH: Sep 15th, 1978, 0h TT:
|
||
|
alpha1_3 = Angle(10, 43, 1.75, ra=True)
|
||
|
delta1_3 = Angle(9, 55, 16.7)
|
||
|
alpha2_3 = Angle(10, 34, 26.22, ra=True)
|
||
|
delta2_3 = Angle(10, 34, 53.9)
|
||
|
a = minimum_angular_separation(
|
||
|
alpha1_1,
|
||
|
delta1_1,
|
||
|
alpha1_2,
|
||
|
delta1_2,
|
||
|
alpha1_3,
|
||
|
delta1_3,
|
||
|
alpha2_1,
|
||
|
delta2_1,
|
||
|
alpha2_2,
|
||
|
delta2_2,
|
||
|
alpha2_3,
|
||
|
delta2_3,
|
||
|
)
|
||
|
# Epoch fraction:
|
||
|
print_me("Minimum angular separation, epoch fraction", round(a[0], 6))
|
||
|
# -0.370726
|
||
|
# NOTE: Given that 'n' is negative, and Sep 14th is the middle epoch (n=0),
|
||
|
# then the minimum angular separation is achieved on Sep 13th, specifically
|
||
|
# at: 1.0 - 0.370726 = 0.629274 => Sep 13.629274 = Sep 13th, 15h 6' 9''
|
||
|
|
||
|
# Minimum angular separation:
|
||
|
print_me("Minimum angular separation", a[1].dms_str(n_dec=0)) # 3' 44.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# If two objects have the same right ascension, then the relative position
|
||
|
# angle between them must be 0 (or 180)
|
||
|
alpha1 = Angle(14, 15, 39.7, ra=True)
|
||
|
delta1 = Angle(19, 10, 57.0)
|
||
|
alpha2 = Angle(14, 15, 39.7, ra=True) # Same as alpha1
|
||
|
delta2 = Angle(-11, 9, 41.0)
|
||
|
pos_ang = relative_position_angle(alpha1, delta1, alpha2, delta2)
|
||
|
print_me("Relative position angle", round(pos_ang, 1)) # 0.0
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Planetary conjunctions may be computed with the appropriate function
|
||
|
alpha1_1 = Angle(10, 24, 30.125, ra=True)
|
||
|
delta1_1 = Angle(6, 26, 32.05)
|
||
|
alpha1_2 = Angle(10, 25, 0.342, ra=True)
|
||
|
delta1_2 = Angle(6, 10, 57.72)
|
||
|
alpha1_3 = Angle(10, 25, 12.515, ra=True)
|
||
|
delta1_3 = Angle(5, 57, 33.08)
|
||
|
alpha1_4 = Angle(10, 25, 6.235, ra=True)
|
||
|
delta1_4 = Angle(5, 46, 27.07)
|
||
|
alpha1_5 = Angle(10, 24, 41.185, ra=True)
|
||
|
delta1_5 = Angle(5, 37, 48.45)
|
||
|
alpha2_1 = Angle(10, 27, 27.175, ra=True)
|
||
|
delta2_1 = Angle(4, 4, 41.83)
|
||
|
alpha2_2 = Angle(10, 26, 32.410, ra=True)
|
||
|
delta2_2 = Angle(3, 55, 54.66)
|
||
|
alpha2_3 = Angle(10, 25, 29.042, ra=True)
|
||
|
delta2_3 = Angle(3, 48, 3.51)
|
||
|
alpha2_4 = Angle(10, 24, 17.191, ra=True)
|
||
|
delta2_4 = Angle(3, 41, 10.25)
|
||
|
alpha2_5 = Angle(10, 22, 57.024, ra=True)
|
||
|
delta2_5 = Angle(3, 35, 16.61)
|
||
|
alpha1_list = [alpha1_1, alpha1_2, alpha1_3, alpha1_4, alpha1_5]
|
||
|
delta1_list = [delta1_1, delta1_2, delta1_3, delta1_4, delta1_5]
|
||
|
alpha2_list = [alpha2_1, alpha2_2, alpha2_3, alpha2_4, alpha2_5]
|
||
|
delta2_list = [delta2_1, delta2_2, delta2_3, delta2_4, delta2_5]
|
||
|
pc = planetary_conjunction(alpha1_list, delta1_list, alpha2_list,
|
||
|
delta2_list)
|
||
|
print_me("Epoch fraction 'n' for planetary conjunction", round(pc[0], 5))
|
||
|
# 0.23797
|
||
|
print_me(
|
||
|
"Difference in declination at conjunction", pc[1].dms_str(n_dec=1)
|
||
|
) # 2d 8' 21.8''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# A planetary conjunction with a star is a little bit simpler
|
||
|
alpha_1 = Angle(15, 3, 51.937, ra=True)
|
||
|
delta_1 = Angle(-8, 57, 34.51)
|
||
|
alpha_2 = Angle(15, 9, 57.327, ra=True)
|
||
|
delta_2 = Angle(-9, 9, 3.88)
|
||
|
alpha_3 = Angle(15, 15, 37.898, ra=True)
|
||
|
delta_3 = Angle(-9, 17, 37.94)
|
||
|
alpha_4 = Angle(15, 20, 50.632, ra=True)
|
||
|
delta_4 = Angle(-9, 23, 16.25)
|
||
|
alpha_5 = Angle(15, 25, 32.695, ra=True)
|
||
|
delta_5 = Angle(-9, 26, 1.01)
|
||
|
alpha_star = Angle(15, 17, 0.446, ra=True)
|
||
|
delta_star = Angle(-9, 22, 58.47)
|
||
|
alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5]
|
||
|
delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5]
|
||
|
pc = planet_star_conjunction(alpha_list, delta_list, alpha_star,
|
||
|
delta_star)
|
||
|
print_me("Epoch fraction 'n' for planetary conjunction with star",
|
||
|
round(pc[0], 4)) # 0.2551
|
||
|
print_me("Difference in declination with star at conjunction",
|
||
|
pc[1].dms_str(n_dec=0)) # 3' 38.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# It is possible to compute when a planet and two other stars will be in a
|
||
|
# straight line
|
||
|
alpha_1 = Angle(7, 55, 55.36, ra=True)
|
||
|
delta_1 = Angle(21, 41, 3.0)
|
||
|
alpha_2 = Angle(7, 58, 22.55, ra=True)
|
||
|
delta_2 = Angle(21, 35, 23.4)
|
||
|
alpha_3 = Angle(8, 0, 48.99, ra=True)
|
||
|
delta_3 = Angle(21, 29, 38.2)
|
||
|
alpha_4 = Angle(8, 3, 14.66, ra=True)
|
||
|
delta_4 = Angle(21, 23, 47.5)
|
||
|
alpha_5 = Angle(8, 5, 39.54, ra=True)
|
||
|
delta_5 = Angle(21, 17, 51.4)
|
||
|
alpha_star1 = Angle(7, 34, 16.40, ra=True)
|
||
|
delta_star1 = Angle(31, 53, 51.2)
|
||
|
alpha_star2 = Angle(7, 45, 0.10, ra=True)
|
||
|
delta_star2 = Angle(28, 2, 12.5)
|
||
|
alpha_list = [alpha_1, alpha_2, alpha_3, alpha_4, alpha_5]
|
||
|
delta_list = [delta_1, delta_2, delta_3, delta_4, delta_5]
|
||
|
n = planet_stars_in_line(alpha_list, delta_list, alpha_star1, delta_star1,
|
||
|
alpha_star2, delta_star2)
|
||
|
print_me("Epoch fraction 'n' when bodies are in a straight line",
|
||
|
round(n, 4)) # 0.2233
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# The function 'straight_line()' computes if three celestial bodies are in
|
||
|
# line providing the angle with which the bodies differ from a great circle
|
||
|
alpha1 = Angle(5, 32, 0.40, ra=True)
|
||
|
delta1 = Angle(0, -17, 56.9)
|
||
|
alpha2 = Angle(5, 36, 12.81, ra=True)
|
||
|
delta2 = Angle(-1, 12, 7.0)
|
||
|
alpha3 = Angle(5, 40, 45.52, ra=True)
|
||
|
delta3 = Angle(-1, 56, 33.3)
|
||
|
psi, omega = straight_line(alpha1, delta1, alpha2, delta2, alpha3, delta3)
|
||
|
print_me("Angle deviation from a straight line", psi.dms_str(n_dec=0))
|
||
|
# 7d 31' 1.0''
|
||
|
print_me("Angular distance of central point to the straight line",
|
||
|
omega.dms_str(n_dec=0)) # -5' 24.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Let's compute the size of the smallest circle that contains three bodies
|
||
|
alpha1 = Angle(12, 41, 8.63, ra=True)
|
||
|
delta1 = Angle(-5, 37, 54.2)
|
||
|
alpha2 = Angle(12, 52, 5.21, ra=True)
|
||
|
delta2 = Angle(-4, 22, 26.2)
|
||
|
alpha3 = Angle(12, 39, 28.11, ra=True)
|
||
|
delta3 = Angle(-1, 50, 3.7)
|
||
|
d = circle_diameter(alpha1, delta1, alpha2, delta2, alpha3, delta3)
|
||
|
print_me(
|
||
|
"Diameter of smallest circle containing three celestial bodies",
|
||
|
d.dms_str(n_dec=0),
|
||
|
) # 4d 15' 49.0''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Now, let's find the apparent position of a star (Theta Persei) for a
|
||
|
# given epoch
|
||
|
epoch = Epoch(2028, 11, 13.19)
|
||
|
alpha = Angle(2, 46, 11.331, ra=True)
|
||
|
delta = Angle(49, 20, 54.54)
|
||
|
sun_lon = Angle(231.328)
|
||
|
app_alpha, app_delta = apparent_position(epoch, alpha, delta, sun_lon)
|
||
|
print_me("Apparent right ascension", app_alpha.ra_str(n_dec=2))
|
||
|
# 2h 46' 14.39''
|
||
|
print_me("Apparent declination", app_delta.dms_str(n_dec=2))
|
||
|
# 49d 21' 7.45''
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Convert orbital elements of an object from one equinox to another
|
||
|
epoch0 = Epoch(2358042.5305)
|
||
|
epoch = Epoch(2433282.4235)
|
||
|
i0 = Angle(47.122)
|
||
|
arg0 = Angle(151.4486)
|
||
|
lon0 = Angle(45.7481)
|
||
|
i1, arg1, lon1 = orbital_equinox2equinox(epoch0, epoch, i0, arg0, lon0)
|
||
|
print_me("New inclination", round(i1(), 3)) # 47.138
|
||
|
print_me("New argument of perihelion", round(arg1(), 4)) # 151.4782
|
||
|
print_me("New longitude of ascending node", round(lon1(), 4)) # 48.6037
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Compute the eccentric and true anomalies using Kepler's equation
|
||
|
eccentricity = 0.1
|
||
|
mean_anomaly = Angle(5.0)
|
||
|
e, v = kepler_equation(eccentricity, mean_anomaly)
|
||
|
print_me("Eccentric anomaly, Case #1", round(e(), 6)) # 5.554589
|
||
|
print_me("True anomaly, Case #1", round(v(), 6)) # 6.139762
|
||
|
e, v = kepler_equation(0.99, Angle(0.2, radians=True))
|
||
|
print_me("Eccentric anomaly, Case #2", round(e(), 8)) # 61.13444578
|
||
|
print_me("True anomaly, Case #2", round(v(), 6)) # 166.311977
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Compute the velocity of a body in a given point of its (unperturbated
|
||
|
# elliptic) orbit
|
||
|
r = 1.0
|
||
|
a = 17.9400782
|
||
|
v = velocity(r, a)
|
||
|
print_me("Velocity at 1 AU", round(v, 2)) # 41.53
|
||
|
|
||
|
# Compute the velocity at perihelion
|
||
|
e = 0.96727426
|
||
|
vp = velocity_perihelion(e, a)
|
||
|
print_me("Velocity at perihelion", round(vp, 2)) # 54.52
|
||
|
|
||
|
# Compute the velocity at aphelion
|
||
|
va = velocity_aphelion(e, a)
|
||
|
print_me("Velocity at aphelion", round(va, 2)) # 0.91
|
||
|
|
||
|
# Calculate the length of the orbit
|
||
|
length = length_orbit(e, a)
|
||
|
print_me("Length of the orbit (AU)", round(length, 2)) # 77.06
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Passage through the nodes of an elliptic orbit
|
||
|
omega = Angle(111.84644)
|
||
|
e = 0.96727426
|
||
|
a = 17.9400782
|
||
|
t = Epoch(1986, 2, 9.45891)
|
||
|
time, r = passage_nodes_elliptic(omega, e, a, t)
|
||
|
y, m, d = time.get_date()
|
||
|
d = round(d, 2)
|
||
|
print("Time of passage through ascending node: {}/{}/{}".format(y, m, d))
|
||
|
# 1985/11/9.16
|
||
|
print("Radius vector at ascending node: {}".format(round(r, 4))) # 1.8045
|
||
|
|
||
|
# Passage through the nodes of a parabolic orbit
|
||
|
omega = Angle(154.9103)
|
||
|
q = 1.324502
|
||
|
t = Epoch(1989, 8, 20.291)
|
||
|
time, r = passage_nodes_parabolic(omega, q, t, ascending=False)
|
||
|
y, m, d = time.get_date()
|
||
|
d = round(d, 2)
|
||
|
print("Time of passage through descending node: {}/{}/{}".format(y, m, d))
|
||
|
# 1989/9/17.64
|
||
|
print("Radius vector at descending node: {}".format(round(r, 4))) # 1.3901
|
||
|
|
||
|
print("")
|
||
|
|
||
|
# Compute the phase angle
|
||
|
sun_dist = 0.724604
|
||
|
earth_dist = 0.910947
|
||
|
sun_earth_dist = 0.983824
|
||
|
angle = phase_angle(sun_dist, earth_dist, sun_earth_dist)
|
||
|
print_me("Phase angle", round(angle, 2)) # 72.96
|
||
|
# Now, let's compute the illuminated fraction of the disk
|
||
|
k = illuminated_fraction(sun_dist, earth_dist, sun_earth_dist)
|
||
|
print_me("Illuminated fraction of planet disk", round(k, 3)) # 0.647
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
|
||
|
main()
|