/** * ephemeris.ts — EphemerisEngine backed by JPL DE440s (public domain) * * Replaces swisseph-wasm (AGPL) with a pure-TypeScript SPK reader against * NASA's freely-distributed de440s.bsp file. The public API is identical to * the previous implementation so every caller (analytics, prediction, tools) * continues to work without modification. * * ── Why DE440 instead of Swiss Ephemeris ───────────────────────────────── * Swiss Ephemeris is AGPL-3.0. Any network-facing service (e.g. Telegram * bot) must release its entire server-side source under AGPL. JPL DE440 * data is U.S. Government public domain — no restrictions at all. * * ── Measured accuracy ──────────────────────────────────────────────────── * Tropical apparent longitude vs JPL Horizons, all bodies: * 1900 · 1970 · 1998 · 2024 ≤ 0.28″ (typically 0.04″) * 2100 ≤ 46″ — entirely the ΔT forecast gap; * Horizons freezes ΔT, this engine * extrapolates it * Sidereal zero point vs JHora, 1998 reference chart: * True Pushya 0.03″ derived from δ Cancri, no fitted constant * Moon 1.0″ * Ascendant 24″ within the ~13″ that UT1−UTC alone can shift it * * See tests/golden/horizons.golden.test.ts and jhora-bridge.golden.test.ts. * * ── Coordinate chain ───────────────────────────────────────────────────── * 1. jdUTtoET(jd) UT → TT → ephemeris seconds (applies ΔT) * 2. observerState() Earth barycentric state, plus the observer's * geocentric vector when topocentric * 3. apparentPlace() light-time (iterated), solar deflection, * relativistic aberration → ICRF direction * 4. icrfToMeanEclipticOfDate() IAU 2006 precession rotation + mean obliquity * 5. toSidereal(lon, ayanamsa) sidereal longitude * * Nutation is applied to the *angles* (via apparent sidereal time and true * obliquity) but not to planetary longitudes: ayanamsa is referred to the mean * equinox of date, so adding nutation there would double-count it. * * ── Data source ────────────────────────────────────────────────────────── * de440s.bsp Coverage: 1849-12-26 to 2150-01-22 (~32 MB) * Downloaded from JPL by @node-jhora/ephe postinstall. * https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/de440s.bsp */ import { DateTime } from 'luxon'; export declare const AYANAMSA: { readonly FAGAN_BRADLEY: 0; /** Lahiri as JHora reports it (see AYANAMSA_MODELS note). */ readonly LAHIRI: 1; /** Lahiri per the Indian Calendar Reform Committee definition. */ readonly LAHIRI_ICRC: 2; readonly RAMAN: 3; readonly KRISHNAMURTI: 5; readonly YUKTESHWAR: 7; readonly JN_BHASIN: 8; readonly J2000: 18; /** True Chitrapaksha — Spica pinned to 180°. The project's reference model. */ readonly TRUE_CITRA: 27; readonly TRUE_PUSHYA: 29; readonly TRUE_REVATI: 30; readonly TRUE_MULA: 35; }; /** * Default sidereal zero point: True Chitrapaksha with Drik Siddhanta. * Unlike the epoch-anchored models it needs no fitted constant — it is derived * from Spica's computed position, so it is exact at every date. */ export declare const DEFAULT_AYANAMSA: number; export type AyanamsaMode = typeof AYANAMSA[keyof typeof AYANAMSA]; export interface GeoLocation { latitude: number; longitude: number; altitude?: number; } export interface PlanetPosition { id: number; name: string; longitude: number; latitude: number; distance: number; speed: number; declination: number; } export interface HouseData { cusps: number[]; ascendant: number; mc: number; armc: number; vertex: number; } /** * Whether to report where a body *is* or where it is *seen*. * * 'geometric' — JHora's convention, and this engine's default. No light-time, * aberration or deflection. * 'apparent' — the astronomical standard, matching JPL Horizons and what a * telescope would point at. * * The two differ by up to ~44″ (Venus near conjunction), so the choice is not * cosmetic. Jyotish practice follows JHora. */ export type PositionMode = 'geometric' | 'apparent'; /** JHora parity: positions are geometric. */ export declare const DEFAULT_POSITION_MODE: PositionMode; /** * JHora reports the **true** (osculating) lunar node, not the mean one — its * Rahu for the 1998 reference chart matches the osculating node to 0.12″ and * misses the mean node by 0.97°. Pass `nodeType: 'mean'` for the smoothed node. */ export declare const DEFAULT_NODE_TYPE: 'mean' | 'true'; export declare class EphemerisEngine { private static instance; private initialized; private spk; private ayanamsaMode; constructor(); /** Singleton accessor — mirrors the previous API. */ static getInstance(): EphemerisEngine; /** * Load de440s.bsp from @node-jhora/ephe (or NODE_JHORA_EPHE_PATH env var). * Idempotent — subsequent calls are instant no-ops. */ initialize(): Promise; private resolveEphePath; private resolveBspPath; /** Set ayanamsa mode. Accepts AYANAMSA.* constants. */ setAyanamsa(mode: number): void; /** Compute Julian Day (UT) for any Luxon DateTime. */ julday(date: DateTime): number; /** * Ayanamsa in degrees at the given Julian Day. * * @param jd Julian Day (UT) * @param mode SE_SIDM_* code; defaults to the instance setting. Pass it * explicitly when a caller has already used a per-call * `ayanamsaOrder` for planets, so the reported ayanamsa cannot * drift out of step with the positions it belongs to. */ getAyanamsa(jd: number, mode?: number, offsetDeg?: number): number; /** * Compute sidereal planet positions from DE440. * * @param date UTC Luxon DateTime * @param location Geographic location (used only for topocentric Moon) * @param options ayanamsaOrder overrides the instance setting for this call */ getPlanets(date: DateTime, location?: GeoLocation, options?: { ayanamsaOrder?: number; topocentric?: boolean; nodeType?: 'mean' | 'true'; positionMode?: PositionMode; /** Constant added to the ayanamsa, degrees. */ ayanamsaOffset?: number; }): PlanetPosition[]; /** * Apparent position of an SPK body in the **mean** ecliptic and equinox of * date — the frame ayanamsa is defined against. * * Nutation is deliberately excluded: adding it would double-count, since the * sidereal zero point is itself referred to the mean equinox. */ private meanEclipticOfDate; /** * Mean or true (osculating) longitude of the Moon's ascending node, * tropical degrees in the mean ecliptic of date. * * The `nodeType` option was previously accepted and silently ignored, so * requesting true nodes returned mean ones. */ private lunarNode; /** * True (osculating) lunar node: the ascending intersection of the Moon's * instantaneous orbital plane with the ecliptic. * * Derived from the geocentric position and velocity of the Moon — the * orbital angular momentum vector h = r × v is normal to the orbit plane, so * the ascending node lies along ẑ × h. */ private trueNodeLongitude; /** Equatorial declination from mean-of-date ecliptic longitude and latitude. */ private declinationOf; /** * Compute house cusps and angles. * Supports Whole Sign (recommended for Jyotish). * * @param jd Julian Day (UT) * @param lat Geographic latitude * @param lon Geographic longitude * @param method 'W' = Whole Sign (default); others treated as Whole Sign * @param sidereal When true (default), all angles have ayanamsa subtracted */ getHouses(jd: number, lat: number, lon: number, method?: string, sidereal?: boolean, ayanamsaMode?: number, ayanamsaOffset?: number): HouseData; /** Greenwich Apparent Sidereal Time in hours. */ getSiderealTime(jd: number): number; /** * Mean obliquity and the IAU 1980 nutation components at `jd`. * Previously a two-term nutation approximation good to only ~1″. */ getEclipticObliquity(jd: number): { eps: number; dpsi: number; deps: number; }; /** ΔT = TT − UT in seconds at `jd`, exposed for diagnostics. */ getDeltaT(jd: number): number; /** Human-readable label for the active (or given) ayanamsa model. */ getAyanamsaName(mode?: number): string; private checkInit; }