pray-calc/src/getSolarEphemeris.ts
Aric Camarata 6f165b79ad ci: fix eslint parser devDeps, typed linting config, prettier formatting
- Add @typescript-eslint/parser and @typescript-eslint/eslint-plugin as
  direct devDependencies (were only transitive, not linked in node_modules)
- Add files pattern and parserOptions.project to eslint.config.mjs so
  ESLint finds and type-checks src/**/*.ts files correctly
- Run prettier --write to fix formatting across all src files
2026-05-31 08:47:17 -04:00

130 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* High-accuracy solar ephemeris features without a full SPA call.
*
* Uses Jean Meeus "Astronomical Algorithms" (2nd ed., Ch. 25) low-precision
* formulas, accurate to approximately ±0.01° for solar declination and
* ±0.0001 AU for Earth-Sun distance over the years 1950-2050. This is
* sufficient for computing twilight angles; exact Sun positioning for
* prayer time solving still uses the full SPA via nrel-spa.
*/
import { DEG } from "./constants.js";
/**
* Convert a JavaScript Date to a Julian Date number.
*
* @param date - Any JavaScript Date object (uses UTC internally)
* @returns Julian Date: days since noon January 1, 4713 BC UTC
* @example
* const jd = toJulianDate(new Date('2024-06-21'));
* // jd ≈ 2460482.5
*/
export function toJulianDate(date: Date): number {
return date.getTime() / 86400000 + 2440587.5;
}
export interface SolarEphemeris {
/** Solar declination in degrees. */
decl: number;
/** Earth-Sun distance in AU. */
r: number;
/** Apparent solar ecliptic longitude in radians (season phase θ, 02π). */
eclLon: number;
}
/**
* Compute solar declination, Earth-Sun distance, and ecliptic longitude
* from a Julian Date. Accuracy: ~0.01° for declination, ~0.0001 AU for r.
*
* @param jd - Julian Date (use toJulianDate to convert a JS Date)
* @returns Solar ephemeris data: declination (degrees), Earth-Sun distance (AU),
* and ecliptic longitude (radians, 0-2π season phase)
* @example
* const jd = toJulianDate(new Date('2024-06-21'));
* const { decl, r, eclLon } = solarEphemeris(jd);
*/
export function solarEphemeris(jd: number): SolarEphemeris {
const T = (jd - 2451545.0) / 36525.0;
// Geometric mean longitude L0 (degrees)
const L0 = (((280.46646 + 36000.76983 * T + 0.0003032 * T * T) % 360) + 360) % 360;
// Mean anomaly M (degrees)
const M = (((357.52911 + 35999.05029 * T - 0.0001537 * T * T) % 360) + 360) % 360;
const Mrad = M * DEG;
// Orbital eccentricity
const e = 0.016708634 - 0.000042037 * T - 0.0000001267 * T * T;
// Equation of center C (degrees)
const C =
(1.914602 - 0.004817 * T - 0.000014 * T * T) * Math.sin(Mrad) +
(0.019993 - 0.000101 * T) * Math.sin(2 * Mrad) +
0.000289 * Math.sin(3 * Mrad);
// Sun's true longitude (degrees)
const sunLon = L0 + C;
// Sun's true anomaly (degrees)
const nu = M + C;
const nuRad = nu * DEG;
// Earth-Sun distance in AU
const r = (1.000001018 * (1 - e * e)) / (1 + e * Math.cos(nuRad));
// Longitude of ascending node of Moon's orbit (for nutation)
const Omega = (((125.04 - 1934.136 * T) % 360) + 360) % 360;
const OmegaRad = Omega * DEG;
// Apparent solar longitude corrected for nutation and aberration
const lambda = sunLon - 0.00569 - 0.00478 * Math.sin(OmegaRad);
const lambdaRad = lambda * DEG;
// Mean obliquity of the ecliptic (degrees)
const epsilon0 = 23.439291 - 0.013004 * T - 1.638e-7 * T * T + 5.036e-7 * T * T * T;
// True obliquity with nutation correction
const epsilon = (epsilon0 + 0.00256 * Math.cos(OmegaRad)) * DEG;
// Solar declination
const sinDecl = Math.sin(epsilon) * Math.sin(lambdaRad);
const decl = Math.asin(Math.max(-1, Math.min(1, sinDecl))) / DEG;
// Ecliptic longitude as season phase θ ∈ [0, 2π)
const eclLon = ((lambdaRad % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);
return { decl, r, eclLon };
}
/**
* Solar vertical angular speed near a given hour angle H (radians),
* in degrees per hour. Useful as a confidence weight for angle predictions:
* higher speed means each degree of angle error is fewer minutes of time error.
*
* Formula: dh/dt ≈ 15 × cos(φ) × cos(δ) × sin(H) [°/hr]
*/
export function solarVerticalSpeed(latRad: number, declRad: number, hAngleRad: number): number {
return 15 * Math.abs(Math.cos(latRad) * Math.cos(declRad) * Math.sin(hAngleRad));
}
/**
* Compute the atmospheric refraction correction (degrees) for a given
* apparent solar altitude using the Bennett/Saemundsson formula.
* Scaled for pressure and temperature.
*
* Returns a positive correction (the Sun appears higher than its geometric
* position). For altitudes below -1°, returns 0 (not meaningful for Fajr/Isha
* at depression angles like 1220°, but included for completeness).
*/
export function atmosphericRefraction(
altitudeDeg: number,
pressureMbar = 1013.25,
temperatureC = 15,
): number {
if (altitudeDeg < -1) return 0;
// Bennett's formula in arcminutes
const R0 = 1.02 / Math.tan((altitudeDeg + 10.3 / (altitudeDeg + 5.11)) * DEG);
// Scale for pressure and temperature
const R = R0 * (pressureMbar / 1010) * (283 / (273 + temperatureC));
return Math.max(0, R) / 60; // convert arcminutes to degrees
}