astronomia
Version:
An astronomical library
416 lines (397 loc) • 13.2 kB
JavaScript
/**
* @copyright 2013 Sonia Keys
* @copyright 2016 commenthol
* @license MIT
* @module precess
*/
/**
* Precession: Chapter 21, Precession.
*
* Functions in this package take Julian epoch argurments rather than Julian
* days. Use base.JDEToJulianYear() to convert.
*
* Also in package base are some definitions related to the Besselian and
* Julian Year.
*
* Partial: Precession from FK4 not implemented. Meeus gives no test cases.
* It's a fair amount of code and data, representing significant chances for
* errors. And precession from FK4 would seem to be of little interest today.
*
* Proper motion units
*
* Meeus gives some example annual proper motions in units of seconds of
* right ascension and seconds of declination. To make units clear,
* functions in this package take proper motions with argument types of
* sexa.HourAngle and sexa.Angle respectively. Error-prone conversions
* can be avoided by using the constructors for these base types.
*
* For example, given an annual proper motion in right ascension of -0ˢ.03847,
* rather than
*
* mra = -0.03847 / 13751 // as Meeus suggests
*
* or
*
* mra = -0.03847 * (15/3600) * (pi/180) // less magic
*
* use
*
* mra = new sexa.HourAngle(false, 0, 0, -0.03847)
*
* Unless otherwise indicated, functions in this library expect proper motions
* to be annual proper motions, so the unit denominator is years.
* (The code, following Meeus's example, technically treats it as Julian years.)
*/
import base from './base.js'
import { Equatorial, Ecliptic } from './coord.js'
import { Elements } from './elementequinox.js'
import nutation from './nutation.js'
import { HourAngle, Angle } from './sexagesimal.js'
/**
* approxAnnualPrecession returns approximate annual precision in right
* ascension and declination.
*
* The two epochs should be within a few hundred years.
* The declinations should not be too close to the poles.
*
* @param {Equatorial} eqFrom
* @param {Number} epochFrom - use `base.JDEToJulianYear(year)` to get epoch
* @param {Number} epochTo - use `base.JDEToJulianYear(year)` to get epoch
* @returns {Object}
* {sexa.HourAngle} seconds of right ascension
* {sexa.Angle} seconds of Declination
*/
export function approxAnnualPrecession (eqFrom, epochFrom, epochTo) {
const [m, na, nd] = mn(epochFrom, epochTo)
const [sa, ca] = base.sincos(eqFrom.ra)
// (21.1) p. 132
const Δαs = m + na * sa * Math.tan(eqFrom.dec) // seconds of RA
const Δδs = nd * ca // seconds of Dec
const ra = new HourAngle(false, 0, 0, Δαs).rad()
const dec = new Angle(false, 0, 0, Δδs).rad()
return { ra, dec }
}
/**
* @param {Number} epochFrom - use `base.JDEToJulianYear(year)` to get epoch
* @param {Number} epochTo - use `base.JDEToJulianYear(year)` to get epoch
* @returns {Number[]}
*/
export function mn (epochFrom, epochTo) {
const T = (epochTo - epochFrom) * 0.01
const m = 3.07496 + 0.00186 * T
const na = 1.33621 - 0.00057 * T
const nd = 20.0431 - 0.0085 * T
return [m, na, nd]
}
/**
* ApproxPosition uses ApproxAnnualPrecession to compute a simple and quick
* precession while still considering proper motion.
*
* @param {Equatorial} eqFrom
* @param {Number} epochFrom
* @param {Number} epochTo
* @param {Number} mα - in radians
* @param {Number} mδ - in radians
* @returns {Equatorial} eqTo
*/
export function approxPosition (eqFrom, epochFrom, epochTo, mα, mδ) {
const { ra, dec } = approxAnnualPrecession(eqFrom, epochFrom, epochTo)
const dy = epochTo - epochFrom
const eqTo = new Equatorial()
eqTo.ra = eqFrom.ra + (ra + mα) * dy
eqTo.dec = eqFrom.dec + (dec + mδ) * dy
return eqTo
}
// constants
const d = Math.PI / 180
const s = d / 3600
// coefficients from (21.2) p. 134
const ζT = [2306.2181 * s, 1.39656 * s, -0.000139 * s]
const zT = [2306.2181 * s, 1.39656 * s, -0.000139 * s]
const θT = [2004.3109 * s, -0.8533 * s, -0.000217 * s]
// coefficients from (21.3) p. 134
const ζt = [2306.2181 * s, 0.30188 * s, 0.017998 * s]
const zt = [2306.2181 * s, 1.09468 * s, 0.018203 * s]
const θt = [2004.3109 * s, -0.42665 * s, -0.041833 * s]
/**
* Precessor represents precession from one epoch to another.
*
* Construct with NewPrecessor, then call method Precess.
* After construction, Precess may be called multiple times to precess
* different coordinates with the same initial and final epochs.
*/
export class Precessor {
/**
* constructs a Precessor object and initializes it to precess
* coordinates from epochFrom to epochTo.
* @param {Number} epochFrom
* @param {Number} epochTo
*/
constructor (epochFrom, epochTo) {
// (21.2) p. 134
let ζCoeff = ζt
let zCoeff = zt
let θCoeff = θt
if (epochFrom !== 2000) {
const T = (epochFrom - 2000) * 0.01
ζCoeff = [
base.horner(T, ...ζT),
0.30188 * s - 0.000344 * s * T,
0.017998 * s
]
zCoeff = [
base.horner(T, ...zT),
1.09468 * s + 0.000066 * s * T,
0.018203 * s
]
θCoeff = [
base.horner(T, ...θT),
-0.42665 * s - 0.000217 * s * T,
-0.041833 * s
]
}
const t = (epochTo - epochFrom) * 0.01
this.ζ = base.horner(t, ...ζCoeff) * t
this.z = base.horner(t, ...zCoeff) * t
const θ = base.horner(t, ...θCoeff) * t
this.sθ = Math.sin(θ)
this.cθ = Math.cos(θ)
}
/**
* Precess precesses coordinates eqFrom, leaving result in eqTo.
*
* @param {Equatorial} eqFrom
* @returns {Equatorial} eqTo
*/
precess (eqFrom) {
// (21.4) p. 134
const [sδ, cδ] = base.sincos(eqFrom.dec)
const [sαζ, cαζ] = base.sincos(eqFrom.ra + this.ζ)
const A = cδ * sαζ
const B = this.cθ * cδ * cαζ - this.sθ * sδ
const C = this.sθ * cδ * cαζ + this.cθ * sδ
const eqTo = new Equatorial()
eqTo.ra = Math.atan2(A, B) + this.z
if (C < base.CosSmallAngle) {
eqTo.dec = Math.asin(C)
} else {
eqTo.dec = Math.acos(Math.hypot(A, B)) // near pole
}
return eqTo
}
}
/**
* Position precesses equatorial coordinates from one epoch to another,
* including proper motions.
*
* If proper motions are not to be considered or are not applicable, pass 0, 0
* for mα, mδ
*
* Both eqFrom and eqTo must be non-nil, although they may point to the same
* struct. EqTo is returned for convenience.
* @param {Equatorial} eqFrom
* @param {Number} epochFrom
* @param {Number} epochTo
* @param {Number} mα - in radians
* @param {Number} mδ - in radians
* @returns {Equatorial} [eqTo]
*/
export function position (eqFrom, epochFrom, epochTo, mα, mδ) {
const p = new Precessor(epochFrom, epochTo)
const t = epochTo - epochFrom
const eqTo = new Equatorial()
eqTo.ra = eqFrom.ra + mα * t
eqTo.dec = eqFrom.dec + mδ * t
return p.precess(eqTo)
}
// coefficients from (21.5) p. 136
const ηT = [47.0029 * s, -0.06603 * s, 0.000598 * s]
const πT = [174.876384 * d, 3289.4789 * s, 0.60622 * s]
const pT = [5029.0966 * s, 2.22226 * s, -0.000042 * s]
const ηt = [47.0029 * s, -0.03302 * s, 0.000060 * s]
const πt = [174.876384 * d, -869.8089 * s, 0.03536 * s]
const pt = [5029.0966 * s, 1.11113 * s, -0.000006 * s]
/**
* EclipticPrecessor represents precession from one epoch to another.
*
* Construct with NewEclipticPrecessor, then call method Precess.
* After construction, Precess may be called multiple times to precess
* different coordinates with the same initial and final epochs.
*/
export class EclipticPrecessor {
/**
* constructs an EclipticPrecessor object and initializes
* it to precess coordinates from epochFrom to epochTo.
* @param {Number} epochFrom
* @param {Number} epochTo
*/
constructor (epochFrom, epochTo) {
// (21.5) p. 136
let ηCoeff = ηt
let πCoeff = πt
let pCoeff = pt
if (epochFrom !== 2000) {
const T = (epochFrom - 2000) * 0.01
ηCoeff = [
base.horner(T, ...ηT),
-0.03302 * s + 0.000598 * s * T,
0.000060 * s
]
πCoeff = [
base.horner(T, ...πT),
-869.8089 * s - 0.50491 * s * T,
0.03536 * s
]
pCoeff = [
base.horner(T, ...pT),
1.11113 * s - 0.000042 * s * T,
-0.000006 * s
]
}
const t = (epochTo - epochFrom) * 0.01
this.π = base.horner(t, ...πCoeff)
this.p = base.horner(t, ...pCoeff) * t
const η = base.horner(t, ...ηCoeff) * t
this.sη = Math.sin(η)
this.cη = Math.cos(η)
}
/**
* EclipticPrecess precesses coordinates eclFrom, leaving result in eclTo.
*
* The same struct may be used for eclFrom and eclTo.
* EclTo is returned for convenience.
* @param {Ecliptic} eclFrom
* @returns {Ecliptic} [eclTo]
*/
precess (eclFrom) {
// (21.7) p. 137
const [sβ, cβ] = base.sincos(eclFrom.lat)
const [sd, cd] = base.sincos(this.π - eclFrom.lon)
const A = this.cη * cβ * sd - this.sη * sβ
const B = cβ * cd
const C = this.cη * sβ + this.sη * cβ * sd
const eclTo = new Ecliptic(this.p + this.π - Math.atan2(A, B))
if (C < base.CosSmallAngle) {
eclTo.lat = Math.asin(C)
} else {
eclTo.lat = Math.acos(Math.hypot(A, B)) // near pole
}
return eclTo
}
/**
* ReduceElements reduces orbital elements of a solar system body from one
* equinox to another.
*
* This function is described in chapter 24, but is located in this
* package so it can be a method of EclipticPrecessor.
*
* @param {Elements} eFrom
* @returns {Elements} eTo
*/
reduceElements (eFrom) {
const ψ = this.π + this.p
const [si, ci] = base.sincos(eFrom.inc)
const [snp, cnp] = base.sincos(eFrom.node - this.π)
const eTo = new Elements()
// (24.1) p. 159
eTo.inc = Math.acos(ci * this.cη + si * this.sη * cnp)
// (24.2) p. 159
eTo.node = Math.atan2(si * snp, this.cη * si * cnp - this.sη * ci) + ψ
// (24.3) p. 159
eTo.peri = Math.atan2(-this.sη * snp, si * this.cη - ci * this.sη * cnp) + eFrom.peri
return eTo
}
}
/**
* eclipticPosition precesses ecliptic coordinates from one epoch to another,
* including proper motions.
* While eclFrom is given as ecliptic coordinates, proper motions mα, mδ are
* still expected to be equatorial. If proper motions are not to be considered
* or are not applicable, pass 0, 0.
* Both eclFrom and eclTo must be non-nil, although they may point to the same
* struct. EclTo is returned for convenience.
*
* @param {Ecliptic} eclFrom,
* @param {Number} epochFrom
* @param {HourAngle} [mα]
* @param {Angle} [mδ]
* @returns {Ecliptic} eclTo
*/
export function eclipticPosition (eclFrom, epochFrom, epochTo, mα, mδ) {
const p = new EclipticPrecessor(epochFrom, epochTo)
if (mα && mδ && (mα.rad() !== 0 || mδ.rad() !== 0)) {
const { lon, lat } = properMotion(mα.rad(), mδ.rad(), epochFrom, eclFrom)
const t = epochTo - epochFrom
eclFrom.lon += lon * t
eclFrom.lat += lat * t
}
return p.precess(eclFrom)
}
/**
* @param {Number} mα - anual proper motion (ra)
* @param {Number} mδ - anual proper motion (dec)
* @param {Number} epoch
* @param {Ecliptic} ecl
* @returns {Ecliptic} {lon, lat}
*/
export function properMotion (mα, mδ, epoch, ecl) {
const ε = nutation.meanObliquity(base.JulianYearToJDE(epoch))
const [εsin, εcos] = base.sincos(ε)
const { ra, dec } = ecl.toEquatorial(ε)
const [sα, cα] = base.sincos(ra)
const [sδ, cδ] = base.sincos(dec)
const cβ = Math.cos(ecl.lat)
const lon = (mδ * εsin * cα + mα * cδ * (εcos * cδ + εsin * sδ * sα)) / (cβ * cβ)
const lat = (mδ * (εcos * cδ + εsin * sδ * sα) - mα * εsin * cα * cδ) / cβ
return new Ecliptic(lon, lat)
}
/**
* ProperMotion3D takes the 3D equatorial coordinates of an object
* at one epoch and computes its coordinates at a new epoch, considering
* proper motion and radial velocity.
*
* Radial distance (r) must be in parsecs, radial velocitiy (mr) in
* parsecs per year.
*
* Both eqFrom and eqTo must be non-nil, although they may point to the same
* struct. EqTo is returned for convenience.
*
* @param {Equatorial} eqFrom,
* @param {Number} epochFrom
* @param {Number} r
* @param {Number} mr
* @param {HourAngle} mα
* @param {Angle} mδ
* @returns {Equatorial} eqTo
*/
export function properMotion3D (eqFrom, epochFrom, epochTo, r, mr, mα, mδ) {
const [sα, cα] = base.sincos(eqFrom.ra)
const [sδ, cδ] = base.sincos(eqFrom.dec)
const x = r * cδ * cα
const y = r * cδ * sα
const z = r * sδ
const mrr = mr / r
const zmδ = z * mδ.rad()
const mx = x * mrr - zmδ * cα - y * mα.rad()
const my = y * mrr - zmδ * sα + x * mα.rad()
const mz = z * mrr + r * mδ.rad() * cδ
const t = epochTo - epochFrom
const xp = x + t * mx
const yp = y + t * my
const zp = z + t * mz
const eqTo = new Equatorial()
eqTo.ra = Math.atan2(yp, xp)
eqTo.dec = Math.atan2(zp, Math.hypot(xp, yp))
return eqTo
}
export default {
approxAnnualPrecession,
mn,
approxPosition,
Precessor,
position,
EclipticPrecessor,
eclipticPosition,
properMotion,
properMotion3D
}