Chapter 5 — Calculating Planetary Positions #
What You Will Learn #
In this chapter, you will master calc_ut — the most used function in the library. You will learn what it returns, all available flags, how velocity and retrograde motion work, and how to calculate planetary phenomena such as magnitude, phase, and elongation.
5.1 The Main Function: calc_ut #
The signature is simple:
pos, flag = ephem.calc_ut(jd_ut, body, iflag)
jd_ut: Julian Day in UT (“civil” time)body: celestial body identifier (SE_SUN,SE_MOON, etc.)iflag: calculation flags (combined with|)
The result consists of two parts: a pos tuple with 6 numbers and an integer flag confirming the calculation options used. The 6 numbers describe where the celestial body is located and how it is moving.
The First Three: Where It Is #
To understand these values, imagine the zodiac as a long circular road of 360 km. Every planet has a position along this road, a lateral distance from the road, and a distance from you.
-
pos[0]— Ecliptic Longitude is the planet’s position along this “zodiacal road”. It ranges from 0° to 360° and corresponds to the 12 signs: the first 30° are Aries, from 30° to 60° is Taurus, and so on. Ifpos[0]is 105°, it means the planet is located at 15° of Cancer (because Cancer starts at 90°, and 105 - 90 = 15). It is the most important data point in astrology: when someone says “my Sun is in Leo”, they mean that the Sun’s ecliptic longitude at the time of their birth was between 120° and 150°. -
pos[1]— Ecliptic Latitude is the planet’s distance from the ecliptic plane, measured in degrees. Think of the ecliptic as a floor: the latitude tells you how far the planet is above (+) or below (-) that floor. The Sun is always at ~0° (it defines the floor itself). The Moon can rise up to ±5.1°. Most planets stay within a few degrees of the ecliptic, but Pluto can reach up to ~17°. -
pos[2]— Distance is how far the celestial body is from Earth, measured in astronomical units (1 AU = ~150 million km, the average Earth-Sun distance). To give you an idea: the Moon is very close (~0.0026 AU, about 384,000 km), Mars varies greatly between 0.37 AU when it’s on our side of the orbit and 2.68 AU when it’s on the other side of the Sun, and Neptune is at about 30 AU.
The Last Three: How It Is Moving #
-
pos[3]— Speed in Longitude tells you how many degrees the planet advances (or retreats) along the zodiac each day. The Sun moves at ~1°/day — almost imperceptible to the naked eye. The Moon races at ~13°/day, crossing an entire sign in just over two days. Saturn drags along at ~0.03°/day. If this value is negative, the planet is in retrograde motion: it appears to move backwards along the zodiac (we will talk about this in section 5.3). -
pos[4]— Speed in Latitude tells you how much the planet is approaching or moving away from the ecliptic each day. It is usually a small value — latitude changes slowly. -
pos[5]— Speed in Distance tells you whether the planet is approaching (negative value) or moving away from (positive) the Earth, in AU/day.
How Values Change with Flags #
The values described above are what you get with the default flag (0). But if you pass certain flags, the meaning of the 6 numbers changes completely — the structure of the tuple remains the same, but the numbers inside represent different things:
-
With
SEFLG_EQUATORIAL,pos[0]is no longer the ecliptic longitude but the Right Ascension (the position along the celestial equator, from 0° to 360°), andpos[1]becomes the Declination (the distance from the celestial equator, from -90° to +90°). This is the system used by telescopes. Distance and speeds keep the same meaning, but referred to the equatorial system. -
With
SEFLG_XYZ, all 6 values become Cartesian coordinates:pos[0],pos[1],pos[2]are the X, Y, Z positions in astronomical units, andpos[3],pos[4],pos[5]are the corresponding speeds in AU/day. There are no more degrees — only distances and speeds in three-dimensional space. -
With
SEFLG_RADIANS, angular values (longitude, latitude, speed) are expressed in radians instead of degrees (2π radians = 360°). Useful if you need to perform trigonometric calculations without converting.
If you don’t use any of these flags (or pass 0), you always get ecliptic coordinates in degrees — the system of astrology.
Main Celestial Bodies #
| Constant | Value | Body |
|---|---|---|
SE_SUN |
0 | Sun |
SE_MOON |
1 | Moon |
SE_MERCURY |
2 | Mercury |
SE_VENUS |
3 | Venus |
SE_MARS |
4 | Mars |
SE_JUPITER |
5 | Jupiter |
SE_SATURN |
6 | Saturn |
SE_URANUS |
7 | Uranus |
SE_NEPTUNE |
8 | Neptune |
SE_PLUTO |
9 | Pluto |
SE_MEAN_NODE |
10 | Mean Lunar Node |
SE_TRUE_NODE |
11 | True Lunar Node |
SE_MEAN_APOG |
12 | Mean Lunar Apogee (Lilith) |
SE_OSCU_APOG |
13 | Osculating Lunar Apogee |
SE_CHIRON |
15 | Chiron |
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 12.0)
# All planets at once
bodies = [
(ephem.SE_SUN, "Sun"), (ephem.SE_MOON, "Moon"),
(ephem.SE_MERCURY, "Mercury"), (ephem.SE_VENUS, "Venus"),
(ephem.SE_MARS, "Mars"), (ephem.SE_JUPITER, "Jupiter"),
(ephem.SE_SATURN, "Saturn"), (ephem.SE_URANUS, "Uranus"),
(ephem.SE_NEPTUNE, "Neptune"), (ephem.SE_PLUTO, "Pluto"),
]
signs = ["Ari", "Tau", "Gem", "Cnc", "Leo", "Vir",
"Lib", "Sco", "Sgr", "Cap", "Aqr", "Psc"]
for body_id, name in bodies:
pos, _ = ephem.calc_ut(jd, body_id, ephem.SEFLG_SPEED)
lon = pos[0]
sign = signs[int(lon / 30)]
degrees = lon % 30
print(f"{name:10s} {degrees:5.1f}° {sign} (vel: {pos[3]:+.4f}°/d)")
Sun 19.1° Ari (vel: +0.9831°/d)
Moon 15.4° Ari (vel: +15.0292°/d)
Mercury 25.0° Ari (vel: -0.6229°/d)
Venus 4.1° Ari (vel: +1.2353°/d)
Mars 12.8° Psc (vel: +0.7775°/d)
Jupiter 19.0° Tau (vel: +0.2211°/d)
Saturn 14.4° Psc (vel: +0.1078°/d)
Uranus 21.2° Tau (vel: +0.0512°/d)
Neptune 28.2° Psc (vel: +0.0359°/d)
Pluto 2.0° Aqr (vel: +0.0114°/d)
5.2 Calculation Flags #
The third argument of calc_ut is an integer that controls how the calculation is performed. Passing 0 means “use all default settings”: ecliptic, geocentric coordinates, with aberration and nutation.
To change behavior, use the SEFLG_* constants and combine them with the | (bitwise OR) operator. For example, ephem.SEFLG_EQUATORIAL | ephem.SEFLG_SPEED requests equatorial coordinates with speed. You can combine as many as you want.
Here are the most important ones, grouped by category.
Coordinates — they change the meaning of the returned values:
- No flag (default): ecliptic coordinates — longitude, latitude, distance
SEFLG_EQUATORIAL: equatorial coordinates — Right Ascension, Declination, distanceSEFLG_XYZ: Cartesian coordinates — X, Y, Z in astronomical unitsSEFLG_RADIANS: angles in radians instead of degrees
Center of observation — where you are “looking” from:
- No flag (default): geocentric — from the center of the Earth
SEFLG_HELCTR: heliocentric — from the center of the SunSEFLG_BARYCTR: barycentric — from the center of mass of the Solar SystemSEFLG_TOPOCTR: topocentric — from your position on the Earth’s surface (requiresset_topo()first)
Reference system — which “zero point” to use:
- No flag (default): equinox of date — the system of astrology
SEFLG_J2000: referred to J2000.0 — the system of astronomical catalogsSEFLG_ICRS: ICRS system — almost identical to J2000SEFLG_SIDEREAL: sidereal coordinates — requiresset_sid_mode()firstSEFLG_NONUT: without nutation — returns “mean” coordinates instead of “true” ones
Corrections — enables or disables physical effects:
SEFLG_SPEED: also calculates speeds (pos[3], pos[4], pos[5]). Almost always useful.SEFLG_TRUEPOS: true geometric position, without light-time correctionSEFLG_NOABERR: without aberration (the displacement due to Earth’s motion)SEFLG_NOGDEFL: without gravitational deflection of light
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 12.0)
# Common combination: equatorial with speed
pos, _ = ephem.calc_ut(jd, ephem.SE_MARS,
ephem.SEFLG_EQUATORIAL | ephem.SEFLG_SPEED)
print(f"RA: {pos[0]:.4f}°, Dec: {pos[1]:+.4f}°")
print(f"Vel RA: {pos[3]:+.4f}°/d, Vel Dec: {pos[4]:+.4f}°/d")
RA: 344.6682°, Dec: -7.8866°
Vel RA: +0.7256°/d, Vel Dec: +0.2960°/d
5.3 Speed and Retrograde Motion #
The speed in longitude (pos[3]) indicates how much the planet moves each day:
- Positive = direct motion (the planet advances along the zodiac)
- Negative = retrograde motion (the planet appears to go backwards)
- Zero = station (the planet “stops” before reversing direction)
Retrograde motion is an optical effect: when the Earth overtakes an outer planet (or is overtaken by an inner one), that planet appears to move backward relative to the stars. It is like overtaking a car on the highway — for a moment it seems like the other car is going backward.
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 12.0)
# Check if Mercury is retrograde
retro = ephem.is_retrograde(ephem.SE_MERCURY, jd)
print(f"Mercury retrograde: {retro}")
# Find Mercury's next station
jd_station, type = ephem.swe_find_station_ut(ephem.SE_MERCURY, jd)
year, month, day, hours = ephem.revjul(jd_station)
# type = "SR" (retrograde station) or "SD" (direct station)
print(f"Next station: {day}/{month}/{year} ({type})")
Mercury retrograde: True
Next station: 25/4/2024 (SD)
🌍 Real Life #
“Mercury retrograde” is one of the most popular astrological concepts. It happens about 3 times a year, for roughly 3 weeks each time. In reality, all planets (except the Sun and Moon) have retrograde periods — outer planets are retrograde for months.
5.4 Planetary Phenomena: Magnitude, Phase, Elongation #
The pheno_ut function returns information on the physical appearance of a planet:
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 21.0)
attr, _ = ephem.pheno_ut(jd, ephem.SE_JUPITER, 0)
phase_angle = attr[0] # Sun-Planet-Earth angle (degrees)
phase = attr[1] # illuminated fraction of the disk (0.0–1.0)
elongation = attr[2] # angular distance from the Sun (degrees)
diameter = attr[3] # apparent diameter (arcseconds)
magnitude = attr[4] # apparent visual magnitude
print(f"Jupiter:")
print(f" Elongation: {elongation:.1f}°")
print(f" Phase: {phase:.2f} ({phase*100:.0f}% illuminated)")
print(f" Magnitude: {magnitude:.1f}")
print(f" Apparent diameter: {diameter:.1f}\"")
Jupiter:
Elongation: 29.6°
Phase: 1.00 (100% illuminated)
Magnitude: -2.0
Apparent diameter: 0.0"
Elongation is the angular distance from the Sun — if it is small (< 10°), the planet is lost in the solar glare and not visible. Magnitude indicates brightness: lower numbers = brighter (Venus reaches -4.6, Jupiter -2.9).
5.5 Orbital Elements #
The get_orbital_elements function returns the orbital parameters of a celestial body — the 6 numbers that describe an ellipse in space:
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 12.0)
jd_tt = jd + ephem.deltat(jd) # JD in TT is required
elem = ephem.get_orbital_elements(jd_tt, ephem.SE_MARS, 0)
print(f"Mars - orbital elements:")
print(f" Semi-major axis: {elem[0]:.6f} AU")
print(f" Eccentricity: {elem[1]:.6f}")
print(f" Inclination: {elem[2]:.4f}°")
print(f" Sidereal period: {elem[10]:.2f} years")
print(f" Synodic period: {elem[13]:.1f} days")
print(f" Perihelion dist.: {elem[15]:.4f} AU")
print(f" Aphelion dist.: {elem[16]:.4f} AU")
Mars - orbital elements:
Semi-major axis: 1.523627 AU
Eccentricity: 0.093278
Inclination: 1.8479°
Sidereal period: 1.88 years
Synodic period: 779.9 days
Perihelion dist.: 1.3815 AU
Aphelion dist.: 1.6657 AU
To get the maximum and minimum distances with a single call:
d_max, d_min, d_now = ephem.orbit_max_min_true_distance(
jd, ephem.SE_MARS, 0
)
print(f"Mars: min {d_min:.4f} AU, max {d_max:.4f} AU, now {d_now:.4f} AU")
Mars: min 0.3648 AU, max 2.6825 AU, now 2.0618 AU
5.6 Nodes and Apsides #
The nodes are the points where a planet’s orbit intersects the ecliptic. The apsides are the points of maximum and minimum distance from the Sun (aphelion and perihelion).
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 12.0)
# nod_aps_ut returns 4 tuples of 6 values each:
# ascending node, descending node, perihelion, aphelion
nasc, ndsc, peri, aphe = ephem.nod_aps_ut(
jd, ephem.SE_MARS, 0, 0 # method=0: mean
)
print(f"Mars - ascending node: {nasc[0]:.4f}°")
print(f"Mars - descending node: {ndsc[0]:.4f}°")
print(f"Mars - perihelion: {peri[0]:.4f}° (dist {peri[2]:.4f} AU)")
print(f"Mars - aphelion: {aphe[0]:.4f}° (dist {aphe[2]:.4f} AU)")
Mars - ascending node: 37.4197°
Mars - descending node: 266.1986°
Mars - perihelion: 354.2716° (dist 2.2240 AU)
Mars - aphelion: 120.3522° (dist 1.1508 AU)
Summary #
calc_ut(jd, body, flag)is the core function of the library. Given an instant and a celestial body, it returns 6 numbers: the first three tell where it is (longitude, latitude, distance), the last three how it is moving.- The flags control the type of coordinates, the observation center, the reference system, and physical corrections. They are combined with
|. Passing0yields geocentric ecliptic coordinates — the default for astrology. - The speed in longitude (
pos[3]) is positive when the planet advances along the zodiac (direct motion) and negative when it seems to go backwards (retrograde motion). pheno_utprovides information on the physical appearance of the planet: magnitude (brightness), phase (illuminated percentage), elongation (angular distance from the Sun), and apparent diameter.get_orbital_elementsreturns the parameters of the Keplerian orbit: semi-major axis, eccentricity, inclination, periods, and distances.nod_aps_utreturns the nodes (where the orbit crosses the ecliptic) and apsides (points of minimum and maximum distance from the Sun).
Introduced Functions #
pheno_ut(jd, body, flag)— phenomena: phase, elongation, magnitudeis_retrograde(body, jd)— is the planet currently retrograde?swe_find_station_ut(body, jd)— finds the next station (retrograde or direct)get_orbital_elements(jd_tt, body, flag)— Keplerian orbital elementsorbit_max_min_true_distance(jd, body, flag)— min, max, and current distances from Earthnod_aps_ut(jd, body, flag, method)— orbit nodes and apsides