codex/equatorial-coordinates-synthetic-transit.py

equatorial-coordinates-synthetic-transit.py
"""Synthetic meridian observation and clock calibration, not measured data.

Run from _media. Tested with Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
Altitude comes from the spherical-Earth, unrefracted fixed-direction model.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
    fig = plt.figure(figsize=(10, 6))
    fig.patch.set_facecolor("white")
    grid = fig.add_gridspec(2, 2, height_ratios=[1, .75], width_ratios=[1.2, 1])
    ax, bx, cx = fig.add_subplot(grid[0, 0]), fig.add_subplot(grid[1, 0]), fig.add_subplot(grid[:, 1])
    fig.suptitle("Synthetic meridian observation: angle plus a clock readout", fontsize=15)
    blue, orange, green = "#1966a3", "#be510b", "#22805b"
    theta = np.linspace(4, 8, 401)
    alpha = 6.0
    phi, delta = np.deg2rad([52, 20])
    hour_angle = np.deg2rad(15 * (theta - alpha))
    altitude = np.rad2deg(np.arcsin(np.sin(phi) * np.sin(delta)
                                   + np.cos(phi) * np.cos(delta) * np.cos(hour_angle)))
    ax.plot(theta, altitude, color=blue, lw=2.5)
    ax.axvline(alpha, color="#777777", ls="--", lw=1)
    ax.scatter([alpha], [58], color=blue, zorder=3)
    ax.annotate("Upper crossing\na = 58°, H = 0", (6, 58), (6.3, 55.0),
                arrowprops={"arrowstyle": "->", "color": blue}, fontsize=10)
    ax.set(xlim=(4, 8), ylim=(50, 59.5), ylabel="Geometric altitude (degrees)")
    ax.set_title("Fixed direction: α = 6 h, δ = 20°; site: φ = 52°", fontsize=11)
    ax.tick_params(labelbottom=False)
    ax.grid(alpha=.2)
    bx.plot(theta, theta, color=orange, lw=2, label="local sidereal time Θ")
    bx.axhline(alpha, color=green, lw=2, label="target right ascension α")
    bx.axvline(alpha, color="#777777", ls="--", lw=1)
    bx.set(xlim=(4, 8), ylim=(3.8, 8.3), xlabel="Local sidereal time (hours)", ylabel="Hours")
    bx.set_xticks([4, 5, 6, 7, 8])
    bx.legend(loc="upper left", fontsize=9, frameon=True, facecolor="white")
    bx.grid(alpha=.2)
    bx.text(4.04, 4.08, "H < 0: before", fontsize=9)
    bx.text(7.92, 4.08, "H > 0: after", ha="right", fontsize=9)

    cx.axis("off")
    cx.set(xlim=(0, 1), ylim=(0, 1))
    cx.text(.5, .99, "Two independent readouts", ha="center", va="top", fontsize=12, weight="bold")
    # A schematic focal-plane view; left/right are deliberately not sky cardinal directions.
    cx.plot([.14, .86, .86, .14, .14], [.70, .70, .87, .87, .70], color="#777777")
    cx.plot([.50, .50], [.70, .87], color=blue, lw=2)
    cx.scatter([.22, .50, .78], [.785] * 3, color=green, s=[25, 55, 25])
    cx.annotate("", (.83, .81), (.18, .81), arrowprops={"arrowstyle": "->", "color": green})
    cx.text(.5, .665, "Image crosses the central sighting line\nRecord crossing time and angular circle", ha="center", va="top", fontsize=10)
    cx.text(.04, .53, "Angular example (south of zenith)", fontsize=11, color=blue, weight="bold")
    cx.text(.04, .475, "φ = 52°; signed zenith distance = 32°\nδ = 52° − 32° = 20°", va="top", fontsize=11)
    cx.text(.04, .355, "Clock example (separate synthetic target)", fontsize=10.5, color=orange, weight="bold")
    cx.text(.04, .30, "Reference RA:                 06:00:00\nReference clock readout:  06:00:20\nClock correction:                  −20 s\nTarget clock readout:       06:30:20\nCorrected target RA:        06:30:00", va="top", linespacing=1.6, fontsize=10.5,
            bbox={"boxstyle": "round,pad=.6", "fc": "#f3f5f7", "ec": "none"})
    cx.text(.5, .01, "06:30:00 = 6.5 h = 97.5°", ha="center", fontsize=12, color=orange)
    fig.text(.5, .025, "Analytical illustration, not an observing record. Spherical Earth; no refraction or source motion.\nClock example assumes correct sidereal rate and an independently known reference direction.",
             ha="center", fontsize=9, color="#555555")
    fig.subplots_adjust(left=.085, right=.98, bottom=.17, top=.86, hspace=.15, wspace=.28)
    fig.savefig(Path(__file__).with_suffix(".png").name, dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()