UNPKG

@tubular/astronomy

Version:

Astronomical calculations for planetary positions, moon phases, eclipses, rise, transit, and set times, and more.

300 lines 11.9 kB
/* This is an implementation of the method of computing nutation presented by Jean Meeus in _Astronomical Algorithms, 2nd Ed._ */ import { abs, Angle, asin, atan2, cos, cos_deg, HALF_PI, limitNeg1to1, PI, sin, sin_deg, SphericalPosition, SphericalPosition3D, sqrt, Unit } from '@tubular/math'; import { isNumber } from '@tubular/util'; import { JD_J2000, OBLIQUITY_J2000 } from './astro-constants'; export var NMode; (function (NMode) { NMode[NMode["NUTATED"] = 0] = "NUTATED"; NMode[NMode["MEAN_OBLIQUITY"] = 1] = "MEAN_OBLIQUITY"; NMode[NMode["J2000"] = 2] = "J2000"; NMode[NMode["ANTI_NUTATED"] = 3] = "ANTI_NUTATED"; })(NMode || (NMode = {})); // Remove nutation from an already-nutated set of coordinates. // From _Astronomical Algorithms, 2nd Ed._ by Jean Meeus // p. 147. const coeffs = [-4680.93, -1.55, 1999.25, -51.38, -249.67, -39.05, 7.12, 27.87, 5.79, 2.45]; // From _Astronomical Algorithms, 2nd Ed._ by Jean Meeus // pp. 145-146, Table 22.A const table = [ '0 0 0 0 1 -171996 -174.2T 92025 8.9T', '-2 0 0 2 2 -13187 -1.6T 5736 -3.1T', '0 0 0 2 2 -2274 -0.2T 977 -0.5T', '0 0 0 0 2 2062 0.2T -895 0.5T', '0 1 0 0 0 1426 -3.4T 54 -0.1T', '0 0 1 0 0 712 0.1T -7', '-2 1 0 2 2 -517 1.2T 224 -0.6T', '0 0 0 2 1 -386 -0.4T 200', '0 0 1 2 2 -301 129 -0.1T', '-2 -1 0 2 2 217 -0.5T -95 0.3T', '-2 0 1 0 0 -158', '-2 0 0 2 1 129 0.1T -70', '0 0 -1 2 2 123 -53', '2 0 0 0 0 63', '0 0 1 0 1 63 0.1T -33', '2 0 -1 2 2 -59 26', '0 0 -1 0 1 -58 -0.1T 32', '0 0 1 2 1 -51 27', '-2 0 2 0 0 48', '0 0 -2 2 1 46 -24', '2 0 0 2 2 -38 16', '0 0 2 2 2 -31 13', '0 0 2 0 0 29', '-2 0 1 2 2 29 -12', '0 0 0 2 0 26', '-2 0 0 2 0 -22', '0 0 -1 2 1 21 -10', '0 2 0 0 0 17 -0.1T', '2 0 -1 0 1 16 -8', '-2 2 0 2 2 -16 0.1T 7', '0 1 0 0 1 -15 9', '-2 0 1 0 1 -13 7', '0 -1 0 0 1 -12 6', '0 0 2 -2 0 11', '2 0 -1 2 1 -10 5', '2 0 1 2 2 -8 3', '0 1 0 2 2 7 -3', '-2 1 1 0 0 -7', '0 -1 0 2 2 -7 3', '2 0 0 2 1 -7 3', '2 0 1 0 0 6', '-2 0 2 2 2 6 -3', '-2 0 1 2 1 6 -3', '2 0 -2 0 1 -6 3', '2 0 0 0 1 -6 3', '0 -1 1 0 0 5', '-2 -1 0 2 1 -5 3', '-2 0 0 0 1 -5 3', '0 0 2 2 1 -5 3', '-2 0 2 0 1 4', '-2 1 0 2 1 4', '0 0 1 -2 0 4', '-1 0 1 0 0 -4', '-2 1 0 0 0 -4', '1 0 0 0 0 -4', '0 0 1 2 0 3', '0 0 -2 2 2 -3', '-1 -1 1 0 0 -3', '0 1 1 0 0 -3', '0 -1 1 2 2 -3', '2 -1 -1 2 2 -3', '0 0 3 2 2 -3', '2 -1 0 2 2 -3' ]; let terms; (function () { terms = table.map((line) => { const fields = line.split(' '); const value = [0, 0, 0, 0, 0, 0, 0, 0, 0]; let index = 0; let hasT; for (let field of fields) { if (field.endsWith('T')) { hasT = true; field = field.substring(0, field.length - 1); } else hasT = false; if (index === 6 && !hasT) ++index; value[index] = Number(field); ++index; } return { fD: value[0], fM: value[1], fM1: value[2], fF: value[3], fQ: value[4], cs0: value[5], cs1: value[6], cc0: value[7], cc1: value[8] }; }); })(); export class Ecliptic { constructor() { this.cachedTime = 0; this.cachedMode = NMode.NUTATED; this.cachedNutation = null; } static precessEquatorial(pos, initialOrFinalEpoch, finalEpoch) { let initialEpoch; if (isNumber(finalEpoch)) initialEpoch = initialOrFinalEpoch; else { initialEpoch = JD_J2000; finalEpoch = initialOrFinalEpoch; } const T = (initialEpoch - JD_J2000) / 36525; const T2 = T ** 2; const t = (finalEpoch - initialEpoch) / 36525; const t2 = t ** 2; const t3 = t2 * t; const RA0 = pos.rightAscension.radians; const dec0 = pos.declination.radians; let ζ = (2306.2181 + 1.39656 * T - 0.000139 * T2) * t + (0.30188 - 0.000344 * T) * t2 + 0.017998 * t3; let z = (2306.2181 + 1.39656 * T - 0.000139 * T2) * t + (1.09468 + 0.000066 * T) * t2 + 0.018203 * t3; let θ = (2004.3109 - 0.85330 * T - 0.000217 * T2) * t - (0.42665 + 0.000217 * T) * t2 - 0.041833 * t3; // For convenience, convert the above arcsecond values to radians. ζ *= PI / 648000; z *= PI / 648000; θ *= PI / 648000; const A = cos(dec0) * sin(RA0 + ζ); const B = cos(θ) * cos(dec0) * cos(RA0 + ζ) - sin(θ) * sin(dec0); const C = sin(θ) * cos(dec0) * cos(RA0 + ζ) + cos(θ) * sin(dec0); const RA = atan2(A, B) + z; let dec; // We'll use a different calculation for positions within 1 arcsecond // of either celestial pole. if (HALF_PI - abs(dec0) > 4.85E-6) dec = asin(C); else dec = sqrt(A ** 2 + B ** 2); return new SphericalPosition(RA, dec); } static precessEquatorial3D(pos, initialOrFinalEpoch, finalEpoch) { const pos2 = Ecliptic.precessEquatorial(pos, initialOrFinalEpoch, finalEpoch); return new SphericalPosition3D(pos2.longitude, pos2.latitude, pos.radius); } static precessEcliptical(pos, initialOrFinalEpoch, finalEpoch) { let initialEpoch; if (isNumber(finalEpoch)) initialEpoch = initialOrFinalEpoch; else { initialEpoch = JD_J2000; finalEpoch = initialOrFinalEpoch; } const T = (initialEpoch - JD_J2000) / 36525; const T2 = T ** 2; const t = (finalEpoch - initialEpoch) / 36525; const t2 = t ** 2; const t3 = t2 * t; const L0 = pos.longitude.radians; const B0 = pos.latitude.radians; let η = (47.0029 - 0.06603 * T + 0.000598 * T2) * t + (-0.03302 + 0.000598 * T) * t2 + 0.000060 * t3; let P1 = (174.876384 * 3600) + 3289.4789 * T + 0.60622 * T2 - (869.8089 + 0.50491 * T) * t + 0.03536 * t2; let p = (5029.0966 + 2.22226 * T - 0.000042 * T2) * t + (1.11113 - 0.000042 * T) * t2 - 0.000006 * t3; // For convenience, convert the above arcsecond values to radians. η *= PI / 648000; P1 *= PI / 648000; p *= PI / 648000; const A1 = cos(η) * cos(B0) * sin(P1 - L0) - sin(η) * sin(B0); const B1 = cos(B0) * cos(P1 - L0); const C1 = cos(η) * sin(B0) + sin(η) * cos(B0) * sin(P1 - L0); const L = p + P1 - atan2(A1, B1); const B = asin(limitNeg1to1(C1)); return new SphericalPosition(L, B); } static precessEcliptical3D(pos, initialOrFinalEpoch, finalEpoch) { const pos2 = Ecliptic.precessEcliptical(pos, initialOrFinalEpoch, finalEpoch); return new SphericalPosition3D(pos2.longitude, pos2.latitude, pos.radius); } getNutation(time_JDE, mode = NMode.NUTATED) { if (this.cachedTime === time_JDE && this.cachedMode === mode) return this.cachedNutation; const T = (time_JDE - JD_J2000) / 36525; const result = {}; if (mode === NMode.J2000) { result.Δψ = new Angle(0); result.Δε = new Angle(0); result.ε = new Angle(OBLIQUITY_J2000, Unit.DEGREES); } else { let U = T / 100; let e = OBLIQUITY_J2000; for (const coeff of coeffs) { e += coeff * U / 3600; U *= U; } result.ε = new Angle(e, Unit.DEGREES); if (mode === NMode.MEAN_OBLIQUITY) { result.Δψ = new Angle(0); result.Δε = new Angle(0); } else { const T2 = T ** 2; const T3 = T2 * T; // Mean elongation of Moon from Sun const D = 297.85036 + 445267.111480 * T - 0.0019142 * T2 + T3 / 189474; // Mean anomaly of Sun const M = 357.52772 + 35999.050340 * T - 0.0001603 * T2 - T3 / 300000; // Mean anomaly of Moon const M1 = 134.96298 + 477198.867398 * T + 0.0086972 * T2 + T3 / 56250; // Moon's argument of latitude const F = 93.27191 + 483202.017538 * T + 0.0036825 * T2 + T3 / 327270; // Longitude of ascending node of Moon's mean orbit const Q = 125.04452 - 1934.136261 * T + 0.0020708 * T2 + T3 / 450000; let arg; let Δψ = 0; let Δε = 0; for (const term of terms) { arg = D * term.fD + M * term.fM + M1 * term.fM1 + F * term.fF + Q * term.fQ; Δψ += sin_deg(arg) * (term.cs0 + term.cs1 * T); Δε += cos_deg(arg) * (term.cc0 + term.cc1 * T); } result.Δψ = new Angle(Δψ / 10000, Unit.ARC_SECONDS); result.Δε = new Angle(Δε / 10000, Unit.ARC_SECONDS); result.ε = result.ε.add(result.Δε); } } this.cachedTime = time_JDE; this.cachedMode = mode; this.cachedNutation = result; return this.cachedNutation; } nutateEclipticPosition(pos, time_JDE, mode = NMode.NUTATED) { if (mode === NMode.J2000) return pos; let nutation = this.getNutation(time_JDE, mode === NMode.ANTI_NUTATED ? NMode.NUTATED : mode).Δψ; if (mode === NMode.ANTI_NUTATED) nutation = nutation.negate(); return new SphericalPosition(pos.longitude.add_nonneg(nutation), pos.latitude); } nutateEclipticPosition3D(pos, time_JDE, mode = NMode.NUTATED) { if (mode === NMode.J2000) return pos; return SphericalPosition3D.from2D(this.nutateEclipticPosition(pos, time_JDE, mode), pos.radius); } nutateEquatorialPosition(pos, time_JDE, mode = NMode.NUTATED) { if (mode === NMode.J2000) return pos; let eclipticPosition = this.equatorialToEcliptic(pos, time_JDE, mode); eclipticPosition = this.nutateEclipticPosition(eclipticPosition, time_JDE, mode); return this.eclipticToEquatorial(eclipticPosition, time_JDE, mode); } nutateEquatorialPosition3D(pos, time_JDE, mode = NMode.NUTATED) { if (mode === NMode.J2000) return pos; return SphericalPosition3D.from2D(this.nutateEquatorialPosition(pos, time_JDE, mode), pos.radius); } eclipticToEquatorial(pos, time_JDE = JD_J2000, mode = NMode.J2000) { const nutation = this.getNutation(time_JDE, mode); const L = pos.rightAscension; const B = pos.declination; const E = nutation.ε; return new SphericalPosition(Angle.atan2_nonneg(L.sin * E.cos - B.tan * E.sin, L.cos), Angle.asin(limitNeg1to1(B.sin * E.cos + B.cos * E.sin * L.sin))); } eclipticToEquatorial3D(pos, time_JDE = JD_J2000, mode = NMode.J2000) { return SphericalPosition3D.from2D(this.eclipticToEquatorial(pos, time_JDE, mode), pos.radius); } equatorialToEcliptic(pos, time_JDE = JD_J2000, mode = NMode.J2000) { const nutation = this.getNutation(time_JDE, mode); const RA = pos.rightAscension; const dec = pos.declination; const E = nutation.ε; return new SphericalPosition(Angle.atan2_nonneg(RA.sin * E.cos + dec.tan * E.sin, RA.cos), Angle.asin(limitNeg1to1(dec.sin * E.cos - dec.cos * E.sin * RA.sin))); } equatorialToEcliptic3D(pos, time_JDE = JD_J2000, mode = NMode.J2000) { return SphericalPosition3D.from2D(this.equatorialToEcliptic(pos, time_JDE, mode), pos.radius); } } //# sourceMappingURL=ecliptic.js.map