codex/horizontal-sky-coordinates-local-frame.py

horizontal-sky-coordinates-local-frame.py
"""Original analytical diagram of a synthetic local sky direction.

Run from _media; tested with Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
The plan view shows the horizontal projection, not a second sky position.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


BLUE = "#186caa"
ORANGE = "#b85817"
GRAY = "#65717a"
eta, gamma = np.deg2rad([60.0, 30.0])
east = np.cos(gamma) * np.sin(eta)
north = np.cos(gamma) * np.cos(eta)
up = np.sin(gamma)
horizontal = np.hypot(east, north)
assert np.isclose(east**2 + north**2 + up**2, 1)

plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
fig, (plan, side) = plt.subplots(1, 2, figsize=(10.4, 5.2), facecolor="white")
fig.subplots_adjust(left=0.04, right=0.97, bottom=0.17, top=0.81, wspace=0.22)
fig.suptitle("One direction, two views of the local frame", fontsize=17, y=0.97)
fig.text(0.5, 0.89, r"Analytical illustration: $\eta=60^\circ$, $\gamma=30^\circ$; unit sky sphere", ha="center")

for ax in (plan, side):
    ax.set_aspect("equal")
    ax.axis("off")

az = np.linspace(0, 2 * np.pi, 361)
plan.plot(np.sin(az), np.cos(az), color=GRAY, lw=1.4)
for end in [(1.08, 0), (-1.08, 0), (0, 1.08), (0, -1.08)]:
    plan.annotate("", xy=end, xytext=(0, 0), arrowprops={"arrowstyle": "->", "color": GRAY})
for x, y, text in [(0, 1.18, "N · 0°"), (1.26, 0, "E · 90°"),
                   (0, -1.18, "S · 180°"), (-1.28, 0, "W · 270°")]:
    plan.text(x, y, text, ha="center", va="center")
plan.annotate("", xy=(east, north), xytext=(0, 0),
              arrowprops={"arrowstyle": "->", "lw": 2.7, "color": BLUE})
plan.plot([east, east, 0], [0, north, north], "--", color=GRAY, lw=1)
plan.scatter([east], [north], color=BLUE, s=35, zorder=4)
plan.text(east + 0.09, north + 0.12, "Horizontal\nprojection", color=BLUE, ha="left", fontsize=10)
arc = np.linspace(0, eta, 80)
plan.plot(0.38 * np.sin(arc), 0.38 * np.cos(arc), color=ORANGE, lw=2)
plan.annotate("", xy=(0.38 * np.sin(eta), 0.38 * np.cos(eta)),
              xytext=(0.38 * np.sin(eta - 0.15), 0.38 * np.cos(eta - 0.15)),
              arrowprops={"arrowstyle": "->", "color": ORANGE, "lw": 1.7})
plan.text(0.18, 0.52, r"$\eta=60^\circ$", color=ORANGE)
plan.text(0.40, -0.10, r"$E=3/4$", ha="center", color=BLUE)
plan.text(-0.12, 0.22, r"$N=\sqrt{3}/4$", ha="right", color=BLUE, fontsize=10)
plan.scatter([0], [0], color="black", s=18, zorder=5)
plan.set_xlim(-1.6, 1.65)
plan.set_ylim(-1.35, 1.45)
plan.set_title("Plan view · looking downward", fontsize=12, pad=12)

elev = np.linspace(0, np.pi, 181)
side.plot(np.cos(elev), np.sin(elev), color=GRAY, lw=1.4)
side.axhline(0, color=GRAY, lw=1)
side.annotate("", xy=(0, 1.16), xytext=(0, 0),
              arrowprops={"arrowstyle": "->", "color": GRAY})
side.annotate("", xy=(horizontal, up), xytext=(0, 0),
              arrowprops={"arrowstyle": "->", "lw": 2.7, "color": BLUE})
side.plot([horizontal, horizontal], [0, up], "--", color=GRAY, lw=1.2)
side.plot([0, horizontal], [0, 0], color=BLUE, lw=2.5)
side.scatter([horizontal], [up], color=BLUE, s=40, zorder=4)
side.text(horizontal + 0.06, up + 0.07, "Sky direction", color=BLUE, fontsize=10)
side.text(horizontal + 0.05, up / 2, r"$U=1/2$", color=BLUE, fontsize=10)
alt_arc = np.linspace(0, gamma, 80)
side.plot(0.40 * np.cos(alt_arc), 0.40 * np.sin(alt_arc), color=ORANGE, lw=2)
side.text(0.47, 0.10, r"$\gamma=30^\circ$", color=ORANGE)
zen_arc = np.linspace(gamma, np.pi / 2, 80)
side.plot(0.65 * np.cos(zen_arc), 0.65 * np.sin(zen_arc), color="#36815a", lw=2)
side.text(0.17, 0.74, r"$z=60^\circ$", color="#36815a")
side.text(-0.09, 1.20, "Zenith · up", ha="center")
side.text(horizontal / 2, -0.16, r"$\sqrt{E^2+N^2}=\sqrt{3}/2$", ha="center", color=BLUE, fontsize=10)
side.text(0, -0.36, "Horizontal plane in this vertical section", ha="center", color=GRAY, fontsize=10)
side.scatter([0], [0], color="black", s=18, zorder=5)
side.text(-0.10, -0.13, "O", ha="right")
side.set_xlim(-1.22, 1.49)
side.set_ylim(-0.42, 1.35)
side.set_title("Vertical section through the direction", fontsize=12, pad=12)

fig.text(0.5, 0.06, "O is the observer. Azimuth turns from north through east; altitude rises from the horizontal plane.", ha="center", fontsize=10)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=140, facecolor="white", transparent=False)
plt.close(fig)