Skip to main content

Overview

The AstroEngine class provides high-precision astronomical calculations based on Meeus algorithms, implementing the ELP 2000-82 lunar theory and VSOP87 solar theory. It handles celestial body positioning, coordinate transformations, and topocentric corrections. Location: widgets/sky_widget.py:68-439

Class: AstroEngine

Constants

ConstantValueDescription
DEG_TO_RADπ / 180.0Degrees to radians conversion
RAD_TO_DEG180.0 / πRadians to degrees conversion
AU_IN_KM149597870.7Astronomical Unit in kilometers
EARTH_RADIUS_KM6378.14Earth’s equatorial radius

Methods

get_julian_century()

Converts UTC datetime to Julian century (T) for astronomical calculations.
@staticmethod
def get_julian_century(dt_utc) -> Tuple[float, float]
Parameters
  • dt_utc (datetime): UTC datetime object
Returns
  • tuple: (T, jd) where:
    • T (float): Julian centuries from J2000.0 epoch (TDB)
    • jd (float): Julian Day number (UTC)
Algorithm
  1. Calculate Julian Day using standard formula:
    • JD = day + ((153 * m + 2) // 5) + 365 * y + y // 4 - y // 100 + y // 400 - 32045
  2. Apply Delta T correction (~72s for 2026): JD_TDB = JD + 72.0/86400.0
  3. Calculate centuries: T = (JD_TDB - 2451545.0) / 36525.0
Example
from datetime import datetime, timezone
dt = datetime(2026, 3, 3, 12, 0, 0, tzinfo=timezone.utc)
T, jd = AstroEngine.get_julian_century(dt)
print(f"T = {T:.6f}, JD = {jd:.2f}")
# T = 0.261644, JD = 2460738.00

get_moon_position_elp()

Calculates geocentric lunar position using ELP 2000-82 truncated theory.
@staticmethod
def get_moon_position_elp(T: float) -> Tuple[float, float, float]
Parameters
  • T (float): Julian centuries from J2000.0
Returns
  • tuple: (longitude, latitude, distance) where:
    • longitude (float): Geocentric ecliptic longitude (degrees)
    • latitude (float): Geocentric ecliptic latitude (degrees)
    • distance (float): Earth-Moon distance (kilometers)
Fundamental Arguments (Degrees)
  • L' = Mean longitude: 218.3164477 + 481267.8812542 * T
  • D = Mean elongation: 297.8501921 + 445267.1114034 * T
  • M = Sun’s mean anomaly: 357.5291092 + 35999.0502909 * T
  • M' = Moon’s mean anomaly: 134.9633964 + 477198.8675055 * T
  • F = Argument of latitude: 93.2720950 + 483202.0175381 * T
Periodic Terms (Longitude) - in microdegreees (10⁻⁶°)
  • +6288774 * sin(2D - M') - Major inequality
  • -1274027 * sin(2D - 2M') - Evection
  • +658314 * sin(2D) - Variation
  • +213618 * sin(2M') - Annual equation
  • -185116 * sin(M) - Solar anomaly term
  • -114332 * sin(2F) - Reduction to ecliptic
Periodic Terms (Latitude) - in microdegreees
  • +5128122 * sin(F)
  • +280602 * sin(M' + F)
  • +277693 * sin(M' - F)
  • +173237 * sin(2D - F)
Distance Terms (km)
  • Base: 385000.56 km
  • -20905.355 * cos(M')
  • -3699.111 * cos(2D - M')
  • -2955.968 * cos(2D)
  • -569.925 * cos(2M')
Final Formula
λ = L' + Σl/1000000 + 0.85°  (epoch correction)
β = Σb/1000000
Δ = 385000.56 + Σr

get_sun_position_vsop()

Calculates geocentric solar position using VSOP87 algorithm.
@staticmethod
def get_sun_position_vsop(T: float) -> Tuple[float, float, float]
Parameters
  • T (float): Julian centuries from J2000.0
Returns
  • tuple: (longitude, latitude, distance) where:
    • longitude (float): Geocentric ecliptic longitude (degrees)
    • latitude (float): Always 0.0 (Sun’s ecliptic latitude)
    • distance (float): Earth-Sun distance (kilometers)
Algorithm
  1. Mean geometric longitude: L₀ = 280.46646 + 36000.76983 * T
  2. Mean anomaly: M = 357.52911 + 35999.05029 * T
  3. Eccentricity: e = 0.016708634 - 0.000042037 * T
  4. Equation of center:
    C = (1.914602 - 0.004817*T) * sin(M)
      + (0.019993 - 0.000101*T) * sin(2M)
      + 0.000289 * sin(3M)
    
  5. True longitude: λ = L₀ + C
  6. Distance (AU to km):
    R_AU = 1.000001018 * (1 - e²) / (1 + e*cos(M + C))
    distance = R_AU * 149597870.7
    

ecliptic_to_equatorial()

Transforms ecliptic coordinates to equatorial (RA/Dec) coordinates.
@staticmethod
def ecliptic_to_equatorial(lon: float, lat: float, T: float) -> Tuple[float, float]
Parameters
  • lon (float): Ecliptic longitude (degrees)
  • lat (float): Ecliptic latitude (degrees)
  • T (float): Julian centuries from J2000.0
Returns
  • tuple: (ra, dec) where:
    • ra (float): Right ascension (degrees, 0-360)
    • dec (float): Declination (degrees, -90 to +90)
Algorithm
  1. Calculate obliquity of ecliptic: ε = 23.4392911° - 0.0130042*T
  2. Convert to Cartesian:
    x = cos(β) * cos(λ)
    y = cos(β) * sin(λ) * cos(ε) - sin(β) * sin(ε)
    z = cos(β) * sin(λ) * sin(ε) + sin(β) * cos(ε)
    
  3. Convert to spherical:
    α = atan2(y, x)
    δ = asin(z)
    

get_topocentric_position()

Converts geocentric coordinates to topocentric (observer-based) coordinates, applying parallax correction.
@staticmethod
def get_topocentric_position(
    ra_geo: float,
    dec_geo: float, 
    dist_km: float,
    obs_lat: float,
    obs_lon: float,
    jd: float
) -> Tuple[float, float, float]
Parameters
  • ra_geo (float): Geocentric right ascension (degrees)
  • dec_geo (float): Geocentric declination (degrees)
  • dist_km (float): Distance to object (kilometers)
  • obs_lat (float): Observer latitude (degrees, -90 to +90)
  • obs_lon (float): Observer longitude (degrees, -180 to +180)
  • jd (float): Julian Day number
Returns
  • tuple: (ra_topo, dec_topo, lst) where:
    • ra_topo (float): Topocentric right ascension (degrees)
    • dec_topo (float): Topocentric declination (degrees)
    • lst (float): Local Sidereal Time (degrees)
Algorithm (Meeus Chapter 40)
  1. Calculate GMST:
    GMST = 280.46061837 + 360.98564736629*(JD - 2451545.0) + 0.000387933*T²
    
  2. Local Sidereal Time: LST = GMST + λ (observer longitude)
  3. Hour Angle: H = LST - α
  4. Horizontal parallax sine: sin(π) = R_Earth / distance
  5. WGS84 geocentric coordinates:
    ρ sin φ' = 0.996647 * sin(φ)
    ρ cos φ' = cos(φ)
    
  6. Parallax corrections:
    Δα = atan2(-ρ cos φ' * sin π * sin H, cos δ - ρ cos φ' * sin π * cos H)
    Δδ = atan2((sin δ - ρ sin φ' * sin π) * cos(Δα), cos δ - ρ cos φ' * sin π * cos H)
    
  7. Topocentric position:
    α_topo = α_geo + Δα
    δ_topo = δ_geo + Δδ
    
Note: Critical for accurate Moon positioning, where parallax can shift position by up to 1° from geocentric coordinates.

get_planet_heliocentric()

Calculates heliocentric position of inner and outer planets using simplified Keplerian elements.
@staticmethod
def get_planet_heliocentric(name: str, T: float) -> Tuple[float, float, float]
Parameters
  • name (str): Planet name - 'mercury', 'venus', 'mars', 'jupiter', 'saturn'
  • T (float): Julian centuries from J2000.0
Returns
  • tuple: (L_hel, B_hel, R_hel) where:
    • L_hel (float): Heliocentric ecliptic longitude (degrees)
    • B_hel (float): Heliocentric ecliptic latitude (degrees)
    • R_hel (float): Heliocentric radius vector (AU)
Orbital Elements (J2000.0 Mean Elements)
  • L = Mean longitude
  • π = Longitude of perihelion
  • e = Eccentricity
  • i = Inclination to ecliptic
  • Ω = Longitude of ascending node
  • a = Semi-major axis (AU)
Algorithm
  1. Calculate mean anomaly: M = L - π
  2. Solve Kepler’s equation iteratively (3 iterations):
    E = M + e * sin(E)
    
  3. True anomaly:
    v = atan2(√(1-e²) * sin(E), cos(E) - e)
    
  4. Radius vector: r = a * (1 - e * cos(E))
  5. Argument of latitude: u = v + ω where ω = π - Ω
  6. Transform to ecliptic coordinates:
    X = r * (cos(Ω)*cos(u) - sin(Ω)*cos(i)*sin(u))
    Y = r * (sin(Ω)*cos(u) + cos(Ω)*cos(i)*sin(u))
    Z = r * sin(i) * sin(u)
    
  7. Convert to spherical:
    L_hel = atan2(Y, X)
    B_hel = asin(Z / √(X²+Y²+Z²))
    R_hel = √(X²+Y²+Z²)
    

get_planet_geocentric()

Converts heliocentric planet position to geocentric position.
@staticmethod
def get_planet_geocentric(
    p_L: float, p_B: float, p_R: float,
    earth_L: float, earth_B: float, earth_R: float
) -> Tuple[float, float, float]
Parameters
  • p_L, p_B, p_R: Planet’s heliocentric longitude (°), latitude (°), radius (AU)
  • earth_L, earth_B, earth_R: Earth’s heliocentric coordinates
Returns
  • tuple: (λ_geo, β_geo, Δ) where:
    • λ_geo (float): Geocentric ecliptic longitude (degrees)
    • β_geo (float): Geocentric ecliptic latitude (degrees)
    • Δ (float): Earth-planet distance (AU)
Algorithm
  1. Convert planet to heliocentric Cartesian
  2. Convert Earth to heliocentric Cartesian
  3. Geocentric vector: G = P - E
  4. Convert back to spherical coordinates

calculate_satellite_magnitude()

Calculates apparent visual magnitude of satellites using phase function.
@staticmethod
def calculate_satellite_magnitude(
    sat_range_km: float,
    phase_angle_rad: float,
    std_mag: float = -1.8
) -> float
Parameters
  • sat_range_km (float): Distance to satellite (km)
  • phase_angle_rad (float): Phase angle in radians (0 = full, π = new)
  • std_mag (float): Standard magnitude at 1000 km, 0° phase (default: -1.8)
Returns
  • float: Apparent visual magnitude
Formula
m = m_std - 15 + 5*log₁₀(range_km) - 2.5*log₁₀(F(φ))

where F(φ) = (1/π) * [sin(φ) + (π - φ)*cos(φ)]  (diffuse sphere)
Example
import math
# ISS at 400 km, 45° phase angle
mag = AstroEngine.calculate_satellite_magnitude(
    sat_range_km=400.0,
    phase_angle_rad=math.radians(45),
    std_mag=-1.8
)
print(f"Apparent magnitude: {mag:.1f}")
# Apparent magnitude: -3.2

normalize()

Normalizes angles to [0, 360) degree range.
@staticmethod
def normalize(angle: float) -> float
Parameters
  • angle (float): Angle in degrees
Returns
  • float: Normalized angle (0 ≤ result < 360)
Example
AstroEngine.normalize(450.0)   # Returns 90.0
AstroEngine.normalize(-30.0)   # Returns 330.0

Usage Example

from datetime import datetime, timezone
from widgets.sky_widget import AstroEngine

# Observer location: Madrid, Spain
obs_lat = 40.4168   # degrees N
obs_lon = -3.7038   # degrees W

# Current UTC time
dt_utc = datetime.now(timezone.utc)

# Get Julian century
T, jd = AstroEngine.get_julian_century(dt_utc)

# Calculate Moon position
moon_lon, moon_lat, moon_dist = AstroEngine.get_moon_position_elp(T)
print(f"Moon ecliptic: λ={moon_lon:.4f}°, β={moon_lat:.4f}°, Δ={moon_dist:.1f} km")

# Convert to equatorial coordinates
moon_ra, moon_dec = AstroEngine.ecliptic_to_equatorial(moon_lon, moon_lat, T)
print(f"Moon equatorial: α={moon_ra:.4f}°, δ={moon_dec:.4f}°")

# Apply topocentric correction
moon_ra_topo, moon_dec_topo, lst = AstroEngine.get_topocentric_position(
    moon_ra, moon_dec, moon_dist, obs_lat, obs_lon, jd
)
print(f"Moon topocentric: α={moon_ra_topo:.4f}°, δ={moon_dec_topo:.4f}°")

# Calculate Sun position
sun_lon, _, sun_dist = AstroEngine.get_sun_position_vsop(T)
sun_ra, sun_dec = AstroEngine.ecliptic_to_equatorial(sun_lon, 0.0, T)
print(f"Sun: α={sun_ra:.4f}°, δ={sun_dec:.4f}°, Δ={sun_dist:.0f} km")

# Calculate Jupiter position
jup_L, jup_B, jup_R = AstroEngine.get_planet_heliocentric('jupiter', T)
earth_L = AstroEngine.normalize(sun_lon + 180.0)  # Earth opposite to Sun
jup_lon, jup_lat, jup_dist = AstroEngine.get_planet_geocentric(
    jup_L, jup_B, jup_R, earth_L, 0.0, sun_dist / AstroEngine.AU_IN_KM
)
jup_ra, jup_dec = AstroEngine.ecliptic_to_equatorial(jup_lon, jup_lat, T)
print(f"Jupiter: α={jup_ra:.4f}°, δ={jup_dec:.4f}°")

Accuracy Notes

  • Moon position: ~1 arcminute accuracy (truncated ELP 2000-82)
  • Sun position: ~0.01° accuracy (low-order VSOP87)
  • Planet positions: ~1° accuracy for inner planets, ~0.5° for outer planets (1800-2050)
  • Time range: Optimized for years 1900-2100
  • Topocentric correction: Essential for Moon (parallax up to 1°), negligible for Sun/planets

References

  • Meeus, Jean. Astronomical Algorithms, 2nd Edition, Willmann-Bell, 1998
  • ELP 2000-82: Chapront-Touzé & Chapront lunar theory
  • VSOP87: Bretagnon & Francou planetary theory

Build docs developers (and LLMs) love