UNPKG

@tubular/astronomy

Version:

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

150 lines 7.89 kB
import { abs, Angle, asin, atan, atan2, cos, FMT_MINS, HALF_PI, interpolate, limitNeg1to1, mod, PI, sign, sin, SphericalPosition, SphericalPosition3D, sqrt, tan, to_degree, to_radian, TWO_PI, Unit } from '@tubular/math'; import { tdtToUt, utToTdt } from '@tubular/time'; import { isNumber } from '@tubular/util'; import { ABERRATION, EARTH_RADIUS_KM, EARTH_RADIUS_POLAR_KM, KM_PER_AU, NUTATION, REFRACTION, SUN, TOPOCENTRIC } from './astro-constants'; import { refractedAltitude, unrefractedAltitude } from './astronomy-util'; import { SolarSystem } from './solar-system'; const A90_1SEC = 1.5707915; const A90_2SEC = 1.5707866; SolarSystem.createSkyObserver = (longitude, latitude) => new SkyObserver(longitude, latitude); export class SkyObserver { constructor(longitudeOrLatLong, latitude) { this.elevation = 0; this.cachedHourAngle = null; this.cacheTimeHourAngle = 0; this.cacheApparentHourAngle = false; if (!SkyObserver.solarSystem) SkyObserver.solarSystem = new SolarSystem(); if (longitudeOrLatLong instanceof SphericalPosition) { this._longitude = longitudeOrLatLong.longitude; this._latitude = longitudeOrLatLong.latitude; } else { if (isNumber(longitudeOrLatLong)) this._longitude = new Angle(longitudeOrLatLong, Unit.DEGREES); else this._longitude = longitudeOrLatLong; if (isNumber(latitude)) this._latitude = new Angle(latitude, Unit.DEGREES); else this._latitude = latitude; } this.computeGeocentricValues(); } computeGeocentricValues() { const peRatio = EARTH_RADIUS_POLAR_KM / EARTH_RADIUS_KM; const latRad = this._latitude.radians; let u; // If within one arc-second of either pole, u is very close to the value of // the latitude anyway, so avoid having the tan function blow up at ±90°. // Between one and two arc-seconds from pole, interpolate to avoid a discontinuity. if (abs(latRad) > A90_1SEC) u = latRad; else if (abs(latRad) > A90_2SEC) { const s = sign(latRad); u = interpolate(s * A90_1SEC, latRad, s * A90_2SEC, latRad, atan(peRatio * tan(A90_2SEC))); } else u = atan(peRatio * this._latitude.tan); this.ρ_sin_gcl = peRatio * sin(u) + this.elevation / EARTH_RADIUS_KM / 1000 * this._latitude.sin; this.ρ_cos_gcl = cos(u) + this.elevation / EARTH_RADIUS_KM / 1000 * this._latitude.cos; } get longitude() { return this._longitude; } get latitude() { return this._latitude; } getLocalHourAngle(time_JDU, apparent) { if (this.cachedHourAngle === null || this.cacheTimeHourAngle !== time_JDU || this.cacheApparentHourAngle === apparent) { let gst; if (apparent) gst = SkyObserver.solarSystem.getGreenwichApparentSiderealTime(time_JDU); else gst = SolarSystem.getGreenwichMeanSiderealTime(time_JDU); this.cachedHourAngle = new Angle(gst, Unit.DEGREES).add_nonneg(this._longitude); this.cacheTimeHourAngle = time_JDU; this.cacheApparentHourAngle = apparent; } return this.cachedHourAngle; } getApparentSolarTime(time_JDU) { const lha = this.getLocalHourAngle(time_JDU, true); const time_JDE = utToTdt(time_JDU); const sun = SkyObserver.solarSystem.getEquatorialPosition(SUN, time_JDE, this).rightAscension; return lha.subtract(sun).add_nonneg(Angle.STRAIGHT); } // Note: Only right ascension and declination are modified -- distance is not modified // by the offset of the geographic location of the observer from the center of the // Earth. Distance is modified, however, in the function equatorialToHorizontalAux() in // order to improve the accuracy of computations such as the angular size of the Moon. // // Note: If diurnal aberration is computed for coordinates near the poles, there is a // slight discontinuity within one arcsecond of each pole. // equatorialTopocentricAdjustment(pos, time_JDE, flags) { const time_JDU = tdtToUt(time_JDE); const lha = this.getLocalHourAngle(time_JDU, (flags & NUTATION) !== 0).radians; const distance = pos.radius; // Sine of parallax, using 8.79412 arc seconds and distance in AU. const sinp = sin(8.79412 / 3600 / 180 * PI) / distance; let RA = pos.rightAscension.radians; const d = pos.declination.radians; const H = lha - RA; let ΔRA = atan2(-this.ρ_cos_gcl * sinp * sin(H), cos(d) - this.ρ_cos_gcl * sinp * cos(H)); let d1 = atan2((sin(d) - this.ρ_sin_gcl * sinp) * cos(ΔRA), cos(d) - this.ρ_cos_gcl * sinp * cos(H)); if ((flags & ABERRATION) !== 0) { RA += ΔRA; if (abs(d1) > HALF_PI - 4.85E-6) { ΔRA = 0; const rd = HALF_PI - abs(d1); const rl = 1.551E-6 * this._latitude.cos; const x = cos(RA) * rd - sin(lha) * rl; const y = sin(RA) * rd + cos(lha) * rl; const r = sqrt(x ** 2 + y ** 2); RA = atan2(y, x); d1 = (HALF_PI - r) * sign(d1); } else { ΔRA = 1.551E-6 * this._latitude.cos * cos(H) / cos(d1); d1 += 1.551E-6 * this._latitude.cos * sin(d1) * sin(H); } } return new SphericalPosition3D(RA + ΔRA, d1, distance); } equatorialToHorizontal(pos, time_JDU, flags = 0) { // Calculations are faster if nutation isn't calculated into the position of the planet, // because then nutation doesn't need to be figured into the hour angle either. const lha = this.getLocalHourAngle(time_JDU, (flags & NUTATION) !== 0).radians; const RA = pos.rightAscension.radians; const d = pos.declination.radians; const H = lha - RA; const azimuth = atan2(sin(H), (cos(H) * this._latitude.sin - tan(d) * this._latitude.cos)); let altitude = asin(limitNeg1to1(this._latitude.sin * sin(d) + this._latitude.cos * cos(d) * cos(H))); const unrefracted = altitude; if ((flags & REFRACTION) !== 0) altitude = to_radian(refractedAltitude(to_degree(altitude))); if (pos instanceof SphericalPosition3D) { let distance = pos.radius; if ((flags & TOPOCENTRIC) !== 0) { const earthCtrDistance = EARTH_RADIUS_POLAR_KM + (EARTH_RADIUS_KM - EARTH_RADIUS_POLAR_KM) * this._latitude.cos + this.elevation / 1000; distance -= sin(unrefracted) * earthCtrDistance / KM_PER_AU; } return new SphericalPosition3D(azimuth, altitude, distance); } else return new SphericalPosition(azimuth, altitude); } horizontalToEquatorial(pos, time_JDU, flags = 0) { const lha = this.getLocalHourAngle(time_JDU, (flags & NUTATION) !== 0).radians; let altitude = pos.altitude.radians; const azimuth = pos.azimuth.radians; if ((flags & REFRACTION) !== 0) altitude = to_radian(unrefractedAltitude(to_degree(altitude))); const RA = lha - atan2(sin(azimuth), cos(azimuth) * this._latitude.sin + tan(altitude) * this._latitude.cos); const declination = asin(limitNeg1to1(this._latitude.sin * sin(altitude) - this._latitude.cos * cos(altitude) * cos(azimuth))); return new SphericalPosition(mod(RA, TWO_PI), declination); } toString() { return `[${this._longitude.toString(FMT_MINS)}, ${this._latitude.toString(FMT_MINS)}, ${this.elevation}]`; } } //# sourceMappingURL=sky-observer.js.map