At one point I was considering writing a review paper on the absolute directions in space. Since the Michelson-Morley experiment, a lot of absolute directions have been discovered and refined. However, I didn’t get much further than creating the map you see below. While each of the measurements an their own deserves a review, there isn’t a lot tying them all together in the end, and so here we are — a blog post.
The map is galactic coordinates projected using the Mollweide projection. Most of us are sort of used to projections of spherical geometries onto flat surfaces, because most of us are living on an oblate spheroid and use flat maps, but I digress.
One thing that might stand out if you haven’t seen these things plotted together at first is that the ecliptic (the path of the sun through the heavens) seems to sort of match up with the CMB dipole, with maximum excursions about 5 degrees or so different. This coincidence is generally regarded as not indicative of much, kind of like the interesting fact that the angular size of the sun is roughly equal to the angular size of the moon (when viewed from Earth).
One might also be tempted to point out that the motion of the local interstellar medium through the solar system is “roughly” as if it were kind of coming from the galactic center, but that angular difference is actually not all that small.

Anyway, here’s some python code to generate the figure, with the coordinates explicitly marked, for those who want to see the numbers. Happy stargazing!
import numpy as np
import matplotlib.pyplot as plt
import astropy.units as u
from astropy.coordinates import SkyCoord, GeocentricTrueEcliptic
# =============================================================================
# 1. GALACTIC-NATIVE DIRECTIONS
# =============================================================================
directions_gal = {
# --- CMBR Dipole (Hot & Cold poles) ---
"CMB Dipole (Hot)": (264.0, 48.0),
"CMB Dipole (Cold)": ( 84.0, -48.0),
# --- Galactic Center ---
"Galactic Center": (0.0, 0.0),
# --- Solar Apex ---
"Solar Apex": (56.0, 23.0),
# --- North Galactic Pole ---
"North Galactic Pole": (0.0, 90.0),
# --- Additional points ---
"Sirius": (227.2, -8.9),
"Alpha Centauri": (315.7, -0.7),
"LMC": (280.5, -32.9),
"SMC": (302.8, -44.3),
"Andromeda (M31)": (121.2, -21.6),
"Spiral Arm Tangent": (305.0, 0.0),
# --- Earth's Celestial Poles ---
"North Celestial Pole": (122.93, 27.13),
"South Celestial Pole": (302.93, -27.13),
# --- Uranus's Poles ---
"Uranus North Pole": (179.3, 31.4),
"Uranus South Pole": (359.3, -31.4),
}
# =============================================================================
# 2. ECLIPTIC-NATIVE DIRECTIONS -> CONVERT TO GALACTIC
# =============================================================================
ecliptic_directions = {
"IBEX Ribbon Center": (218.3, 40.4),
"Interstellar Wind (Upwind)": (255.7, 5.1),
}
for name, (lon_deg, lat_deg) in ecliptic_directions.items():
ecl = SkyCoord(
lon=lon_deg * u.deg,
lat=lat_deg * u.deg,
frame=GeocentricTrueEcliptic(equinox='J2000')
)
gal = ecl.galactic
directions_gal[name] = (
gal.l.wrap_at(180 * u.deg).deg,
gal.b.deg
)
# =============================================================================
# 3. COMPUTE TAIL AS TRUE ANTIPODE (Cartesian negation)
# =============================================================================
from astropy.coordinates import spherical_to_cartesian, cartesian_to_spherical
up_l, up_b = directions_gal["Interstellar Wind (Upwind)"]
up_l_rad = np.deg2rad(float(up_l))
up_b_rad = np.deg2rad(float(up_b))
x, y, z = spherical_to_cartesian(1.0, up_b_rad, up_l_rad)
x, y, z = -x, -y, -z
r, tail_b_rad, tail_l_rad = cartesian_to_spherical(x, y, z)
# Convert Angle objects to plain floats in degrees
tail_l_deg = float(tail_l_rad.to_value(u.rad)) * 180.0 / np.pi
tail_b_deg = float(tail_b_rad.to_value(u.rad)) * 180.0 / np.pi
directions_gal["Interstellar Wind (Tail)"] = (tail_l_deg, tail_b_deg)
# --- Alias for the rest of the script ---
directions = directions_gal
print("Upwind:", directions_gal["Interstellar Wind (Upwind)"])
print("Tail: ", directions_gal["Interstellar Wind (Tail)"])
# =============================================================================
# 4. COMPUTE KREUTZ APHELION
# =============================================================================
kreutz_aphelion_ecl = SkyCoord(
lon=(282.0 + 180.0) * u.deg,
lat=-35.0 * u.deg,
frame=GeocentricTrueEcliptic(equinox='J2000')
)
kreutz_aphelion_gal = kreutz_aphelion_ecl.galactic
kreutz_ap_l = kreutz_aphelion_gal.l.wrap_at(180 * u.deg).deg
kreutz_ap_b = kreutz_aphelion_gal.b.deg
# =============================================================================
# 5. SETUP THE MOLLWEIDE PROJECTION FIGURE
# =============================================================================
fig = plt.figure(figsize=(14, 8))
ax = fig.add_subplot(111, projection='mollweide')
ax.grid(True, linestyle='--', alpha=0.4)
# --- Custom longitude tick labels ---
lon_ticks_deg = np.arange(-180, 181, 30)
ax.set_xticks(np.deg2rad(lon_ticks_deg))
ax.set_xticklabels([f"{int(l)}°" for l in lon_ticks_deg])
# --- Custom latitude tick labels ---
lat_ticks_deg = np.arange(-90, 91, 30)
ax.set_yticks(np.deg2rad(lat_ticks_deg))
ax.set_yticklabels([f"{int(b)}°" for b in lat_ticks_deg])
ax.set_xlabel('Galactic Longitude (l)', labelpad=10)
ax.set_ylabel('Galactic Latitude (b)', labelpad=10)
ax.set_title('Key Directions in Galactic Coordinates', pad=20, fontsize=14)
# =============================================================================
# 6. ECLIPTIC DASHED LINE
# =============================================================================
ecl_lon = np.linspace(0, 360, 2000) * u.deg
ecl_lat = np.zeros(2000) * u.deg
ecliptic = SkyCoord(
lon=ecl_lon, lat=ecl_lat,
frame=GeocentricTrueEcliptic(equinox='J2000')
)
ecl_gal = ecliptic.galactic
ax.plot(ecl_gal.l.wrap_at(180 * u.deg).radian, ecl_gal.b.radian,
'k--', linewidth=1.2, alpha=0.7, label='Ecliptic', zorder=2)
# =============================================================================
# 7. PLOT DIRECTIONS
# =============================================================================
for name, (lon_deg, lat_deg) in directions.items():
lon_wrapped = ((lon_deg + 180) % 360) - 180
lon_rad = np.deg2rad(lon_wrapped)
lat_rad = np.deg2rad(lat_deg)
if "Uranus" in name: marker = '^'
elif "Pole" in name: marker = 's'
elif "CMB" in name: marker = 'D'
else: marker = 'o'
ax.scatter(lon_rad, lat_rad, s=90, marker=marker, zorder=5,
label=name, edgecolors='black', linewidth=0.5)
ax.annotate(name, xy=(lon_rad, lat_rad),
xytext=(5, 5), textcoords='offset points',
fontsize=7.5, fontweight='bold', zorder=6,
bbox=dict(boxstyle="round,pad=0.15", fc="white",
ec="gray", alpha=0.75))
# --- Kreutz Aphelion ---
ax.scatter(np.deg2rad(kreutz_ap_l), np.deg2rad(kreutz_ap_b),
s=110, marker='*', color='red', zorder=7,
edgecolors='black', linewidth=0.5, label='Kreutz Aphelion')
ax.annotate('Kreutz Aphelion',
xy=(np.deg2rad(kreutz_ap_l), np.deg2rad(kreutz_ap_b)),
xytext=(5, 5), textcoords='offset points',
fontsize=8, fontweight='bold', zorder=8, color='darkred',
bbox=dict(boxstyle="round,pad=0.15", fc="mistyrose",
ec="red", alpha=0.85))
# =============================================================================
# 8. FINALIZE
# =============================================================================
ax.legend(loc='lower center', bbox_to_anchor=(0.5, -0.32),
ncol=3, fontsize=8, frameon=False)
plt.tight_layout()
plt.show()
print(f"Kreutz Aphelion (Galactic): l = {kreutz_ap_l:.1f}°, b = {kreutz_ap_b:.1f}°")
