mirror of
https://github.com/cosinekitty/astronomy.git
synced 2026-01-04 13:37:44 -05:00
4139 lines
168 KiB
JavaScript
4139 lines
168 KiB
JavaScript
/**
|
|
@preserve
|
|
|
|
Astronomy library for JavaScript (browser and Node.js).
|
|
https://github.com/cosinekitty/astronomy
|
|
|
|
MIT License
|
|
|
|
Copyright (c) 2019 Don Cross <cosinekitty@gmail.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Astronomy calculation library for browser scripting and Node.js.
|
|
*
|
|
* @author Don Cross <cosinekitty@gmail.com>
|
|
* @license MIT
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* @name Astronomy
|
|
* @namespace Astronomy
|
|
*/
|
|
(function(Astronomy){
|
|
'use strict';
|
|
const J2000 = new Date('2000-01-01T12:00:00Z');
|
|
const T0 = 2451545.0;
|
|
const MJD_BASIS = 2400000.5; // mjd + MJD_BASIS = jd
|
|
const Y2000_IN_MJD = T0 - MJD_BASIS; // the 2000.0 epoch expressed in MJD
|
|
const PI2 = 2 * Math.PI;
|
|
const ARC = 3600 * (180 / Math.PI); // arcseconds per radian
|
|
const ERAD = 6378136.6; // mean earth radius in meters
|
|
const AU = 1.4959787069098932e+11; // astronomical unit in meters
|
|
const C_AUDAY = 173.1446326846693; // speed of light in AU/day
|
|
const ASEC2RAD = 4.848136811095359935899141e-6;
|
|
const DEG2RAD = 0.017453292519943296;
|
|
const RAD2DEG = 57.295779513082321;
|
|
const ASEC180 = 180 * 60 * 60; // arcseconds per 180 degrees (or pi radians)
|
|
const ASEC360 = 2 * ASEC180; // arcseconds per 360 degrees (or 2*pi radians)
|
|
const ANGVEL = 7.2921150e-5;
|
|
const KM_PER_AU = 1.4959787069098932e+8;
|
|
const AU_PER_PARSEC = ASEC180 / Math.PI; // exact definition of how many AU = one parsec
|
|
const SUN_MAG_1AU = -0.17 - 5*Math.log10(AU_PER_PARSEC); // formula from JPL Horizons
|
|
const MEAN_SYNODIC_MONTH = 29.530588; // average number of days for Moon to return to the same phase
|
|
const SECONDS_PER_DAY = 24 * 3600;
|
|
const MILLIS_PER_DAY = SECONDS_PER_DAY * 1000;
|
|
const SOLAR_DAYS_PER_SIDEREAL_DAY = 0.9972695717592592;
|
|
const SUN_RADIUS_AU = 4.6505e-3;
|
|
const MOON_RADIUS_AU = 1.15717e-5;
|
|
const REFRACTION_NEAR_HORIZON = 34 / 60; // degrees of refractive "lift" seen for objects near horizon
|
|
let ob2000; // lazy-evaluated mean obliquity of the ecliptic at J2000, in radians
|
|
let cos_ob2000;
|
|
let sin_ob2000;
|
|
|
|
//========================================================================================
|
|
// BEGIN performance measurement
|
|
|
|
/**
|
|
* Holds performance metrics for developers to optimize execution speed.
|
|
* Most users can safely ignore this class.
|
|
*
|
|
* @class
|
|
*
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} search_func
|
|
* Number of times {@link Astronomy.Search} called a <code>func</code> passed to it.
|
|
*
|
|
* @property {number} search
|
|
* Number of times {@link Astronomy.Search} was called.
|
|
*
|
|
* @property {number} longitude_search
|
|
* Number of times {@link Astronomy.SearchRelativeLongitude} was called.
|
|
*
|
|
* @property {number} longitude_iter
|
|
* The total number of iterations executed inside {@link Astronomy.SearchRelativeLongitude}.
|
|
*
|
|
* @property {number} lunar_apsis_calls
|
|
* The number of times {@link Astronomy.SearchLunarApsis} was called.
|
|
*
|
|
* @property {number} lunar_apsis_iter
|
|
* The number of search iterations inside {@link Astronomy.SearchLunarApsis}.
|
|
*
|
|
* @property {number} calcmoon
|
|
* The number of times the Moon's position was calculated. (This is an expensive operation.)
|
|
*/
|
|
class PerformanceInfo {
|
|
constructor() {
|
|
this.search_func = 0;
|
|
this.search = 0;
|
|
this.longitude_search = 0;
|
|
this.longitude_iter = 0;
|
|
this.lunar_apsis_calls = 0;
|
|
this.lunar_apsis_iter = 0;
|
|
this.calcmoon = 0;
|
|
}
|
|
|
|
/**
|
|
* Creates a copy of a <code>PerformanceInfo</code> object.
|
|
* This allows us to create a snapshot of the performance metrics
|
|
* that can be handed back to outside code that will not change
|
|
* as the Astronomy code continues to execute and change the metrics.
|
|
*
|
|
* @returns {Astronomy.PerformanceInfo}
|
|
*/
|
|
Clone() {
|
|
const clone = new PerformanceInfo();
|
|
clone.search_func = this.search_func;
|
|
clone.search = this.search;
|
|
clone.longitude_search = this.longitude_search;
|
|
clone.longitude_iter = this.longitude_iter;
|
|
clone.lunar_apsis_calls = this.lunar_apsis_calls;
|
|
clone.lunar_apsis_iter = this.lunar_apsis_iter;
|
|
clone.calcmoon = this.calcmoon;
|
|
return clone;
|
|
}
|
|
}
|
|
|
|
let Perf = new PerformanceInfo();
|
|
|
|
/**
|
|
* Takes a snapshot of the current state of the performance metrics.
|
|
* The metrics inside the returned object will not change and can be retained by calling code
|
|
* to be compared with later snapshots.
|
|
*
|
|
* @returns {Astronomy.PerformanceInfo}
|
|
*/
|
|
Astronomy.GetPerformanceMetrics = function() {
|
|
return Perf.Clone();
|
|
}
|
|
|
|
/**
|
|
* Resets the internal performance metrics back to their initial states.
|
|
* You can call this before starting a new series of performance tests.
|
|
*/
|
|
Astronomy.ResetPerformanceMetrics = function() {
|
|
Perf = new PerformanceInfo();
|
|
}
|
|
|
|
// END performance measurement
|
|
//========================================================================================
|
|
|
|
function Frac(x) {
|
|
return x - Math.floor(x);
|
|
}
|
|
|
|
/**
|
|
* Calculates the angle in degrees between two vectors.
|
|
* The angle is measured in the plane that contains both vectors.
|
|
*
|
|
* @param {Astronomy.Vector} a
|
|
* The first of a pair of vectors between which to measure an angle.
|
|
*
|
|
* @param {Astronomy.Vector} b
|
|
* The second of a pair of vectors between which to measure an angle.
|
|
*
|
|
* @returns {number}
|
|
* The angle between the two vectors expressed in degrees.
|
|
* The value is in the range [0, 180].
|
|
*/
|
|
function AngleBetween(a, b) {
|
|
const aa = (a.x*a.x + a.y*a.y + a.z*a.z);
|
|
if (Math.abs(aa) < 1.0e-8)
|
|
throw `AngleBetween: first vector is too short.`;
|
|
|
|
const bb = (b.x*b.x + b.y*b.y + b.z*b.z);
|
|
if (Math.abs(bb) < 1.0e-8)
|
|
throw `AngleBetween: second vector is too short.`;
|
|
|
|
const dot = (a.x*b.x + a.y*b.y + a.z*b.z) / Math.sqrt(aa * bb);
|
|
|
|
if (dot <= -1.0)
|
|
return 180;
|
|
|
|
if (dot >= +1.0)
|
|
return 0;
|
|
|
|
const angle = RAD2DEG * Math.acos(dot);
|
|
return angle;
|
|
}
|
|
|
|
/**
|
|
* @constant {string[]} Astronomy.Bodies
|
|
* An array of strings, each a name of a supported astronomical body.
|
|
* Not all bodies are valid for all functions, but any string not in this
|
|
* list is not supported at all.
|
|
*/
|
|
Astronomy.Bodies = [
|
|
'Sun',
|
|
'Moon',
|
|
'Mercury',
|
|
'Venus',
|
|
'Earth',
|
|
'Mars',
|
|
'Jupiter',
|
|
'Saturn',
|
|
'Uranus',
|
|
'Neptune',
|
|
'Pluto'
|
|
];
|
|
|
|
const Planet = {
|
|
Mercury: { OrbitalPeriod: 87.969 },
|
|
Venus: { OrbitalPeriod: 224.701 },
|
|
Earth: { OrbitalPeriod: 365.256 },
|
|
Mars: { OrbitalPeriod: 686.980 },
|
|
Jupiter: { OrbitalPeriod: 4332.589 },
|
|
Saturn: { OrbitalPeriod: 10759.22 },
|
|
Uranus: { OrbitalPeriod: 30685.4 },
|
|
Neptune: { OrbitalPeriod: 60189.0 },
|
|
Pluto: { OrbitalPeriod: 90560.0 }
|
|
};
|
|
|
|
const vsop = {
|
|
Mercury: [
|
|
[
|
|
[
|
|
[4.40250710144, 0.00000000000, 0.00000000000],
|
|
[0.40989414977, 1.48302034195, 26087.90314157420],
|
|
[0.05046294200, 4.47785489551, 52175.80628314840],
|
|
[0.00855346844, 1.16520322459, 78263.70942472259],
|
|
[0.00165590362, 4.11969163423, 104351.61256629678],
|
|
[0.00034561897, 0.77930768443, 130439.51570787099],
|
|
[0.00007583476, 3.71348404924, 156527.41884944518]
|
|
],
|
|
[
|
|
[26087.90313685529, 0.00000000000, 0.00000000000],
|
|
[0.01131199811, 6.21874197797, 26087.90314157420],
|
|
[0.00292242298, 3.04449355541, 52175.80628314840],
|
|
[0.00075775081, 6.08568821653, 78263.70942472259],
|
|
[0.00019676525, 2.80965111777, 104351.61256629678]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.11737528961, 1.98357498767, 26087.90314157420],
|
|
[0.02388076996, 5.03738959686, 52175.80628314840],
|
|
[0.01222839532, 3.14159265359, 0.00000000000],
|
|
[0.00543251810, 1.79644363964, 78263.70942472259],
|
|
[0.00129778770, 4.83232503958, 104351.61256629678],
|
|
[0.00031866927, 1.58088495658, 130439.51570787099],
|
|
[0.00007963301, 4.60972126127, 156527.41884944518]
|
|
],
|
|
[
|
|
[0.00274646065, 3.95008450011, 26087.90314157420],
|
|
[0.00099737713, 3.14159265359, 0.00000000000]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.39528271651, 0.00000000000, 0.00000000000],
|
|
[0.07834131818, 6.19233722598, 26087.90314157420],
|
|
[0.00795525558, 2.95989690104, 52175.80628314840],
|
|
[0.00121281764, 6.01064153797, 78263.70942472259],
|
|
[0.00021921969, 2.77820093972, 104351.61256629678],
|
|
[0.00004354065, 5.82894543774, 130439.51570787099]
|
|
],
|
|
[
|
|
[0.00217347740, 4.65617158665, 26087.90314157420],
|
|
[0.00044141826, 1.42385544001, 52175.80628314840]
|
|
]
|
|
]
|
|
],
|
|
Venus: [
|
|
[
|
|
[
|
|
[3.17614666774, 0.00000000000, 0.00000000000],
|
|
[0.01353968419, 5.59313319619, 10213.28554621100],
|
|
[0.00089891645, 5.30650047764, 20426.57109242200],
|
|
[0.00005477194, 4.41630661466, 7860.41939243920],
|
|
[0.00003455741, 2.69964447820, 11790.62908865880],
|
|
[0.00002372061, 2.99377542079, 3930.20969621960],
|
|
[0.00001317168, 5.18668228402, 26.29831979980],
|
|
[0.00001664146, 4.25018630147, 1577.34354244780],
|
|
[0.00001438387, 4.15745084182, 9683.59458111640],
|
|
[0.00001200521, 6.15357116043, 30639.85663863300]
|
|
],
|
|
[
|
|
[10213.28554621638, 0.00000000000, 0.00000000000],
|
|
[0.00095617813, 2.46406511110, 10213.28554621100],
|
|
[0.00007787201, 0.62478482220, 20426.57109242200]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.05923638472, 0.26702775812, 10213.28554621100],
|
|
[0.00040107978, 1.14737178112, 20426.57109242200],
|
|
[0.00032814918, 3.14159265359, 0.00000000000]
|
|
],
|
|
[
|
|
[0.00287821243, 1.88964962838, 10213.28554621100]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.72334820891, 0.00000000000, 0.00000000000],
|
|
[0.00489824182, 4.02151831717, 10213.28554621100],
|
|
[0.00001658058, 4.90206728031, 20426.57109242200]
|
|
],
|
|
[
|
|
[0.00034551041, 0.89198706276, 10213.28554621100]
|
|
]
|
|
]
|
|
],
|
|
Earth: [
|
|
[
|
|
[
|
|
[1.75347045673, 0.00000000000, 0.00000000000],
|
|
[0.03341656453, 4.66925680415, 6283.07584999140],
|
|
[0.00034894275, 4.62610242189, 12566.15169998280],
|
|
[0.00003417572, 2.82886579754, 3.52311834900],
|
|
[0.00003497056, 2.74411783405, 5753.38488489680],
|
|
[0.00003135899, 3.62767041756, 77713.77146812050],
|
|
[0.00002676218, 4.41808345438, 7860.41939243920],
|
|
[0.00002342691, 6.13516214446, 3930.20969621960],
|
|
[0.00001273165, 2.03709657878, 529.69096509460],
|
|
[0.00001324294, 0.74246341673, 11506.76976979360],
|
|
[0.00000901854, 2.04505446477, 26.29831979980],
|
|
[0.00001199167, 1.10962946234, 1577.34354244780],
|
|
[0.00000857223, 3.50849152283, 398.14900340820],
|
|
[0.00000779786, 1.17882681962, 5223.69391980220],
|
|
[0.00000990250, 5.23268072088, 5884.92684658320],
|
|
[0.00000753141, 2.53339052847, 5507.55323866740],
|
|
[0.00000505267, 4.58292599973, 18849.22754997420],
|
|
[0.00000492392, 4.20505711826, 775.52261132400],
|
|
[0.00000356672, 2.91954114478, 0.06731030280],
|
|
[0.00000284125, 1.89869240932, 796.29800681640],
|
|
[0.00000242879, 0.34481445893, 5486.77784317500],
|
|
[0.00000317087, 5.84901948512, 11790.62908865880],
|
|
[0.00000271112, 0.31486255375, 10977.07880469900],
|
|
[0.00000206217, 4.80646631478, 2544.31441988340],
|
|
[0.00000205478, 1.86953770281, 5573.14280143310],
|
|
[0.00000202318, 2.45767790232, 6069.77675455340],
|
|
[0.00000126225, 1.08295459501, 20.77539549240],
|
|
[0.00000155516, 0.83306084617, 213.29909543800]
|
|
],
|
|
[
|
|
[6283.07584999140, 0.00000000000, 0.00000000000],
|
|
[0.00206058863, 2.67823455808, 6283.07584999140],
|
|
[0.00004303419, 2.63512233481, 12566.15169998280]
|
|
],
|
|
[
|
|
[0.00008721859, 1.07253635559, 6283.07584999140]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
],
|
|
[
|
|
[0.00227777722, 3.41376620530, 6283.07584999140],
|
|
[0.00003805678, 3.37063423795, 12566.15169998280]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[1.00013988784, 0.00000000000, 0.00000000000],
|
|
[0.01670699632, 3.09846350258, 6283.07584999140],
|
|
[0.00013956024, 3.05524609456, 12566.15169998280],
|
|
[0.00003083720, 5.19846674381, 77713.77146812050],
|
|
[0.00001628463, 1.17387558054, 5753.38488489680],
|
|
[0.00001575572, 2.84685214877, 7860.41939243920],
|
|
[0.00000924799, 5.45292236722, 11506.76976979360],
|
|
[0.00000542439, 4.56409151453, 3930.20969621960],
|
|
[0.00000472110, 3.66100022149, 5884.92684658320]
|
|
],
|
|
[
|
|
[0.00103018607, 1.10748968172, 6283.07584999140],
|
|
[0.00001721238, 1.06442300386, 12566.15169998280]
|
|
],
|
|
[
|
|
[0.00004359385, 5.78455133808, 6283.07584999140]
|
|
]
|
|
]
|
|
],
|
|
Mars: [
|
|
[
|
|
[
|
|
[6.20347711581, 0.00000000000, 0.00000000000],
|
|
[0.18656368093, 5.05037100270, 3340.61242669980],
|
|
[0.01108216816, 5.40099836344, 6681.22485339960],
|
|
[0.00091798406, 5.75478744667, 10021.83728009940],
|
|
[0.00027744987, 5.97049513147, 3.52311834900],
|
|
[0.00010610235, 2.93958560338, 2281.23049651060],
|
|
[0.00012315897, 0.84956094002, 2810.92146160520],
|
|
[0.00008926784, 4.15697846427, 0.01725365220],
|
|
[0.00008715691, 6.11005153139, 13362.44970679920],
|
|
[0.00006797556, 0.36462229657, 398.14900340820],
|
|
[0.00007774872, 3.33968761376, 5621.84292321040],
|
|
[0.00003575078, 1.66186505710, 2544.31441988340],
|
|
[0.00004161108, 0.22814971327, 2942.46342329160],
|
|
[0.00003075252, 0.85696614132, 191.44826611160],
|
|
[0.00002628117, 0.64806124465, 3337.08930835080],
|
|
[0.00002937546, 6.07893711402, 0.06731030280],
|
|
[0.00002389414, 5.03896442664, 796.29800681640],
|
|
[0.00002579844, 0.02996736156, 3344.13554504880],
|
|
[0.00001528141, 1.14979301996, 6151.53388830500],
|
|
[0.00001798806, 0.65634057445, 529.69096509460],
|
|
[0.00001264357, 3.62275122593, 5092.15195811580],
|
|
[0.00001286228, 3.06796065034, 2146.16541647520],
|
|
[0.00001546404, 2.91579701718, 1751.53953141600],
|
|
[0.00001024902, 3.69334099279, 8962.45534991020],
|
|
[0.00000891566, 0.18293837498, 16703.06213349900],
|
|
[0.00000858759, 2.40093811940, 2914.01423582380],
|
|
[0.00000832715, 2.46418619474, 3340.59517304760],
|
|
[0.00000832720, 4.49495782139, 3340.62968035200],
|
|
[0.00000712902, 3.66335473479, 1059.38193018920],
|
|
[0.00000748723, 3.82248614017, 155.42039943420],
|
|
[0.00000723861, 0.67497311481, 3738.76143010800],
|
|
[0.00000635548, 2.92182225127, 8432.76438481560],
|
|
[0.00000655162, 0.48864064125, 3127.31333126180],
|
|
[0.00000550474, 3.81001042328, 0.98032106820],
|
|
[0.00000552750, 4.47479317037, 1748.01641306700],
|
|
[0.00000425966, 0.55364317304, 6283.07584999140],
|
|
[0.00000415131, 0.49662285038, 213.29909543800],
|
|
[0.00000472167, 3.62547124025, 1194.44701022460],
|
|
[0.00000306551, 0.38052848348, 6684.74797174860],
|
|
[0.00000312141, 0.99853944405, 6677.70173505060],
|
|
[0.00000293198, 4.22131299634, 20.77539549240],
|
|
[0.00000302375, 4.48618007156, 3532.06069281140],
|
|
[0.00000274027, 0.54222167059, 3340.54511639700],
|
|
[0.00000281079, 5.88163521788, 1349.86740965880],
|
|
[0.00000231183, 1.28242156993, 3870.30339179440],
|
|
[0.00000283602, 5.76885434940, 3149.16416058820],
|
|
[0.00000236117, 5.75503217933, 3333.49887969900],
|
|
[0.00000274033, 0.13372524985, 3340.67973700260],
|
|
[0.00000299395, 2.78323740866, 6254.62666252360]
|
|
],
|
|
[
|
|
[3340.61242700512, 0.00000000000, 0.00000000000],
|
|
[0.01457554523, 3.60433733236, 3340.61242669980],
|
|
[0.00168414711, 3.92318567804, 6681.22485339960],
|
|
[0.00020622975, 4.26108844583, 10021.83728009940],
|
|
[0.00003452392, 4.73210393190, 3.52311834900],
|
|
[0.00002586332, 4.60670058555, 13362.44970679920],
|
|
[0.00000841535, 4.45864030426, 2281.23049651060]
|
|
],
|
|
[
|
|
[0.00058152577, 2.04961712429, 3340.61242669980],
|
|
[0.00013459579, 2.45738706163, 6681.22485339960]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.03197134986, 3.76832042431, 3340.61242669980],
|
|
[0.00298033234, 4.10616996305, 6681.22485339960],
|
|
[0.00289104742, 0.00000000000, 0.00000000000],
|
|
[0.00031365539, 4.44651053090, 10021.83728009940],
|
|
[0.00003484100, 4.78812549260, 13362.44970679920]
|
|
],
|
|
[
|
|
[0.00217310991, 6.04472194776, 3340.61242669980],
|
|
[0.00020976948, 3.14159265359, 0.00000000000],
|
|
[0.00012834709, 1.60810667915, 6681.22485339960]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[1.53033488271, 0.00000000000, 0.00000000000],
|
|
[0.14184953160, 3.47971283528, 3340.61242669980],
|
|
[0.00660776362, 3.81783443019, 6681.22485339960],
|
|
[0.00046179117, 4.15595316782, 10021.83728009940],
|
|
[0.00008109733, 5.55958416318, 2810.92146160520],
|
|
[0.00007485318, 1.77239078402, 5621.84292321040],
|
|
[0.00005523191, 1.36436303770, 2281.23049651060],
|
|
[0.00003825160, 4.49407183687, 13362.44970679920],
|
|
[0.00002306537, 0.09081579001, 2544.31441988340],
|
|
[0.00001999396, 5.36059617709, 3337.08930835080],
|
|
[0.00002484394, 4.92545639920, 2942.46342329160],
|
|
[0.00001960195, 4.74249437639, 3344.13554504880],
|
|
[0.00001167119, 2.11260868341, 5092.15195811580],
|
|
[0.00001102816, 5.00908403998, 398.14900340820],
|
|
[0.00000899066, 4.40791133207, 529.69096509460],
|
|
[0.00000992252, 5.83861961952, 6151.53388830500],
|
|
[0.00000807354, 2.10217065501, 1059.38193018920],
|
|
[0.00000797915, 3.44839203899, 796.29800681640],
|
|
[0.00000740975, 1.49906336885, 2146.16541647520]
|
|
],
|
|
[
|
|
[0.01107433345, 2.03250524857, 3340.61242669980],
|
|
[0.00103175887, 2.37071847807, 6681.22485339960],
|
|
[0.00012877200, 0.00000000000, 0.00000000000],
|
|
[0.00010815880, 2.70888095665, 10021.83728009940]
|
|
],
|
|
[
|
|
[0.00044242249, 0.47930604954, 3340.61242669980],
|
|
[0.00008138042, 0.86998389204, 6681.22485339960]
|
|
]
|
|
]
|
|
],
|
|
Jupiter: [
|
|
[
|
|
[
|
|
[0.59954691494, 0.00000000000, 0.00000000000],
|
|
[0.09695898719, 5.06191793158, 529.69096509460],
|
|
[0.00573610142, 1.44406205629, 7.11354700080],
|
|
[0.00306389205, 5.41734730184, 1059.38193018920],
|
|
[0.00097178296, 4.14264726552, 632.78373931320],
|
|
[0.00072903078, 3.64042916389, 522.57741809380],
|
|
[0.00064263975, 3.41145165351, 103.09277421860],
|
|
[0.00039806064, 2.29376740788, 419.48464387520],
|
|
[0.00038857767, 1.27231755835, 316.39186965660],
|
|
[0.00027964629, 1.78454591820, 536.80451209540],
|
|
[0.00013589730, 5.77481040790, 1589.07289528380],
|
|
[0.00008246349, 3.58227925840, 206.18554843720],
|
|
[0.00008768704, 3.63000308199, 949.17560896980],
|
|
[0.00007368042, 5.08101194270, 735.87651353180],
|
|
[0.00006263150, 0.02497628807, 213.29909543800],
|
|
[0.00006114062, 4.51319998626, 1162.47470440780],
|
|
[0.00004905396, 1.32084470588, 110.20632121940],
|
|
[0.00005305285, 1.30671216791, 14.22709400160],
|
|
[0.00005305441, 4.18625634012, 1052.26838318840],
|
|
[0.00004647248, 4.69958103684, 3.93215326310],
|
|
[0.00003045023, 4.31676431084, 426.59819087600],
|
|
[0.00002609999, 1.56667394063, 846.08283475120],
|
|
[0.00002028191, 1.06376530715, 3.18139373770],
|
|
[0.00001764763, 2.14148655117, 1066.49547719000],
|
|
[0.00001722972, 3.88036268267, 1265.56747862640],
|
|
[0.00001920945, 0.97168196472, 639.89728631400],
|
|
[0.00001633223, 3.58201833555, 515.46387109300],
|
|
[0.00001431999, 4.29685556046, 625.67019231240],
|
|
[0.00000973272, 4.09764549134, 95.97922721780]
|
|
],
|
|
[
|
|
[529.69096508814, 0.00000000000, 0.00000000000],
|
|
[0.00489503243, 4.22082939470, 529.69096509460],
|
|
[0.00228917222, 6.02646855621, 7.11354700080],
|
|
[0.00030099479, 4.54540782858, 1059.38193018920],
|
|
[0.00020720920, 5.45943156902, 522.57741809380],
|
|
[0.00012103653, 0.16994816098, 536.80451209540],
|
|
[0.00006067987, 4.42422292017, 103.09277421860],
|
|
[0.00005433968, 3.98480737746, 419.48464387520],
|
|
[0.00004237744, 5.89008707199, 14.22709400160]
|
|
],
|
|
[
|
|
[0.00047233601, 4.32148536482, 7.11354700080],
|
|
[0.00030649436, 2.92977788700, 529.69096509460],
|
|
[0.00014837605, 3.14159265359, 0.00000000000]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.02268615702, 3.55852606721, 529.69096509460],
|
|
[0.00109971634, 3.90809347197, 1059.38193018920],
|
|
[0.00110090358, 0.00000000000, 0.00000000000],
|
|
[0.00008101428, 3.60509572885, 522.57741809380],
|
|
[0.00006043996, 4.25883108339, 1589.07289528380],
|
|
[0.00006437782, 0.30627119215, 536.80451209540]
|
|
],
|
|
[
|
|
[0.00078203446, 1.52377859742, 529.69096509460]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[5.20887429326, 0.00000000000, 0.00000000000],
|
|
[0.25209327119, 3.49108639871, 529.69096509460],
|
|
[0.00610599976, 3.84115365948, 1059.38193018920],
|
|
[0.00282029458, 2.57419881293, 632.78373931320],
|
|
[0.00187647346, 2.07590383214, 522.57741809380],
|
|
[0.00086792905, 0.71001145545, 419.48464387520],
|
|
[0.00072062974, 0.21465724607, 536.80451209540],
|
|
[0.00065517248, 5.97995884790, 316.39186965660],
|
|
[0.00029134542, 1.67759379655, 103.09277421860],
|
|
[0.00030135335, 2.16132003734, 949.17560896980],
|
|
[0.00023453271, 3.54023522184, 735.87651353180],
|
|
[0.00022283743, 4.19362594399, 1589.07289528380],
|
|
[0.00023947298, 0.27458037480, 7.11354700080],
|
|
[0.00013032614, 2.96042965363, 1162.47470440780],
|
|
[0.00009703360, 1.90669633585, 206.18554843720],
|
|
[0.00012749023, 2.71550286592, 1052.26838318840]
|
|
],
|
|
[
|
|
[0.01271801520, 2.64937512894, 529.69096509460],
|
|
[0.00061661816, 3.00076460387, 1059.38193018920],
|
|
[0.00053443713, 3.89717383175, 522.57741809380],
|
|
[0.00031185171, 4.88276958012, 536.80451209540],
|
|
[0.00041390269, 0.00000000000, 0.00000000000]
|
|
]
|
|
]
|
|
],
|
|
Saturn: [
|
|
[
|
|
[
|
|
[0.87401354025, 0.00000000000, 0.00000000000],
|
|
[0.11107659762, 3.96205090159, 213.29909543800],
|
|
[0.01414150957, 4.58581516874, 7.11354700080],
|
|
[0.00398379389, 0.52112032699, 206.18554843720],
|
|
[0.00350769243, 3.30329907896, 426.59819087600],
|
|
[0.00206816305, 0.24658372002, 103.09277421860],
|
|
[0.00079271300, 3.84007056878, 220.41264243880],
|
|
[0.00023990355, 4.66976924553, 110.20632121940],
|
|
[0.00016573588, 0.43719228296, 419.48464387520],
|
|
[0.00014906995, 5.76903183869, 316.39186965660],
|
|
[0.00015820290, 0.93809155235, 632.78373931320],
|
|
[0.00014609559, 1.56518472000, 3.93215326310],
|
|
[0.00013160301, 4.44891291899, 14.22709400160],
|
|
[0.00015053543, 2.71669915667, 639.89728631400],
|
|
[0.00013005299, 5.98119023644, 11.04570026390],
|
|
[0.00010725067, 3.12939523827, 202.25339517410],
|
|
[0.00005863206, 0.23656938524, 529.69096509460],
|
|
[0.00005227757, 4.20783365759, 3.18139373770],
|
|
[0.00006126317, 1.76328667907, 277.03499374140],
|
|
[0.00005019687, 3.17787728405, 433.71173787680],
|
|
[0.00004592550, 0.61977744975, 199.07200143640],
|
|
[0.00004005867, 2.24479718502, 63.73589830340],
|
|
[0.00002953796, 0.98280366998, 95.97922721780],
|
|
[0.00003873670, 3.22283226966, 138.51749687070],
|
|
[0.00002461186, 2.03163875071, 735.87651353180],
|
|
[0.00003269484, 0.77492638211, 949.17560896980],
|
|
[0.00001758145, 3.26580109940, 522.57741809380],
|
|
[0.00001640172, 5.50504453050, 846.08283475120],
|
|
[0.00001391327, 4.02333150505, 323.50541665740],
|
|
[0.00001580648, 4.37265307169, 309.27832265580],
|
|
[0.00001123498, 2.83726798446, 415.55249061210],
|
|
[0.00001017275, 3.71700135395, 227.52618943960],
|
|
[0.00000848642, 3.19150170830, 209.36694217490]
|
|
],
|
|
[
|
|
[213.29909521690, 0.00000000000, 0.00000000000],
|
|
[0.01297370862, 1.82834923978, 213.29909543800],
|
|
[0.00564345393, 2.88499717272, 7.11354700080],
|
|
[0.00093734369, 1.06311793502, 426.59819087600],
|
|
[0.00107674962, 2.27769131009, 206.18554843720],
|
|
[0.00040244455, 2.04108104671, 220.41264243880],
|
|
[0.00019941774, 1.27954390470, 103.09277421860],
|
|
[0.00010511678, 2.74880342130, 14.22709400160],
|
|
[0.00006416106, 0.38238295041, 639.89728631400],
|
|
[0.00004848994, 2.43037610229, 419.48464387520],
|
|
[0.00004056892, 2.92133209468, 110.20632121940],
|
|
[0.00003768635, 3.64965330780, 3.93215326310]
|
|
],
|
|
[
|
|
[0.00116441330, 1.17988132879, 7.11354700080],
|
|
[0.00091841837, 0.07325195840, 213.29909543800],
|
|
[0.00036661728, 0.00000000000, 0.00000000000],
|
|
[0.00015274496, 4.06493179167, 206.18554843720]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.04330678039, 3.60284428399, 213.29909543800],
|
|
[0.00240348302, 2.85238489373, 426.59819087600],
|
|
[0.00084745939, 0.00000000000, 0.00000000000],
|
|
[0.00030863357, 3.48441504555, 220.41264243880],
|
|
[0.00034116062, 0.57297307557, 206.18554843720],
|
|
[0.00014734070, 2.11846596715, 639.89728631400],
|
|
[0.00009916667, 5.79003188904, 419.48464387520],
|
|
[0.00006993564, 4.73604689720, 7.11354700080],
|
|
[0.00004807588, 5.43305312061, 316.39186965660]
|
|
],
|
|
[
|
|
[0.00198927992, 4.93901017903, 213.29909543800],
|
|
[0.00036947916, 3.14159265359, 0.00000000000],
|
|
[0.00017966989, 0.51979431110, 426.59819087600]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[9.55758135486, 0.00000000000, 0.00000000000],
|
|
[0.52921382865, 2.39226219573, 213.29909543800],
|
|
[0.01873679867, 5.23549604660, 206.18554843720],
|
|
[0.01464663929, 1.64763042902, 426.59819087600],
|
|
[0.00821891141, 5.93520042303, 316.39186965660],
|
|
[0.00547506923, 5.01532618980, 103.09277421860],
|
|
[0.00371684650, 2.27114821115, 220.41264243880],
|
|
[0.00361778765, 3.13904301847, 7.11354700080],
|
|
[0.00140617506, 5.70406606781, 632.78373931320],
|
|
[0.00108974848, 3.29313390175, 110.20632121940],
|
|
[0.00069006962, 5.94099540992, 419.48464387520],
|
|
[0.00061053367, 0.94037691801, 639.89728631400],
|
|
[0.00048913294, 1.55733638681, 202.25339517410],
|
|
[0.00034143772, 0.19519102597, 277.03499374140],
|
|
[0.00032401773, 5.47084567016, 949.17560896980],
|
|
[0.00020936596, 0.46349251129, 735.87651353180]
|
|
],
|
|
[
|
|
[0.06182981340, 0.25843511480, 213.29909543800],
|
|
[0.00506577242, 0.71114625261, 206.18554843720],
|
|
[0.00341394029, 5.79635741658, 426.59819087600],
|
|
[0.00188491195, 0.47215589652, 220.41264243880],
|
|
[0.00186261486, 3.14159265359, 0.00000000000],
|
|
[0.00143891146, 1.40744822888, 7.11354700080]
|
|
],
|
|
[
|
|
[0.00436902572, 4.78671677509, 213.29909543800]
|
|
]
|
|
]
|
|
],
|
|
Uranus: [
|
|
[
|
|
[
|
|
[5.48129294297, 0.00000000000, 0.00000000000],
|
|
[0.09260408234, 0.89106421507, 74.78159856730],
|
|
[0.01504247898, 3.62719260920, 1.48447270830],
|
|
[0.00365981674, 1.89962179044, 73.29712585900],
|
|
[0.00272328168, 3.35823706307, 149.56319713460],
|
|
[0.00070328461, 5.39254450063, 63.73589830340],
|
|
[0.00068892678, 6.09292483287, 76.26607127560],
|
|
[0.00061998615, 2.26952066061, 2.96894541660],
|
|
[0.00061950719, 2.85098872691, 11.04570026390],
|
|
[0.00026468770, 3.14152083966, 71.81265315070],
|
|
[0.00025710476, 6.11379840493, 454.90936652730],
|
|
[0.00021078850, 4.36059339067, 148.07872442630],
|
|
[0.00017818647, 1.74436930289, 36.64856292950],
|
|
[0.00014613507, 4.73732166022, 3.93215326310],
|
|
[0.00011162509, 5.82681796350, 224.34479570190],
|
|
[0.00010997910, 0.48865004018, 138.51749687070],
|
|
[0.00009527478, 2.95516862826, 35.16409022120],
|
|
[0.00007545601, 5.23626582400, 109.94568878850],
|
|
[0.00004220241, 3.23328220918, 70.84944530420],
|
|
[0.00004051900, 2.27755017300, 151.04766984290],
|
|
[0.00003354596, 1.06549007380, 4.45341812490],
|
|
[0.00002926718, 4.62903718891, 9.56122755560],
|
|
[0.00003490340, 5.48306144511, 146.59425171800],
|
|
[0.00003144069, 4.75199570434, 77.75054398390],
|
|
[0.00002922333, 5.35235361027, 85.82729883120],
|
|
[0.00002272788, 4.36600400036, 70.32818044240],
|
|
[0.00002051219, 1.51773566586, 0.11187458460],
|
|
[0.00002148602, 0.60745949945, 38.13303563780],
|
|
[0.00001991643, 4.92437588682, 277.03499374140],
|
|
[0.00001376226, 2.04283539351, 65.22037101170],
|
|
[0.00001666902, 3.62744066769, 380.12776796000],
|
|
[0.00001284107, 3.11347961505, 202.25339517410],
|
|
[0.00001150429, 0.93343589092, 3.18139373770],
|
|
[0.00001533221, 2.58594681212, 52.69019803950],
|
|
[0.00001281604, 0.54271272721, 222.86032299360],
|
|
[0.00001372139, 4.19641530878, 111.43016149680],
|
|
[0.00001221029, 0.19900650030, 108.46121608020],
|
|
[0.00000946181, 1.19253165736, 127.47179660680],
|
|
[0.00001150989, 4.17898916639, 33.67961751290]
|
|
],
|
|
[
|
|
[74.78159860910, 0.00000000000, 0.00000000000],
|
|
[0.00154332863, 5.24158770553, 74.78159856730],
|
|
[0.00024456474, 1.71260334156, 1.48447270830],
|
|
[0.00009258442, 0.42829732350, 11.04570026390],
|
|
[0.00008265977, 1.50218091379, 63.73589830340],
|
|
[0.00009150160, 1.41213765216, 149.56319713460]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.01346277648, 2.61877810547, 74.78159856730],
|
|
[0.00062341400, 5.08111189648, 149.56319713460],
|
|
[0.00061601196, 3.14159265359, 0.00000000000],
|
|
[0.00009963722, 1.61603805646, 76.26607127560],
|
|
[0.00009926160, 0.57630380333, 73.29712585900]
|
|
],
|
|
[
|
|
[0.00034101978, 0.01321929936, 74.78159856730]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[19.21264847206, 0.00000000000, 0.00000000000],
|
|
[0.88784984413, 5.60377527014, 74.78159856730],
|
|
[0.03440836062, 0.32836099706, 73.29712585900],
|
|
[0.02055653860, 1.78295159330, 149.56319713460],
|
|
[0.00649322410, 4.52247285911, 76.26607127560],
|
|
[0.00602247865, 3.86003823674, 63.73589830340],
|
|
[0.00496404167, 1.40139935333, 454.90936652730],
|
|
[0.00338525369, 1.58002770318, 138.51749687070],
|
|
[0.00243509114, 1.57086606044, 71.81265315070],
|
|
[0.00190522303, 1.99809394714, 1.48447270830],
|
|
[0.00161858838, 2.79137786799, 148.07872442630],
|
|
[0.00143706183, 1.38368544947, 11.04570026390],
|
|
[0.00093192405, 0.17437220467, 36.64856292950],
|
|
[0.00071424548, 4.24509236074, 224.34479570190],
|
|
[0.00089806014, 3.66105364565, 109.94568878850],
|
|
[0.00039009723, 1.66971401684, 70.84944530420],
|
|
[0.00046677296, 1.39976401694, 35.16409022120],
|
|
[0.00039025624, 3.36234773834, 277.03499374140],
|
|
[0.00036755274, 3.88649278513, 146.59425171800],
|
|
[0.00030348723, 0.70100838798, 151.04766984290],
|
|
[0.00029156413, 3.18056336700, 77.75054398390]
|
|
],
|
|
[
|
|
[0.01479896629, 3.67205697578, 74.78159856730]
|
|
]
|
|
]
|
|
],
|
|
Neptune: [
|
|
[
|
|
[
|
|
[5.31188633046, 0.00000000000, 0.00000000000],
|
|
[0.01798475530, 2.90101273890, 38.13303563780],
|
|
[0.01019727652, 0.48580922867, 1.48447270830],
|
|
[0.00124531845, 4.83008090676, 36.64856292950],
|
|
[0.00042064466, 5.41054993053, 2.96894541660],
|
|
[0.00037714584, 6.09221808686, 35.16409022120],
|
|
[0.00033784738, 1.24488874087, 76.26607127560],
|
|
[0.00016482741, 0.00007727998, 491.55792945680],
|
|
[0.00009198584, 4.93747051954, 39.61750834610],
|
|
[0.00008994250, 0.27462171806, 175.16605980020]
|
|
],
|
|
[
|
|
[38.13303563957, 0.00000000000, 0.00000000000],
|
|
[0.00016604172, 4.86323329249, 1.48447270830],
|
|
[0.00015744045, 2.27887427527, 38.13303563780]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[0.03088622933, 1.44104372644, 38.13303563780],
|
|
[0.00027780087, 5.91271884599, 76.26607127560],
|
|
[0.00027623609, 0.00000000000, 0.00000000000],
|
|
[0.00015355489, 2.52123799551, 36.64856292950],
|
|
[0.00015448133, 3.50877079215, 39.61750834610]
|
|
]
|
|
],
|
|
[
|
|
[
|
|
[30.07013205828, 0.00000000000, 0.00000000000],
|
|
[0.27062259632, 1.32999459377, 38.13303563780],
|
|
[0.01691764014, 3.25186135653, 36.64856292950],
|
|
[0.00807830553, 5.18592878704, 1.48447270830],
|
|
[0.00537760510, 4.52113935896, 35.16409022120],
|
|
[0.00495725141, 1.57105641650, 491.55792945680],
|
|
[0.00274571975, 1.84552258866, 175.16605980020]
|
|
]
|
|
]
|
|
]
|
|
};
|
|
|
|
const cheb = {
|
|
Pluto: [
|
|
{ 'tt':-109573.500000, 'ndays':26141.000000, 'coeff':[
|
|
[-30.303124711144, -18.980368465705, 3.206649343866],
|
|
[20.092745278347, -27.533908687219, -14.641121965990],
|
|
[9.137264744925, 6.513103657467, -0.720732357468],
|
|
[-1.201554708717, 2.149917852301, 1.032022293526],
|
|
[-0.566068170022, -0.285737361191, 0.081379987808],
|
|
[0.041678527795, -0.143363105040, -0.057534475984],
|
|
[0.041087908142, 0.007911321580, -0.010270655537],
|
|
[0.001611769878, 0.011409821837, 0.003679980733],
|
|
[-0.002536458296, -0.000145632543, 0.000949924030],
|
|
[0.001167651969, -0.000049912680, 0.000115867710],
|
|
[-0.000196953286, 0.000420406270, 0.000110147171],
|
|
[0.001073825784, 0.000442658285, 0.000146985332],
|
|
[-0.000906160087, 0.001702360394, 0.000758987924],
|
|
[-0.001467464335, -0.000622191266, -0.000231866243],
|
|
[-0.000008986691, 0.000004086384, 0.000001442956],
|
|
[-0.001099078039, -0.000544633529, -0.000205534708],
|
|
[0.001259974751, -0.002178533187, -0.000965315934],
|
|
[0.001695288316, 0.000768480768, 0.000287916141],
|
|
[-0.001428026702, 0.002707551594, 0.001195955756]]
|
|
},
|
|
{ 'tt':-83432.500000, 'ndays':26141.000000, 'coeff':[
|
|
[67.049456204563, -9.279626603192, -23.091941092128],
|
|
[14.860676672314, 26.594121136143, 3.819668867047],
|
|
[-6.254409044120, 1.408757903538, 2.323726101433],
|
|
[0.114416381092, -0.942273228585, -0.328566335886],
|
|
[0.074973631246, 0.106749156044, 0.010806547171],
|
|
[-0.018627741964, -0.009983491157, 0.002589955906],
|
|
[0.006167206174, -0.001042430439, -0.001521881831],
|
|
[-0.000471293617, 0.002337935239, 0.001060879763],
|
|
[-0.000240627462, -0.001380351742, -0.000546042590],
|
|
[0.001872140444, 0.000679876620, 0.000240384842],
|
|
[-0.000334705177, 0.000693528330, 0.000301138309],
|
|
[0.000796124758, 0.000653183163, 0.000259527079],
|
|
[-0.001276116664, 0.001393959948, 0.000629574865],
|
|
[-0.001235158458, -0.000889985319, -0.000351392687],
|
|
[-0.000019881944, 0.000048339979, 0.000021342186],
|
|
[-0.000987113745, -0.000748420747, -0.000296503569],
|
|
[0.001721891782, -0.001893675502, -0.000854270937],
|
|
[0.001505145187, 0.001081653337, 0.000426723640],
|
|
[-0.002019479384, 0.002375617497, 0.001068258925]]
|
|
},
|
|
{ 'tt':-57291.500000, 'ndays':26141.000000, 'coeff':[
|
|
[46.038290912405, 73.773759757856, 9.148670950706],
|
|
[-22.354364534703, 10.217143138926, 9.921247676076],
|
|
[-2.696282001399, -4.440843715929, -0.572373037840],
|
|
[0.385475818800, -0.287872688575, -0.205914693555],
|
|
[0.020994433095, 0.004256602589, -0.004817361041],
|
|
[0.003212255378, 0.000574875698, -0.000764464370],
|
|
[-0.000158619286, -0.001035559544, -0.000535612316],
|
|
[0.000967952107, -0.000653111849, -0.000292019750],
|
|
[0.001763494906, -0.000370815938, -0.000224698363],
|
|
[0.001157990330, 0.001849810828, 0.000759641577],
|
|
[-0.000883535516, 0.000384038162, 0.000191242192],
|
|
[0.000709486562, 0.000655810827, 0.000265431131],
|
|
[-0.001525810419, 0.001126870468, 0.000520202001],
|
|
[-0.000983210860, -0.001116073455, -0.000456026382],
|
|
[-0.000015655450, 0.000069184008, 0.000029796623],
|
|
[-0.000815102021, -0.000900597010, -0.000365274209],
|
|
[0.002090300438, -0.001536778673, -0.000709827438],
|
|
[0.001234661297, 0.001342978436, 0.000545313112],
|
|
[-0.002517963678, 0.001941826791, 0.000893859860]]
|
|
},
|
|
{ 'tt':-31150.500000, 'ndays':26141.000000, 'coeff':[
|
|
[-39.074661990988, 30.963513412373, 21.431709298065],
|
|
[-12.033639281924, -31.693679132310, -6.263961539568],
|
|
[7.233936758611, -3.979157072767, -3.421027935569],
|
|
[1.383182539917, 1.090729793400, -0.076771771448],
|
|
[-0.009894394996, 0.313614402007, 0.101180677344],
|
|
[-0.055459383449, 0.031782406403, 0.026374448864],
|
|
[-0.011074105991, -0.007176759494, 0.001896208351],
|
|
[-0.000263363398, -0.001145329444, 0.000215471838],
|
|
[0.000405700185, -0.000839229891, -0.000418571366],
|
|
[0.001004921401, 0.001135118493, 0.000406734549],
|
|
[-0.000473938695, 0.000282751002, 0.000114911593],
|
|
[0.000528685886, 0.000966635293, 0.000401955197],
|
|
[-0.001838869845, 0.000806432189, 0.000394594478],
|
|
[-0.000713122169, -0.001334810971, -0.000554511235],
|
|
[0.000006449359, 0.000060730000, 0.000024513230],
|
|
[-0.000596025142, -0.000999492770, -0.000413930406],
|
|
[0.002364904429, -0.001099236865, -0.000528480902],
|
|
[0.000907458104, 0.001537243912, 0.000637001965],
|
|
[-0.002909908764, 0.001413648354, 0.000677030924]]
|
|
},
|
|
{ 'tt':-5009.500000, 'ndays':26141.000000, 'coeff':[
|
|
[23.380075041204, -38.969338804442, -19.204762094135],
|
|
[33.437140696536, 8.735194448531, -7.348352917314],
|
|
[-3.127251304544, 8.324311848708, 3.540122328502],
|
|
[-1.491354030154, -1.350371407475, 0.028214278544],
|
|
[0.361398480996, -0.118420687058, -0.145375605480],
|
|
[-0.011771350229, 0.085880588309, 0.030665997197],
|
|
[-0.015839541688, -0.014165128211, 0.000523465951],
|
|
[0.004213218926, -0.001426373728, -0.001906412496],
|
|
[0.001465150002, 0.000451513538, 0.000081936194],
|
|
[0.000640069511, 0.001886692235, 0.000884675556],
|
|
[-0.000883554940, 0.000301907356, 0.000127310183],
|
|
[0.000245524038, 0.000910362686, 0.000385555148],
|
|
[-0.001942010476, 0.000438682280, 0.000237124027],
|
|
[-0.000425455660, -0.001442138768, -0.000607751390],
|
|
[0.000004168433, 0.000033856562, 0.000013881811],
|
|
[-0.000337920193, -0.001074290356, -0.000452503056],
|
|
[0.002544755354, -0.000620356219, -0.000327246228],
|
|
[0.000534534110, 0.001670320887, 0.000702775941],
|
|
[-0.003169380270, 0.000816186705, 0.000427213817]]
|
|
},
|
|
{ 'tt':21131.500000, 'ndays':26141.000000, 'coeff':[
|
|
[74.130449310804, 43.372111541004, -8.799489207171],
|
|
[-8.705941488523, 23.344631690845, 9.908006472122],
|
|
[-4.614752911564, -2.587334376729, 0.583321715294],
|
|
[0.316219286624, -0.395448970181, -0.219217574801],
|
|
[0.004593734664, 0.027528474371, 0.007736197280],
|
|
[-0.001192268851, -0.004987723997, -0.001599399192],
|
|
[0.003051998429, -0.001287028653, -0.000780744058],
|
|
[0.001482572043, 0.001613554244, 0.000635747068],
|
|
[0.000581965277, 0.000788286674, 0.000315285159],
|
|
[-0.000311830730, 0.001622369930, 0.000714817617],
|
|
[-0.000711275723, -0.000160014561, -0.000050445901],
|
|
[0.000177159088, 0.001032713853, 0.000435835541],
|
|
[-0.002032280820, 0.000144281331, 0.000111910344],
|
|
[-0.000148463759, -0.001495212309, -0.000635892081],
|
|
[-0.000009629403, -0.000013678407, -0.000006187457],
|
|
[-0.000061196084, -0.001119783520, -0.000479221572],
|
|
[0.002630993795, -0.000113042927, -0.000112115452],
|
|
[0.000132867113, 0.001741417484, 0.000743224630],
|
|
[-0.003293498893, 0.000182437998, 0.000158073228]]
|
|
},
|
|
{ 'tt':47272.500000, 'ndays':26141.000000, 'coeff':[
|
|
[-5.727994625506, 71.194823351703, 23.946198176031],
|
|
[-26.767323214686, -12.264949302780, 4.238297122007],
|
|
[0.890596204250, -5.970227904551, -2.131444078785],
|
|
[0.808383708156, -0.143104108476, -0.288102517987],
|
|
[0.089303327519, 0.049290470655, -0.010970501667],
|
|
[0.010197195705, 0.012879721400, 0.001317586740],
|
|
[0.001795282629, 0.004482403780, 0.001563326157],
|
|
[-0.001974716105, 0.001278073933, 0.000652735133],
|
|
[0.000906544715, -0.000805502229, -0.000336200833],
|
|
[0.000283816745, 0.001799099064, 0.000756827653],
|
|
[-0.000784971304, 0.000123081220, 0.000068812133],
|
|
[-0.000237033406, 0.000980100466, 0.000427758498],
|
|
[-0.001976846386, -0.000280421081, -0.000072417045],
|
|
[0.000195628511, -0.001446079585, -0.000624011074],
|
|
[-0.000044622337, -0.000035865046, -0.000013581236],
|
|
[0.000204397832, -0.001127474894, -0.000488668673],
|
|
[0.002625373003, 0.000389300123, 0.000102756139],
|
|
[-0.000277321614, 0.001732818354, 0.000749576471],
|
|
[-0.003280537764, -0.000457571669, -0.000116383655]]
|
|
}]
|
|
};
|
|
|
|
const DT = [
|
|
{ mjd:-72638.0, dt:38 },
|
|
{ mjd:-65333.0, dt:26 },
|
|
{ mjd:-58028.0, dt:21 },
|
|
{ mjd:-50724.0, dt:21.1 },
|
|
{ mjd:-43419.0, dt:13.5 },
|
|
{ mjd:-39766.0, dt:13.7 },
|
|
{ mjd:-36114.0, dt:14.8 },
|
|
{ mjd:-32461.0, dt:15.7 },
|
|
{ mjd:-28809.0, dt:15.6 },
|
|
{ mjd:-25156.0, dt:13.3 },
|
|
{ mjd:-21504.0, dt:12.6 },
|
|
{ mjd:-17852.0, dt:11.2 },
|
|
{ mjd:-14200.0, dt:11.13 },
|
|
{ mjd:-10547.0, dt:7.95 },
|
|
{ mjd:-6895.0, dt:6.22 },
|
|
{ mjd:-3242.0, dt:6.55 },
|
|
{ mjd:-1416.0, dt:7.26 },
|
|
{ mjd:410.0, dt:7.35 },
|
|
{ mjd:2237.0, dt:5.92 },
|
|
{ mjd:4063.0, dt:1.04 },
|
|
{ mjd:5889.0, dt:-3.19 },
|
|
{ mjd:7715.0, dt:-5.36 },
|
|
{ mjd:9542.0, dt:-5.74 },
|
|
{ mjd:11368.0, dt:-5.86 },
|
|
{ mjd:13194.0, dt:-6.41 },
|
|
{ mjd:15020.0, dt:-2.70 },
|
|
{ mjd:16846.0, dt:3.92 },
|
|
{ mjd:18672.0, dt:10.38 },
|
|
{ mjd:20498.0, dt:17.19 },
|
|
{ mjd:22324.0, dt:21.41 },
|
|
{ mjd:24151.0, dt:23.63 },
|
|
{ mjd:25977.0, dt:24.02 },
|
|
{ mjd:27803.0, dt:23.91 },
|
|
{ mjd:29629.0, dt:24.35 },
|
|
{ mjd:31456.0, dt:26.76 },
|
|
{ mjd:33282.0, dt:29.15 },
|
|
{ mjd:35108.0, dt:31.07 },
|
|
{ mjd:36934.0, dt:33.150 },
|
|
{ mjd:38761.0, dt:35.738 },
|
|
{ mjd:40587.0, dt:40.182 },
|
|
{ mjd:42413.0, dt:45.477 },
|
|
{ mjd:44239.0, dt:50.540 },
|
|
{ mjd:44605.0, dt:51.3808 },
|
|
{ mjd:44970.0, dt:52.1668 },
|
|
{ mjd:45335.0, dt:52.9565 },
|
|
{ mjd:45700.0, dt:53.7882 },
|
|
{ mjd:46066.0, dt:54.3427 },
|
|
{ mjd:46431.0, dt:54.8712 },
|
|
{ mjd:46796.0, dt:55.3222 },
|
|
{ mjd:47161.0, dt:55.8197 },
|
|
{ mjd:47527.0, dt:56.3000 },
|
|
{ mjd:47892.0, dt:56.8553 },
|
|
{ mjd:48257.0, dt:57.5653 },
|
|
{ mjd:48622.0, dt:58.3092 },
|
|
{ mjd:48988.0, dt:59.1218 },
|
|
{ mjd:49353.0, dt:59.9845 },
|
|
{ mjd:49718.0, dt:60.7853 },
|
|
{ mjd:50083.0, dt:61.6287 },
|
|
{ mjd:50449.0, dt:62.2950 },
|
|
{ mjd:50814.0, dt:62.9659 },
|
|
{ mjd:51179.0, dt:63.4673 },
|
|
{ mjd:51544.0, dt:63.8285 },
|
|
{ mjd:51910.0, dt:64.0908 },
|
|
{ mjd:52275.0, dt:64.2998 },
|
|
{ mjd:52640.0, dt:64.4734 },
|
|
{ mjd:53005.0, dt:64.5736 },
|
|
{ mjd:53371.0, dt:64.6876 },
|
|
{ mjd:53736.0, dt:64.8452 },
|
|
{ mjd:54101.0, dt:65.1464 },
|
|
{ mjd:54466.0, dt:65.4573 },
|
|
{ mjd:54832.0, dt:65.7768 },
|
|
{ mjd:55197.0, dt:66.0699 },
|
|
{ mjd:55562.0, dt:66.3246 },
|
|
{ mjd:55927.0, dt:66.6030 },
|
|
{ mjd:56293.0, dt:66.9069 },
|
|
{ mjd:56658.0, dt:67.2810 },
|
|
{ mjd:57023.0, dt:67.6439 },
|
|
{ mjd:57388.0, dt:68.1024 },
|
|
{ mjd:57754.0, dt:68.5927 },
|
|
{ mjd:58119.0, dt:68.9676 },
|
|
{ mjd:58484.0, dt:69.2201 },
|
|
{ mjd:58849.0, dt:69.87 },
|
|
{ mjd:59214.0, dt:70.39 },
|
|
{ mjd:59580.0, dt:70.91 },
|
|
{ mjd:59945.0, dt:71.40 },
|
|
{ mjd:60310.0, dt:71.88 },
|
|
{ mjd:60675.0, dt:72.36 },
|
|
{ mjd:61041.0, dt:72.83 },
|
|
{ mjd:61406.0, dt:73.32 },
|
|
{ mjd:61680.0, dt:73.66 }
|
|
];
|
|
|
|
/**
|
|
* Calculates the difference TT-UT for the given date/time, expressed
|
|
* as a Modified Julian Date.
|
|
*
|
|
* @param {number} mjd
|
|
* A date and time expressed as a
|
|
* <a href="http://scienceworld.wolfram.com/astronomy/ModifiedJulianDate.html">Modified Julian Date</a>.
|
|
*
|
|
* @returns {number}
|
|
* The difference TT-UT in seconds for the given date and time.
|
|
*/
|
|
function DeltaT(mjd) {
|
|
// DT[i] = { mjd: 58484.0, dt: 69.34 }
|
|
// Check end ranges. If outside the known bounds, clamp to the closest known value.
|
|
|
|
if (mjd <= DT[0].mjd) {
|
|
return DT[0].dt;
|
|
}
|
|
|
|
if (mjd >= DT[DT.length-1].mjd) {
|
|
return DT[DT.length-1].dt;
|
|
}
|
|
|
|
// Do a binary search to find the pair of indexes this mjd lies between.
|
|
|
|
let lo = 0;
|
|
let hi = DT.length-2; // make sure there is always an array element after the one we are looking at
|
|
while (lo <= hi) {
|
|
let c = (lo + hi) >> 1;
|
|
if (mjd < DT[c].mjd) {
|
|
hi = c-1;
|
|
} else if (mjd > DT[c+1].mjd) {
|
|
lo = c+1;
|
|
} else {
|
|
let frac = (mjd - DT[c].mjd) / (DT[c+1].mjd - DT[c].mjd);
|
|
return DT[c].dt + frac*(DT[c+1].dt - DT[c].dt);
|
|
}
|
|
}
|
|
|
|
// This should never happen if the binary search algorithm is correct.
|
|
throw `Could not find Delta-T value for MJD=${mjd}`;
|
|
}
|
|
|
|
/**
|
|
* Calculates Terrestrial Time (TT) from Universal Time (UT).
|
|
*
|
|
* @param {number} ut
|
|
* The Universal Time expressed as a floating point number of days since the 2000.0 epoch.
|
|
*
|
|
* @returns {number}
|
|
* A Terrestrial Time expressed as a floating point number of days since the 2000.0 epoch.
|
|
*/
|
|
function TerrestrialTime(ut) {
|
|
return ut + DeltaT(ut + Y2000_IN_MJD)/86400;
|
|
}
|
|
|
|
/**
|
|
* The date and time of an astronomical observation.
|
|
* Objects of this type are used throughout the internals
|
|
* of the Astronomy library, and are included in certain return objects.
|
|
* The constructor is not accessible outside the Astronomy library;
|
|
* outside users should call the {@link Astronomy.MakeTime} function
|
|
* to create an <code>AstroTime</code> object.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {Date} date
|
|
* The JavaScript Date object for the given date and time.
|
|
* This Date corresponds to the numeric day value stored in the <code>ut</code> property.
|
|
*
|
|
* @property {number} ut
|
|
* Universal Time (UT1/UTC) in fractional days since the J2000 epoch.
|
|
* Universal Time represents time measured with respect to the Earth's rotation,
|
|
* tracking mean solar days.
|
|
* The Astronomy library approximates UT1 and UTC as being the same thing.
|
|
* This gives sufficient accuracy for the precision requirements of this project.
|
|
*
|
|
* @property {number} tt
|
|
* Terrestrial Time in fractional days since the J2000 epoch.
|
|
* TT represents a continuously flowing ephemeris timescale independent of
|
|
* any variations of the Earth's rotation, and is adjusted from UT
|
|
* using historical and predictive models of those variations.
|
|
*/
|
|
class AstroTime {
|
|
/**
|
|
* @param {(Date|number)} date
|
|
* A JavaScript Date object or a numeric UTC value expressed in J2000 days.
|
|
*/
|
|
constructor(date) {
|
|
const MillisPerDay = 1000 * 3600 * 24;
|
|
|
|
if (date instanceof Date) {
|
|
this.date = date;
|
|
this.ut = (date - J2000) / MillisPerDay;
|
|
this.tt = TerrestrialTime(this.ut);
|
|
return;
|
|
}
|
|
|
|
if (typeof date === 'number') {
|
|
this.date = new Date(J2000 - (-date)*MillisPerDay);
|
|
this.ut = date;
|
|
this.tt = TerrestrialTime(this.ut);
|
|
return;
|
|
}
|
|
|
|
throw 'Argument must be a Date object, an AstroTime object, or a numeric UTC Julian date.';
|
|
}
|
|
|
|
/**
|
|
* Formats an <code>AstroTime</code> object as an
|
|
* <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>
|
|
* date/time string in UTC, to millisecond resolution.
|
|
* Example:
|
|
* <pre>
|
|
* <code>2018-08-17T17:22:04.050Z</code>
|
|
* </pre>
|
|
* @returns {string}
|
|
*/
|
|
toString() {
|
|
return this.date.toISOString();
|
|
}
|
|
|
|
/**
|
|
* Returns a new <code>AstroTime</code> object adjusted by the floating point number of days.
|
|
* Does NOT modify the original <code>AstroTime</code> object.
|
|
*
|
|
* @param {number} days
|
|
* The floating point number of days by which to adjust the given date and time.
|
|
* Positive values adjust the date toward the future, and
|
|
* negative values adjust the date toward the past.
|
|
*
|
|
* @returns {Astronomy.AstroTime}
|
|
*/
|
|
AddDays(days) {
|
|
// This is slightly wrong, but the error is tiny.
|
|
// We really should be adding to TT, not to UT.
|
|
// But using TT would require creating an inverse function for DeltaT,
|
|
// which would be quite a bit of extra calculation.
|
|
// I estimate the error is in practice on the order of 10^(-7)
|
|
// times the value of 'days'.
|
|
// This is based on a typical drift of 1 second per year between UT and TT.
|
|
return new AstroTime(this.ut + days);
|
|
}
|
|
}
|
|
|
|
function InterpolateTime(time1, time2, fraction) {
|
|
return new AstroTime(time1.ut + fraction*(time2.ut - time1.ut));
|
|
}
|
|
|
|
/**
|
|
* Given a Date object or a number days since noon (12:00) on January 1, 2000 (UTC),
|
|
* this function creates an {@link Astronomy.AstroTime} object.
|
|
* Given an {@link Astronomy.AstroTime} object, returns the same object unmodified.
|
|
* Use of this function is not required for any of the other exposed functions in this library,
|
|
* because they all guarantee converting date/time parameters to Astronomy.AstroTime
|
|
* as needed. However, it may be convenient for callers who need to understand
|
|
* the difference between UTC and TT (Terrestrial Time). In some use cases,
|
|
* converting once to Astronomy.AstroTime format and passing the result into multiple
|
|
* function calls may be more efficient than passing in native JavaScript Date objects.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} date
|
|
* A Date object, a number of UTC days since the J2000 epoch (noon on January 1, 2000),
|
|
* or an Astronomy.AstroTime object. See remarks above.
|
|
*
|
|
* @returns {Astronomy.AstroTime}
|
|
*/
|
|
Astronomy.MakeTime = function(date) {
|
|
if (date instanceof AstroTime) {
|
|
return date;
|
|
}
|
|
return new AstroTime(date);
|
|
}
|
|
|
|
const iaudata = [
|
|
|
|
{ nals:[ 0, 0, 0, 0, 1 ], cls:[ -172064161, -174666, 33386, 92052331, 9086, 15377 ] },
|
|
{ nals:[ 0, 0, 2, -2, 2 ], cls:[ -13170906, -1675, -13696, 5730336, -3015, -4587 ] },
|
|
{ nals:[ 0, 0, 2, 0, 2 ], cls:[ -2276413, -234, 2796, 978459, -485, 1374 ] },
|
|
{ nals:[ 0, 0, 0, 0, 2 ], cls:[ 2074554, 207, -698, -897492, 470, -291 ] },
|
|
{ nals:[ 0, 1, 0, 0, 0 ], cls:[ 1475877, -3633, 11817, 73871, -184, -1924 ] },
|
|
{ nals:[ 0, 1, 2, -2, 2 ], cls:[ -516821, 1226, -524, 224386, -677, -174 ] },
|
|
{ nals:[ 1, 0, 0, 0, 0 ], cls:[ 711159, 73, -872, -6750, 0, 358 ] },
|
|
{ nals:[ 0, 0, 2, 0, 1 ], cls:[ -387298, -367, 380, 200728, 18, 318 ] },
|
|
{ nals:[ 1, 0, 2, 0, 2 ], cls:[ -301461, -36, 816, 129025, -63, 367 ] },
|
|
{ nals:[ 0, -1, 2, -2, 2 ], cls:[ 215829, -494, 111, -95929, 299, 132 ] },
|
|
{ nals:[ 0, 0, 2, -2, 1 ], cls:[ 128227, 137, 181, -68982, -9, 39 ] },
|
|
{ nals:[ -1, 0, 2, 0, 2 ], cls:[ 123457, 11, 19, -53311, 32, -4 ] },
|
|
{ nals:[ -1, 0, 0, 2, 0 ], cls:[ 156994, 10, -168, -1235, 0, 82 ] },
|
|
{ nals:[ 1, 0, 0, 0, 1 ], cls:[ 63110, 63, 27, -33228, 0, -9 ] },
|
|
{ nals:[ -1, 0, 0, 0, 1 ], cls:[ -57976, -63, -189, 31429, 0, -75 ] },
|
|
{ nals:[ -1, 0, 2, 2, 2 ], cls:[ -59641, -11, 149, 25543, -11, 66 ] },
|
|
{ nals:[ 1, 0, 2, 0, 1 ], cls:[ -51613, -42, 129, 26366, 0, 78 ] },
|
|
{ nals:[ -2, 0, 2, 0, 1 ], cls:[ 45893, 50, 31, -24236, -10, 20 ] },
|
|
{ nals:[ 0, 0, 0, 2, 0 ], cls:[ 63384, 11, -150, -1220, 0, 29 ] },
|
|
{ nals:[ 0, 0, 2, 2, 2 ], cls:[ -38571, -1, 158, 16452, -11, 68 ] },
|
|
{ nals:[ 0, -2, 2, -2, 2 ], cls:[ 32481, 0, 0, -13870, 0, 0 ] },
|
|
{ nals:[ -2, 0, 0, 2, 0 ], cls:[ -47722, 0, -18, 477, 0, -25 ] },
|
|
{ nals:[ 2, 0, 2, 0, 2 ], cls:[ -31046, -1, 131, 13238, -11, 59 ] },
|
|
{ nals:[ 1, 0, 2, -2, 2 ], cls:[ 28593, 0, -1, -12338, 10, -3 ] },
|
|
{ nals:[ -1, 0, 2, 0, 1 ], cls:[ 20441, 21, 10, -10758, 0, -3 ] },
|
|
{ nals:[ 2, 0, 0, 0, 0 ], cls:[ 29243, 0, -74, -609, 0, 13 ] },
|
|
{ nals:[ 0, 0, 2, 0, 0 ], cls:[ 25887, 0, -66, -550, 0, 11 ] },
|
|
{ nals:[ 0, 1, 0, 0, 1 ], cls:[ -14053, -25, 79, 8551, -2, -45 ] },
|
|
{ nals:[ -1, 0, 0, 2, 1 ], cls:[ 15164, 10, 11, -8001, 0, -1 ] },
|
|
{ nals:[ 0, 2, 2, -2, 2 ], cls:[ -15794, 72, -16, 6850, -42, -5 ] },
|
|
{ nals:[ 0, 0, -2, 2, 0 ], cls:[ 21783, 0, 13, -167, 0, 13 ] },
|
|
{ nals:[ 1, 0, 0, -2, 1 ], cls:[ -12873, -10, -37, 6953, 0, -14 ] },
|
|
{ nals:[ 0, -1, 0, 0, 1 ], cls:[ -12654, 11, 63, 6415, 0, 26 ] },
|
|
{ nals:[ -1, 0, 2, 2, 1 ], cls:[ -10204, 0, 25, 5222, 0, 15 ] },
|
|
{ nals:[ 0, 2, 0, 0, 0 ], cls:[ 16707, -85, -10, 168, -1, 10 ] },
|
|
{ nals:[ 1, 0, 2, 2, 2 ], cls:[ -7691, 0, 44, 3268, 0, 19 ] },
|
|
{ nals:[ -2, 0, 2, 0, 0 ], cls:[ -11024, 0, -14, 104, 0, 2 ] },
|
|
{ nals:[ 0, 1, 2, 0, 2 ], cls:[ 7566, -21, -11, -3250, 0, -5 ] },
|
|
{ nals:[ 0, 0, 2, 2, 1 ], cls:[ -6637, -11, 25, 3353, 0, 14 ] },
|
|
{ nals:[ 0, -1, 2, 0, 2 ], cls:[ -7141, 21, 8, 3070, 0, 4 ] },
|
|
{ nals:[ 0, 0, 0, 2, 1 ], cls:[ -6302, -11, 2, 3272, 0, 4 ] },
|
|
{ nals:[ 1, 0, 2, -2, 1 ], cls:[ 5800, 10, 2, -3045, 0, -1 ] },
|
|
{ nals:[ 2, 0, 2, -2, 2 ], cls:[ 6443, 0, -7, -2768, 0, -4 ] },
|
|
{ nals:[ -2, 0, 0, 2, 1 ], cls:[ -5774, -11, -15, 3041, 0, -5 ] },
|
|
{ nals:[ 2, 0, 2, 0, 1 ], cls:[ -5350, 0, 21, 2695, 0, 12 ] },
|
|
{ nals:[ 0, -1, 2, -2, 1 ], cls:[ -4752, -11, -3, 2719, 0, -3 ] },
|
|
{ nals:[ 0, 0, 0, -2, 1 ], cls:[ -4940, -11, -21, 2720, 0, -9 ] },
|
|
{ nals:[ -1, -1, 0, 2, 0 ], cls:[ 7350, 0, -8, -51, 0, 4 ] },
|
|
{ nals:[ 2, 0, 0, -2, 1 ], cls:[ 4065, 0, 6, -2206, 0, 1 ] },
|
|
{ nals:[ 1, 0, 0, 2, 0 ], cls:[ 6579, 0, -24, -199, 0, 2 ] },
|
|
{ nals:[ 0, 1, 2, -2, 1 ], cls:[ 3579, 0, 5, -1900, 0, 1 ] },
|
|
{ nals:[ 1, -1, 0, 0, 0 ], cls:[ 4725, 0, -6, -41, 0, 3 ] },
|
|
{ nals:[ -2, 0, 2, 0, 2 ], cls:[ -3075, 0, -2, 1313, 0, -1 ] },
|
|
{ nals:[ 3, 0, 2, 0, 2 ], cls:[ -2904, 0, 15, 1233, 0, 7 ] },
|
|
{ nals:[ 0, -1, 0, 2, 0 ], cls:[ 4348, 0, -10, -81, 0, 2 ] },
|
|
{ nals:[ 1, -1, 2, 0, 2 ], cls:[ -2878, 0, 8, 1232, 0, 4 ] },
|
|
{ nals:[ 0, 0, 0, 1, 0 ], cls:[ -4230, 0, 5, -20, 0, -2 ] },
|
|
{ nals:[ -1, -1, 2, 2, 2 ], cls:[ -2819, 0, 7, 1207, 0, 3 ] },
|
|
{ nals:[ -1, 0, 2, 0, 0 ], cls:[ -4056, 0, 5, 40, 0, -2 ] },
|
|
{ nals:[ 0, -1, 2, 2, 2 ], cls:[ -2647, 0, 11, 1129, 0, 5 ] },
|
|
{ nals:[ -2, 0, 0, 0, 1 ], cls:[ -2294, 0, -10, 1266, 0, -4 ] },
|
|
{ nals:[ 1, 1, 2, 0, 2 ], cls:[ 2481, 0, -7, -1062, 0, -3 ] },
|
|
{ nals:[ 2, 0, 0, 0, 1 ], cls:[ 2179, 0, -2, -1129, 0, -2 ] },
|
|
{ nals:[ -1, 1, 0, 1, 0 ], cls:[ 3276, 0, 1, -9, 0, 0 ] },
|
|
{ nals:[ 1, 1, 0, 0, 0 ], cls:[ -3389, 0, 5, 35, 0, -2 ] },
|
|
{ nals:[ 1, 0, 2, 0, 0 ], cls:[ 3339, 0, -13, -107, 0, 1 ] },
|
|
{ nals:[ -1, 0, 2, -2, 1 ], cls:[ -1987, 0, -6, 1073, 0, -2 ] },
|
|
{ nals:[ 1, 0, 0, 0, 2 ], cls:[ -1981, 0, 0, 854, 0, 0 ] },
|
|
{ nals:[ -1, 0, 0, 1, 0 ], cls:[ 4026, 0, -353, -553, 0, -139 ] },
|
|
{ nals:[ 0, 0, 2, 1, 2 ], cls:[ 1660, 0, -5, -710, 0, -2 ] },
|
|
{ nals:[ -1, 0, 2, 4, 2 ], cls:[ -1521, 0, 9, 647, 0, 4 ] },
|
|
{ nals:[ -1, 1, 0, 1, 1 ], cls:[ 1314, 0, 0, -700, 0, 0 ] },
|
|
{ nals:[ 0, -2, 2, -2, 1 ], cls:[ -1283, 0, 0, 672, 0, 0 ] },
|
|
{ nals:[ 1, 0, 2, 2, 1 ], cls:[ -1331, 0, 8, 663, 0, 4 ] },
|
|
{ nals:[ -2, 0, 2, 2, 2 ], cls:[ 1383, 0, -2, -594, 0, -2 ] },
|
|
{ nals:[ -1, 0, 0, 0, 2 ], cls:[ 1405, 0, 4, -610, 0, 2 ] },
|
|
{ nals:[ 1, 1, 2, -2, 2 ], cls:[ 1290, 0, 0, -556, 0, 0 ] }
|
|
|
|
];
|
|
|
|
function iau2000b(time) {
|
|
var i, t, el, elp, f, d, om, arg, dp, de, sarg, carg;
|
|
var nals, cls;
|
|
|
|
function mod(x) {
|
|
return (x % ASEC360) * ASEC2RAD;
|
|
}
|
|
|
|
t = time.tt / 36525;
|
|
el = mod(485868.249036 + t*1717915923.2178);
|
|
elp = mod(1287104.79305 + t*129596581.0481);
|
|
f = mod(335779.526232 + t*1739527262.8478);
|
|
d = mod(1072260.70369 + t*1602961601.2090);
|
|
om = mod(450160.398036 - t*6962890.5431);
|
|
dp = 0;
|
|
de = 0;
|
|
for (i=76; i >= 0; --i) {
|
|
nals = iaudata[i].nals;
|
|
cls = iaudata[i].cls;
|
|
arg = (nals[0]*el + nals[1]*elp + nals[2]*f + nals[3]*d + nals[4]*om) % PI2;
|
|
sarg = Math.sin(arg);
|
|
carg = Math.cos(arg);
|
|
dp += (cls[0] + cls[1]*t) * sarg + cls[2]*carg;
|
|
de += (cls[3] + cls[4]*t) * carg + cls[5]*sarg;
|
|
}
|
|
return {
|
|
dpsi: (-0.000135 * ASEC2RAD) + (dp * 1.0e-7 * ASEC2RAD),
|
|
deps: (+0.000388 * ASEC2RAD) + (de * 1.0e-7 * ASEC2RAD)
|
|
};
|
|
}
|
|
|
|
function nutation_angles(time) {
|
|
var nut = iau2000b(time);
|
|
return { dpsi: nut.dpsi/ASEC2RAD, deps: nut.deps/ASEC2RAD };
|
|
}
|
|
|
|
function mean_obliq(time) {
|
|
var t = time.tt / 36525;
|
|
var asec = (
|
|
(((( - 0.0000000434 * t
|
|
- 0.000000576 ) * t
|
|
+ 0.00200340 ) * t
|
|
- 0.0001831 ) * t
|
|
- 46.836769 ) * t + 84381.406
|
|
);
|
|
return asec / 3600.0;
|
|
}
|
|
|
|
var cache_e_tilt;
|
|
|
|
function e_tilt(time) {
|
|
if (!cache_e_tilt || Math.abs(cache_e_tilt.tt - time.tt) > 1.0e-6) {
|
|
const nut = nutation_angles(time);
|
|
const mean_ob = mean_obliq(time);
|
|
const true_ob = mean_ob + (nut.deps / 3600);
|
|
cache_e_tilt = {
|
|
tt: time.tt,
|
|
dpsi: nut.dpsi,
|
|
deps: nut.deps,
|
|
ee: nut.dpsi * Math.cos(mean_ob * DEG2RAD) / 15,
|
|
mobl: mean_ob,
|
|
tobl: true_ob
|
|
};
|
|
}
|
|
return cache_e_tilt;
|
|
}
|
|
|
|
function ecl2equ_vec(time, pos) {
|
|
var obl = mean_obliq(time) * DEG2RAD;
|
|
var cos_obl = Math.cos(obl);
|
|
var sin_obl = Math.sin(obl);
|
|
return [
|
|
pos[0],
|
|
pos[1]*cos_obl - pos[2]*sin_obl,
|
|
pos[1]*sin_obl + pos[2]*cos_obl
|
|
];
|
|
}
|
|
|
|
function CalcMoon(time) {
|
|
++Perf.calcmoon;
|
|
|
|
const T = time.tt / 36525;
|
|
|
|
function DeclareArray1(xmin, xmax) {
|
|
var array = [];
|
|
var i;
|
|
for (i=0; i <= xmax-xmin; ++i) {
|
|
array.push(0);
|
|
}
|
|
return {min:xmin, array:array};
|
|
}
|
|
|
|
function DeclareArray2(xmin, xmax, ymin, ymax) {
|
|
var array = [];
|
|
var i;
|
|
for (i=0; i <= xmax-xmin; ++i) {
|
|
array.push(DeclareArray1(ymin, ymax));
|
|
}
|
|
return {min:xmin, array:array};
|
|
}
|
|
|
|
function ArrayGet2(a, x, y) {
|
|
var m = a.array[x - a.min];
|
|
return m.array[y - m.min];
|
|
}
|
|
|
|
function ArraySet2(a, x, y, v) {
|
|
var m = a.array[x - a.min];
|
|
m.array[y - m.min] = v;
|
|
}
|
|
|
|
var S, MAX, ARG, FAC, I, J, T2, DGAM, DLAM, N, GAM1C, SINPI, L0, L, LS, F, D, DL0, DL, DLS, DF, DD, DS;
|
|
var coArray = DeclareArray2(-6, 6, 1, 4);
|
|
var siArray = DeclareArray2(-6, 6, 1, 4);
|
|
|
|
function CO(x, y) {
|
|
return ArrayGet2(coArray, x, y);
|
|
}
|
|
|
|
function SI(x, y) {
|
|
return ArrayGet2(siArray, x, y);
|
|
}
|
|
|
|
function SetCO(x, y, v) {
|
|
return ArraySet2(coArray, x, y, v);
|
|
}
|
|
|
|
function SetSI(x, y, v) {
|
|
return ArraySet2(siArray, x, y, v);
|
|
}
|
|
|
|
function AddThe(c1, s1, c2, s2, func) {
|
|
return func(c1*c2 - s1*s2, s1*c2 + c1*s2);
|
|
}
|
|
|
|
function Sine(phi) {
|
|
return Math.sin(PI2 * phi);
|
|
}
|
|
|
|
T2 = T*T;
|
|
DLAM = 0;
|
|
DS = 0;
|
|
GAM1C = 0;
|
|
SINPI = 3422.7000;
|
|
|
|
var S1 = Sine(0.19833+0.05611*T);
|
|
var S2 = Sine(0.27869+0.04508*T);
|
|
var S3 = Sine(0.16827-0.36903*T);
|
|
var S4 = Sine(0.34734-5.37261*T);
|
|
var S5 = Sine(0.10498-5.37899*T);
|
|
var S6 = Sine(0.42681-0.41855*T);
|
|
var S7 = Sine(0.14943-5.37511*T);
|
|
DL0 = 0.84*S1+0.31*S2+14.27*S3+ 7.26*S4+ 0.28*S5+0.24*S6;
|
|
DL = 2.94*S1+0.31*S2+14.27*S3+ 9.34*S4+ 1.12*S5+0.83*S6;
|
|
DLS =-6.40*S1 -1.89*S6;
|
|
DF = 0.21*S1+0.31*S2+14.27*S3-88.70*S4-15.30*S5+0.24*S6-1.86*S7;
|
|
DD = DL0-DLS;
|
|
DGAM = (-3332E-9 * Sine(0.59734-5.37261*T)
|
|
-539E-9 * Sine(0.35498-5.37899*T)
|
|
-64E-9 * Sine(0.39943-5.37511*T));
|
|
|
|
L0 = PI2*Frac(0.60643382+1336.85522467*T-0.00000313*T2) + DL0/ARC;
|
|
L = PI2*Frac(0.37489701+1325.55240982*T+0.00002565*T2) + DL /ARC;
|
|
LS = PI2*Frac(0.99312619+ 99.99735956*T-0.00000044*T2) + DLS/ARC;
|
|
F = PI2*Frac(0.25909118+1342.22782980*T-0.00000892*T2) + DF /ARC;
|
|
D = PI2*Frac(0.82736186+1236.85308708*T-0.00000397*T2) + DD /ARC;
|
|
for (I=1; I<=4; ++I)
|
|
{
|
|
switch (I)
|
|
{
|
|
case 1: ARG=L; MAX=4; FAC=1.000002208; break;
|
|
case 2: ARG=LS; MAX=3; FAC=0.997504612-0.002495388*T; break;
|
|
case 3: ARG=F; MAX=4; FAC=1.000002708+139.978*DGAM; break;
|
|
case 4: ARG=D; MAX=6; FAC=1.0; break;
|
|
}
|
|
SetCO(0, I, 1);
|
|
SetCO(1, I, Math.cos(ARG) * FAC);
|
|
SetSI(0, I, 0);
|
|
SetSI(1, I, Math.sin(ARG) * FAC);
|
|
for (J=2; J<=MAX; ++J) {
|
|
AddThe(CO(J-1,I), SI(J-1,I), CO(1,I), SI(1,I), (c, s) => (SetCO(J,I,c), SetSI(J,I,s)));
|
|
}
|
|
for (J=1; J<=MAX; ++J) {
|
|
SetCO(-J, I, CO(J, I));
|
|
SetSI(-J, I, -SI(J, I));
|
|
}
|
|
}
|
|
|
|
function Term(p, q, r, s) {
|
|
var result = { x:1, y:0 };
|
|
var I = [ null, p, q, r, s ];
|
|
for (var k=1; k <= 4; ++k)
|
|
if (I[k] !== 0)
|
|
AddThe(result.x, result.y, CO(I[k], k), SI(I[k], k), (c, s) => (result.x=c, result.y=s));
|
|
return result;
|
|
}
|
|
|
|
function AddSol(coeffl, coeffs, coeffg, coeffp, p, q, r, s) {
|
|
var result = Term(p, q, r, s);
|
|
DLAM += coeffl * result.y;
|
|
DS += coeffs * result.y;
|
|
GAM1C += coeffg * result.x;
|
|
SINPI += coeffp * result.x;
|
|
}
|
|
|
|
|
|
AddSol( 13.9020, 14.0600, -0.0010, 0.2607, 0, 0, 0, 4);
|
|
AddSol( 0.4030, -4.0100, 0.3940, 0.0023, 0, 0, 0, 3);
|
|
AddSol( 2369.9120, 2373.3600, 0.6010, 28.2333, 0, 0, 0, 2);
|
|
AddSol( -125.1540, -112.7900, -0.7250, -0.9781, 0, 0, 0, 1);
|
|
AddSol( 1.9790, 6.9800, -0.4450, 0.0433, 1, 0, 0, 4);
|
|
AddSol( 191.9530, 192.7200, 0.0290, 3.0861, 1, 0, 0, 2);
|
|
AddSol( -8.4660, -13.5100, 0.4550, -0.1093, 1, 0, 0, 1);
|
|
AddSol( 22639.5000, 22609.0700, 0.0790, 186.5398, 1, 0, 0, 0);
|
|
AddSol( 18.6090, 3.5900, -0.0940, 0.0118, 1, 0, 0,-1);
|
|
AddSol( -4586.4650, -4578.1300, -0.0770, 34.3117, 1, 0, 0,-2);
|
|
AddSol( 3.2150, 5.4400, 0.1920, -0.0386, 1, 0, 0,-3);
|
|
AddSol( -38.4280, -38.6400, 0.0010, 0.6008, 1, 0, 0,-4);
|
|
AddSol( -0.3930, -1.4300, -0.0920, 0.0086, 1, 0, 0,-6);
|
|
AddSol( -0.2890, -1.5900, 0.1230, -0.0053, 0, 1, 0, 4);
|
|
AddSol( -24.4200, -25.1000, 0.0400, -0.3000, 0, 1, 0, 2);
|
|
AddSol( 18.0230, 17.9300, 0.0070, 0.1494, 0, 1, 0, 1);
|
|
AddSol( -668.1460, -126.9800, -1.3020, -0.3997, 0, 1, 0, 0);
|
|
AddSol( 0.5600, 0.3200, -0.0010, -0.0037, 0, 1, 0,-1);
|
|
AddSol( -165.1450, -165.0600, 0.0540, 1.9178, 0, 1, 0,-2);
|
|
AddSol( -1.8770, -6.4600, -0.4160, 0.0339, 0, 1, 0,-4);
|
|
AddSol( 0.2130, 1.0200, -0.0740, 0.0054, 2, 0, 0, 4);
|
|
AddSol( 14.3870, 14.7800, -0.0170, 0.2833, 2, 0, 0, 2);
|
|
AddSol( -0.5860, -1.2000, 0.0540, -0.0100, 2, 0, 0, 1);
|
|
AddSol( 769.0160, 767.9600, 0.1070, 10.1657, 2, 0, 0, 0);
|
|
AddSol( 1.7500, 2.0100, -0.0180, 0.0155, 2, 0, 0,-1);
|
|
AddSol( -211.6560, -152.5300, 5.6790, -0.3039, 2, 0, 0,-2);
|
|
AddSol( 1.2250, 0.9100, -0.0300, -0.0088, 2, 0, 0,-3);
|
|
AddSol( -30.7730, -34.0700, -0.3080, 0.3722, 2, 0, 0,-4);
|
|
AddSol( -0.5700, -1.4000, -0.0740, 0.0109, 2, 0, 0,-6);
|
|
AddSol( -2.9210, -11.7500, 0.7870, -0.0484, 1, 1, 0, 2);
|
|
AddSol( 1.2670, 1.5200, -0.0220, 0.0164, 1, 1, 0, 1);
|
|
AddSol( -109.6730, -115.1800, 0.4610, -0.9490, 1, 1, 0, 0);
|
|
AddSol( -205.9620, -182.3600, 2.0560, 1.4437, 1, 1, 0,-2);
|
|
AddSol( 0.2330, 0.3600, 0.0120, -0.0025, 1, 1, 0,-3);
|
|
AddSol( -4.3910, -9.6600, -0.4710, 0.0673, 1, 1, 0,-4);
|
|
AddSol( 0.2830, 1.5300, -0.1110, 0.0060, 1,-1, 0, 4);
|
|
AddSol( 14.5770, 31.7000, -1.5400, 0.2302, 1,-1, 0, 2);
|
|
AddSol( 147.6870, 138.7600, 0.6790, 1.1528, 1,-1, 0, 0);
|
|
AddSol( -1.0890, 0.5500, 0.0210, 0.0000, 1,-1, 0,-1);
|
|
AddSol( 28.4750, 23.5900, -0.4430, -0.2257, 1,-1, 0,-2);
|
|
AddSol( -0.2760, -0.3800, -0.0060, -0.0036, 1,-1, 0,-3);
|
|
AddSol( 0.6360, 2.2700, 0.1460, -0.0102, 1,-1, 0,-4);
|
|
AddSol( -0.1890, -1.6800, 0.1310, -0.0028, 0, 2, 0, 2);
|
|
AddSol( -7.4860, -0.6600, -0.0370, -0.0086, 0, 2, 0, 0);
|
|
AddSol( -8.0960, -16.3500, -0.7400, 0.0918, 0, 2, 0,-2);
|
|
AddSol( -5.7410, -0.0400, 0.0000, -0.0009, 0, 0, 2, 2);
|
|
AddSol( 0.2550, 0.0000, 0.0000, 0.0000, 0, 0, 2, 1);
|
|
AddSol( -411.6080, -0.2000, 0.0000, -0.0124, 0, 0, 2, 0);
|
|
AddSol( 0.5840, 0.8400, 0.0000, 0.0071, 0, 0, 2,-1);
|
|
AddSol( -55.1730, -52.1400, 0.0000, -0.1052, 0, 0, 2,-2);
|
|
AddSol( 0.2540, 0.2500, 0.0000, -0.0017, 0, 0, 2,-3);
|
|
AddSol( 0.0250, -1.6700, 0.0000, 0.0031, 0, 0, 2,-4);
|
|
AddSol( 1.0600, 2.9600, -0.1660, 0.0243, 3, 0, 0, 2);
|
|
AddSol( 36.1240, 50.6400, -1.3000, 0.6215, 3, 0, 0, 0);
|
|
AddSol( -13.1930, -16.4000, 0.2580, -0.1187, 3, 0, 0,-2);
|
|
AddSol( -1.1870, -0.7400, 0.0420, 0.0074, 3, 0, 0,-4);
|
|
AddSol( -0.2930, -0.3100, -0.0020, 0.0046, 3, 0, 0,-6);
|
|
AddSol( -0.2900, -1.4500, 0.1160, -0.0051, 2, 1, 0, 2);
|
|
AddSol( -7.6490, -10.5600, 0.2590, -0.1038, 2, 1, 0, 0);
|
|
AddSol( -8.6270, -7.5900, 0.0780, -0.0192, 2, 1, 0,-2);
|
|
AddSol( -2.7400, -2.5400, 0.0220, 0.0324, 2, 1, 0,-4);
|
|
AddSol( 1.1810, 3.3200, -0.2120, 0.0213, 2,-1, 0, 2);
|
|
AddSol( 9.7030, 11.6700, -0.1510, 0.1268, 2,-1, 0, 0);
|
|
AddSol( -0.3520, -0.3700, 0.0010, -0.0028, 2,-1, 0,-1);
|
|
AddSol( -2.4940, -1.1700, -0.0030, -0.0017, 2,-1, 0,-2);
|
|
AddSol( 0.3600, 0.2000, -0.0120, -0.0043, 2,-1, 0,-4);
|
|
AddSol( -1.1670, -1.2500, 0.0080, -0.0106, 1, 2, 0, 0);
|
|
AddSol( -7.4120, -6.1200, 0.1170, 0.0484, 1, 2, 0,-2);
|
|
AddSol( -0.3110, -0.6500, -0.0320, 0.0044, 1, 2, 0,-4);
|
|
AddSol( 0.7570, 1.8200, -0.1050, 0.0112, 1,-2, 0, 2);
|
|
AddSol( 2.5800, 2.3200, 0.0270, 0.0196, 1,-2, 0, 0);
|
|
AddSol( 2.5330, 2.4000, -0.0140, -0.0212, 1,-2, 0,-2);
|
|
AddSol( -0.3440, -0.5700, -0.0250, 0.0036, 0, 3, 0,-2);
|
|
AddSol( -0.9920, -0.0200, 0.0000, 0.0000, 1, 0, 2, 2);
|
|
AddSol( -45.0990, -0.0200, 0.0000, -0.0010, 1, 0, 2, 0);
|
|
AddSol( -0.1790, -9.5200, 0.0000, -0.0833, 1, 0, 2,-2);
|
|
AddSol( -0.3010, -0.3300, 0.0000, 0.0014, 1, 0, 2,-4);
|
|
AddSol( -6.3820, -3.3700, 0.0000, -0.0481, 1, 0,-2, 2);
|
|
AddSol( 39.5280, 85.1300, 0.0000, -0.7136, 1, 0,-2, 0);
|
|
AddSol( 9.3660, 0.7100, 0.0000, -0.0112, 1, 0,-2,-2);
|
|
AddSol( 0.2020, 0.0200, 0.0000, 0.0000, 1, 0,-2,-4);
|
|
AddSol( 0.4150, 0.1000, 0.0000, 0.0013, 0, 1, 2, 0);
|
|
AddSol( -2.1520, -2.2600, 0.0000, -0.0066, 0, 1, 2,-2);
|
|
AddSol( -1.4400, -1.3000, 0.0000, 0.0014, 0, 1,-2, 2);
|
|
AddSol( 0.3840, -0.0400, 0.0000, 0.0000, 0, 1,-2,-2);
|
|
AddSol( 1.9380, 3.6000, -0.1450, 0.0401, 4, 0, 0, 0);
|
|
AddSol( -0.9520, -1.5800, 0.0520, -0.0130, 4, 0, 0,-2);
|
|
AddSol( -0.5510, -0.9400, 0.0320, -0.0097, 3, 1, 0, 0);
|
|
AddSol( -0.4820, -0.5700, 0.0050, -0.0045, 3, 1, 0,-2);
|
|
AddSol( 0.6810, 0.9600, -0.0260, 0.0115, 3,-1, 0, 0);
|
|
AddSol( -0.2970, -0.2700, 0.0020, -0.0009, 2, 2, 0,-2);
|
|
AddSol( 0.2540, 0.2100, -0.0030, 0.0000, 2,-2, 0,-2);
|
|
AddSol( -0.2500, -0.2200, 0.0040, 0.0014, 1, 3, 0,-2);
|
|
AddSol( -3.9960, 0.0000, 0.0000, 0.0004, 2, 0, 2, 0);
|
|
AddSol( 0.5570, -0.7500, 0.0000, -0.0090, 2, 0, 2,-2);
|
|
AddSol( -0.4590, -0.3800, 0.0000, -0.0053, 2, 0,-2, 2);
|
|
AddSol( -1.2980, 0.7400, 0.0000, 0.0004, 2, 0,-2, 0);
|
|
AddSol( 0.5380, 1.1400, 0.0000, -0.0141, 2, 0,-2,-2);
|
|
AddSol( 0.2630, 0.0200, 0.0000, 0.0000, 1, 1, 2, 0);
|
|
AddSol( 0.4260, 0.0700, 0.0000, -0.0006, 1, 1,-2,-2);
|
|
AddSol( -0.3040, 0.0300, 0.0000, 0.0003, 1,-1, 2, 0);
|
|
AddSol( -0.3720, -0.1900, 0.0000, -0.0027, 1,-1,-2, 2);
|
|
AddSol( 0.4180, 0.0000, 0.0000, 0.0000, 0, 0, 4, 0);
|
|
AddSol( -0.3300, -0.0400, 0.0000, 0.0000, 3, 0, 2, 0);
|
|
|
|
|
|
function ADDN(coeffn, p, q, r, s) {
|
|
return coeffn * Term(p, q, r, s).y;
|
|
}
|
|
|
|
N = 0;
|
|
N += ADDN(-526.069, 0, 0,1,-2);
|
|
N += ADDN( -3.352, 0, 0,1,-4);
|
|
N += ADDN( +44.297,+1, 0,1,-2);
|
|
N += ADDN( -6.000,+1, 0,1,-4);
|
|
N += ADDN( +20.599,-1, 0,1, 0);
|
|
N += ADDN( -30.598,-1, 0,1,-2);
|
|
N += ADDN( -24.649,-2, 0,1, 0);
|
|
N += ADDN( -2.000,-2, 0,1,-2);
|
|
N += ADDN( -22.571, 0,+1,1,-2);
|
|
N += ADDN( +10.985, 0,-1,1,-2);
|
|
|
|
DLAM += (
|
|
+0.82*Sine(0.7736 -62.5512*T)+0.31*Sine(0.0466 -125.1025*T)
|
|
+0.35*Sine(0.5785 -25.1042*T)+0.66*Sine(0.4591+1335.8075*T)
|
|
+0.64*Sine(0.3130 -91.5680*T)+1.14*Sine(0.1480+1331.2898*T)
|
|
+0.21*Sine(0.5918+1056.5859*T)+0.44*Sine(0.5784+1322.8595*T)
|
|
+0.24*Sine(0.2275 -5.7374*T)+0.28*Sine(0.2965 +2.6929*T)
|
|
+0.33*Sine(0.3132 +6.3368*T)
|
|
);
|
|
|
|
S = F + DS/ARC;
|
|
|
|
var lat_seconds = (1.000002708 + 139.978*DGAM)*(18518.511+1.189+GAM1C)*Math.sin(S) - 6.24*Math.sin(3*S) + N;
|
|
|
|
return {
|
|
geo_eclip_lon: PI2 * Frac((L0+DLAM/ARC) / PI2),
|
|
geo_eclip_lat: (Math.PI / (180 * 3600)) * lat_seconds,
|
|
distance_au: (ARC * (ERAD / AU)) / (0.999953253 * SINPI)
|
|
};
|
|
}
|
|
|
|
function precession(tt1, pos1, tt2) {
|
|
var xx, yx, zx, xy, yy, zy, xz, yz, zz;
|
|
var eps0 = 84381.406;
|
|
var t, psia, omegaa, chia, sa, ca, sb, cb, sc, cc, sd, cd;
|
|
|
|
if ((tt1 !== 0) && (tt2 !== 0))
|
|
throw 'One of (tt1, tt2) must be 0.';
|
|
|
|
t = (tt2 - tt1) / 36525;
|
|
if (tt2 === 0)
|
|
t = -t;
|
|
|
|
psia = (((((- 0.0000000951 * t
|
|
+ 0.000132851 ) * t
|
|
- 0.00114045 ) * t
|
|
- 1.0790069 ) * t
|
|
+ 5038.481507 ) * t);
|
|
|
|
omegaa = (((((+ 0.0000003337 * t
|
|
- 0.000000467 ) * t
|
|
- 0.00772503 ) * t
|
|
+ 0.0512623 ) * t
|
|
- 0.025754 ) * t + eps0);
|
|
|
|
chia = (((((- 0.0000000560 * t
|
|
+ 0.000170663 ) * t
|
|
- 0.00121197 ) * t
|
|
- 2.3814292 ) * t
|
|
+ 10.556403 ) * t);
|
|
|
|
eps0 = eps0 * ASEC2RAD;
|
|
psia = psia * ASEC2RAD;
|
|
omegaa = omegaa * ASEC2RAD;
|
|
chia = chia * ASEC2RAD;
|
|
|
|
sa = Math.sin(eps0);
|
|
ca = Math.cos(eps0);
|
|
sb = Math.sin(-psia);
|
|
cb = Math.cos(-psia);
|
|
sc = Math.sin(-omegaa);
|
|
cc = Math.cos(-omegaa);
|
|
sd = Math.sin(chia);
|
|
cd = Math.cos(chia);
|
|
|
|
xx = cd * cb - sb * sd * cc;
|
|
yx = cd * sb * ca + sd * cc * cb * ca - sa * sd * sc;
|
|
zx = cd * sb * sa + sd * cc * cb * sa + ca * sd * sc;
|
|
xy = -sd * cb - sb * cd * cc;
|
|
yy = -sd * sb * ca + cd * cc * cb * ca - sa * cd * sc;
|
|
zy = -sd * sb * sa + cd * cc * cb * sa + ca * cd * sc;
|
|
xz = sb * sc;
|
|
yz = -sc * cb * ca - sa * cc;
|
|
zz = -sc * cb * sa + cc * ca;
|
|
|
|
if (tt2 == 0) {
|
|
// Perform rotation from epoch to J2000.0.
|
|
return [
|
|
xx * pos1[0] + xy * pos1[1] + xz * pos1[2],
|
|
yx * pos1[0] + yy * pos1[1] + yz * pos1[2],
|
|
zx * pos1[0] + zy * pos1[1] + zz * pos1[2]
|
|
];
|
|
}
|
|
|
|
// Perform rotation from J2000.0 to epoch.
|
|
return [
|
|
xx * pos1[0] + yx * pos1[1] + zx * pos1[2],
|
|
xy * pos1[0] + yy * pos1[1] + zy * pos1[2],
|
|
xz * pos1[0] + yz * pos1[1] + zz * pos1[2]
|
|
];
|
|
}
|
|
|
|
function era(time) { // Earth Rotation Angle
|
|
const thet1 = 0.7790572732640 + 0.00273781191135448 * time.ut;
|
|
const thet3 = time.ut % 1;
|
|
let theta = 360 * ((thet1 + thet3) % 1);
|
|
if (theta < 0) {
|
|
theta += 360;
|
|
}
|
|
return theta;
|
|
}
|
|
|
|
function sidereal_time(time) { // calculates Greenwich Apparent Sidereal Time (GAST)
|
|
const t = time.tt / 36525;
|
|
let eqeq = 15 * e_tilt(time).ee; // Replace with eqeq=0 to get GMST instead of GAST (if we ever need it)
|
|
const theta = era(time);
|
|
const st = (eqeq + 0.014506 +
|
|
(((( - 0.0000000368 * t
|
|
- 0.000029956 ) * t
|
|
- 0.00000044 ) * t
|
|
+ 1.3915817 ) * t
|
|
+ 4612.156534 ) * t);
|
|
|
|
let gst = ((st/3600 + theta) % 360) / 15;
|
|
if (gst < 0) {
|
|
gst += 24;
|
|
}
|
|
return gst;
|
|
}
|
|
|
|
function terra(observer, st) {
|
|
const erad_km = ERAD / 1000;
|
|
const df = 1 - 0.003352819697896; // flattening of the Earth
|
|
const df2 = df * df;
|
|
const phi = observer.latitude * DEG2RAD;
|
|
const sinphi = Math.sin(phi);
|
|
const cosphi = Math.cos(phi);
|
|
const c = 1 / Math.sqrt(cosphi*cosphi + df2*sinphi*sinphi);
|
|
const s = df2 * c;
|
|
const ht_km = observer.height / 1000;
|
|
const ach = erad_km*c + ht_km;
|
|
const ash = erad_km*s + ht_km;
|
|
const stlocl = (15*st + observer.longitude) * DEG2RAD;
|
|
const sinst = Math.sin(stlocl);
|
|
const cosst = Math.cos(stlocl);
|
|
return {
|
|
pos: [ach*cosphi*cosst/KM_PER_AU, ach*cosphi*sinst/KM_PER_AU, ash*sinphi/KM_PER_AU],
|
|
vel: [-ANGVEL*ach*cosphi*sinst*86400, ANGVEL*ach*cosphi*cosst*86400, 0]
|
|
};
|
|
}
|
|
|
|
function nutation(time, direction, pos) {
|
|
const tilt = e_tilt(time);
|
|
const oblm = tilt.mobl * DEG2RAD;
|
|
const oblt = tilt.tobl * DEG2RAD;
|
|
const psi = tilt.dpsi * ASEC2RAD;
|
|
const cobm = Math.cos(oblm);
|
|
const sobm = Math.sin(oblm);
|
|
const cobt = Math.cos(oblt);
|
|
const sobt = Math.sin(oblt);
|
|
const cpsi = Math.cos(psi);
|
|
const spsi = Math.sin(psi);
|
|
|
|
const xx = cpsi;
|
|
const yx = -spsi * cobm;
|
|
const zx = -spsi * sobm;
|
|
const xy = spsi * cobt;
|
|
const yy = cpsi * cobm * cobt + sobm * sobt;
|
|
const zy = cpsi * sobm * cobt - cobm * sobt;
|
|
const xz = spsi * sobt;
|
|
const yz = cpsi * cobm * sobt - sobm * cobt;
|
|
const zz = cpsi * sobm * sobt + cobm * cobt;
|
|
|
|
if (direction === 0) {
|
|
// forward rotation
|
|
return [
|
|
xx * pos[0] + yx * pos[1] + zx * pos[2],
|
|
xy * pos[0] + yy * pos[1] + zy * pos[2],
|
|
xz * pos[0] + yz * pos[1] + zz * pos[2]
|
|
];
|
|
}
|
|
|
|
// inverse rotation
|
|
return [
|
|
xx * pos[0] + xy * pos[1] + xz * pos[2],
|
|
yx * pos[0] + yy * pos[1] + yz * pos[2],
|
|
zx * pos[0] + zy * pos[1] + zz * pos[2]
|
|
];
|
|
}
|
|
|
|
function geo_pos(time, observer) {
|
|
const gast = sidereal_time(time);
|
|
const pos1 = terra(observer, gast).pos;
|
|
const pos2 = nutation(time, -1, pos1);
|
|
const pos3 = precession(time.tt, pos2, 0);
|
|
return pos3;
|
|
}
|
|
|
|
/**
|
|
* Holds the Cartesian coordinates of a vector in 3D space,
|
|
* along with the time at which the vector is valid.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} x The x-coordinate expressed in astronomical units (AU).
|
|
* @property {number} y The y-coordinate expressed in astronomical units (AU).
|
|
* @property {number} z The z-coordinate expressed in astronomical units (AU).
|
|
* @property {Astronomy.AstroTime} t The time at which the vector is valid.
|
|
*/
|
|
class Vector {
|
|
constructor(x, y, z, t) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
this.t = t;
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the vector in astronomical units (AU).
|
|
* @returns {number}
|
|
*/
|
|
Length() {
|
|
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds right ascension, declination, and distance of a celestial object.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} ra
|
|
* Right ascension in sidereal hours: [0, 24).
|
|
*
|
|
* @property {number} dec
|
|
* Declination in degrees: [-90, +90].
|
|
*
|
|
* @property {number} dist
|
|
* Distance to the celestial object expressed in
|
|
* <a href="https://en.wikipedia.org/wiki/Astronomical_unit">astronomical units</a> (AU).
|
|
*/
|
|
class EquatorialCoordinates {
|
|
constructor(ra, dec, dist) {
|
|
this.ra = ra;
|
|
this.dec = dec;
|
|
this.dist = dist;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds azimuth (compass direction) and altitude (angle above/below the horizon)
|
|
* of a celestial object as seen by an observer at a particular location on the Earth's surface.
|
|
* Also holds right ascension and declination of the same object.
|
|
* All of these coordinates are optionally adjusted for atmospheric refraction;
|
|
* therefore the right ascension and declination values may not exactly match
|
|
* those found inside a corresponding {@link Astronomy.EquatorialCoordinates} object.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} azimuth
|
|
* A horizontal compass direction angle in degrees measured starting at north
|
|
* and increasing positively toward the east.
|
|
* The value is in the range [0, 360).
|
|
* North = 0, east = 90, south = 180, west = 270.
|
|
*
|
|
* @property {number} altitude
|
|
* A vertical angle in degrees above (positive) or below (negative) the horizon.
|
|
* The value is in the range [-90, +90].
|
|
* The altitude angle is optionally adjusted upward due to atmospheric refraction.
|
|
*
|
|
* @property {number} ra
|
|
* The right ascension of the celestial body in sidereal hours.
|
|
* The value is in the reange [0, 24).
|
|
* If <code>altitude</code> was adjusted for atmospheric reaction, <code>ra</code>
|
|
* is likewise adjusted.
|
|
*
|
|
* @property {number} dec
|
|
* The declination of of the celestial body in degrees.
|
|
* The value in the range [-90, +90].
|
|
* If <code>altitude</code> was adjusted for atmospheric reaction, <code>dec</code>
|
|
* is likewise adjusted.
|
|
*/
|
|
class HorizontalCoordinates {
|
|
constructor(azimuth, altitude, ra, dec) {
|
|
this.azimuth = azimuth;
|
|
this.altitude = altitude;
|
|
this.ra = ra;
|
|
this.dec = dec;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds ecliptic coordinates of a celestial body.
|
|
* The origin and date of the coordinate system may vary depending on the caller's usage.
|
|
* In general, ecliptic coordinates are measured with respect to the mean plane of the Earth's
|
|
* orbit around the Sun.
|
|
* Includes Cartesian coordinates <code>(ex, ey, ez)</code> measured in
|
|
* <a href="https://en.wikipedia.org/wiki/Astronomical_unit">astronomical units</a> (AU)
|
|
* and spherical coordinates <code>(elon, elat)</code> measured in degrees.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} ex
|
|
* The Cartesian x-coordinate of the body in astronomical units (AU).
|
|
* The x-axis is within the ecliptic plane and is oriented in the direction of the
|
|
* <a href="https://en.wikipedia.org/wiki/Equinox_(celestial_coordinates)">equinox</a>.
|
|
*
|
|
* @property {number} ey
|
|
* The Cartesian y-coordinate of the body in astronomical units (AU).
|
|
* The y-axis is within the ecliptic plane and is oriented 90 degrees
|
|
* counterclockwise from the equinox, as seen from above the Sun's north pole.
|
|
*
|
|
* @property {number} ez
|
|
* The Cartesian z-coordinate of the body in astronomical units (AU).
|
|
* The z-axis is oriented perpendicular to the ecliptic plane,
|
|
* along the direction of the Sun's north pole.
|
|
*
|
|
* @property {number} elat
|
|
* The ecliptic latitude of the body in degrees.
|
|
* This is the angle north or south of the ecliptic plane.
|
|
* The value is in the range [-90, +90].
|
|
* Positive values are north and negative values are south.
|
|
*
|
|
* @property {number} elon
|
|
* The ecliptic longitude of the body in degrees.
|
|
* This is the angle measured counterclockwise around the ecliptic plane,
|
|
* as seen from above the Sun's north pole.
|
|
* This is the same direction that the Earth orbits around the Sun.
|
|
* The angle is measured starting at 0 from the equinox and increases
|
|
* up to 360 degrees.
|
|
*/
|
|
class EclipticCoordinates {
|
|
constructor(ex, ey, ez, elat, elon) {
|
|
this.ex = ex;
|
|
this.ey = ey;
|
|
this.ez = ez;
|
|
this.elat = elat;
|
|
this.elon = elon;
|
|
}
|
|
}
|
|
|
|
function vector2radec(pos)
|
|
{
|
|
const xyproj = pos[0]*pos[0] + pos[1]*pos[1];
|
|
const dist = Math.sqrt(xyproj + pos[2]*pos[2]);
|
|
if (xyproj === 0)
|
|
{
|
|
if (pos[2] === 0)
|
|
throw 'Indeterminate sky coordinates';
|
|
|
|
if (pos[2] < 0)
|
|
return { ra:0, dec:-90, dist:dist };
|
|
|
|
return { ra:0, dec:+90, dist:dist };
|
|
}
|
|
|
|
let ra = Math.atan2(pos[1], pos[0]) / (DEG2RAD * 15);
|
|
if (ra < 0) {
|
|
ra += 24;
|
|
}
|
|
let dec = Math.atan2(pos[2], Math.sqrt(xyproj)) / DEG2RAD;
|
|
return new EquatorialCoordinates(ra, dec, dist);
|
|
}
|
|
|
|
function spin(angle, pos1) {
|
|
const angr = angle * DEG2RAD;
|
|
const cosang = Math.cos(angr);
|
|
const sinang = Math.sin(angr);
|
|
const xx = cosang;
|
|
const yx = sinang;
|
|
const zx = 0;
|
|
const xy = -sinang;
|
|
const yy = cosang;
|
|
const zy = 0;
|
|
const xz = 0;
|
|
const yz = 0;
|
|
const zz = 1;
|
|
let pos2 = [
|
|
xx*pos1[0] + yx*pos1[1] + zx*pos1[2],
|
|
xy*pos1[0] + yy*pos1[1] + zy*pos1[2],
|
|
xz*pos1[0] + yz*pos1[1] + zz*pos1[2]
|
|
];
|
|
return pos2;
|
|
}
|
|
|
|
/**
|
|
* Given a date and time, a geographic location of an observer on the Earth, and
|
|
* equatorial coordinates (right ascension and declination) of a celestial body,
|
|
* returns horizontal coordinates (azimuth and altitude angles) for that body
|
|
* as seen by that observer. Allows optional correction for atmospheric refraction.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} date
|
|
* The date and time for which to find horizontal coordinates.
|
|
*
|
|
* @param {Astronomy.Observer} observer
|
|
* The location of the observer for which to find horizontal coordinates.
|
|
*
|
|
* @param {number} ra
|
|
* Right ascension in sidereal hours of the celestial object,
|
|
* referred to the mean equinox of date for the J2000 epoch.
|
|
*
|
|
* @param {number} dec
|
|
* Declination in degrees of the celestial object,
|
|
* referred to the mean equator of date for the J2000 epoch.
|
|
* Positive values are north of the celestial equator and negative values are south.
|
|
*
|
|
* @param {string} refraction
|
|
* If omitted or has a false-like value (false, null, undefined, etc.)
|
|
* the calculations are performed without any correction for atmospheric
|
|
* refraction. If the value is the string <code>"normal"</code>,
|
|
* uses the recommended refraction correction based on Meeus "Astronomical Algorithms"
|
|
* with a linear taper more than 1 degree below the horizon. The linear
|
|
* taper causes the refraction to linearly approach 0 as the altitude of the
|
|
* body approaches the nadir (-90 degrees).
|
|
* If the value is the string <code>"jplhor"</code>, uses a JPL Horizons
|
|
* compatible formula. This is the same algorithm as <code>"normal"</code>,
|
|
* only without linear tapering; this can result in physically impossible
|
|
* altitudes of less than -90 degrees, which may cause problems for some applications.
|
|
* (The <code>"jplhor"</code> option was created for unit testing against data
|
|
* generated by JPL Horizons, and is otherwise not recommended for use.)
|
|
*
|
|
* @returns {Astronomy.HorizontalCoordinates}
|
|
*/
|
|
Astronomy.Horizon = function(date, observer, ra, dec, refraction) { // based on NOVAS equ2hor()
|
|
let time = Astronomy.MakeTime(date);
|
|
|
|
const sinlat = Math.sin(observer.latitude * DEG2RAD);
|
|
const coslat = Math.cos(observer.latitude * DEG2RAD);
|
|
const sinlon = Math.sin(observer.longitude * DEG2RAD);
|
|
const coslon = Math.cos(observer.longitude * DEG2RAD);
|
|
const sindc = Math.sin(dec * DEG2RAD);
|
|
const cosdc = Math.cos(dec * DEG2RAD);
|
|
const sinra = Math.sin(ra * 15 * DEG2RAD);
|
|
const cosra = Math.cos(ra * 15 * DEG2RAD);
|
|
let uze = [coslat*coslon, coslat*sinlon, sinlat];
|
|
let une = [-sinlat*coslon, -sinlat*sinlon, coslat];
|
|
let uwe = [sinlon, -coslon, 0];
|
|
|
|
const spin_angle = -15 * sidereal_time(time);
|
|
let uz = spin(spin_angle, uze);
|
|
let un = spin(spin_angle, une);
|
|
let uw = spin(spin_angle, uwe);
|
|
|
|
let p = [cosdc*cosra, cosdc*sinra, sindc];
|
|
|
|
const pz = p[0]*uz[0] + p[1]*uz[1] + p[2]*uz[2];
|
|
const pn = p[0]*un[0] + p[1]*un[1] + p[2]*un[2];
|
|
const pw = p[0]*uw[0] + p[1]*uw[1] + p[2]*uw[2];
|
|
|
|
let proj = Math.sqrt(pn*pn + pw*pw);
|
|
let az = 0;
|
|
if (proj > 0) {
|
|
az = -Math.atan2(pw, pn) * RAD2DEG;
|
|
if (az < 0) az += 360;
|
|
if (az >= 360) az -= 360;
|
|
}
|
|
let zd = Math.atan2(proj, pz) * RAD2DEG;
|
|
let out_ra = ra;
|
|
let out_dec = dec;
|
|
|
|
if (refraction) {
|
|
let refr, j;
|
|
let zd0 = zd;
|
|
|
|
if (refraction === 'normal' || refraction === 'jplhor') {
|
|
// http://extras.springer.com/1999/978-1-4471-0555-8/chap4/horizons/horizons.pdf
|
|
// JPL Horizons says it uses refraction algorithm from
|
|
// Meeus "Astronomical Algorithms", 1991, p. 101-102.
|
|
// I found the following Go implementation:
|
|
// https://github.com/soniakeys/meeus/blob/master/v3/refraction/refract.go
|
|
// This is a translation from the function "Saemundsson" there.
|
|
// I found experimentally that JPL Horizons clamps the angle to 1 degree below the horizon.
|
|
// This is important because the 'refr' formula below goes crazy near hd = -5.11.
|
|
let hd = Math.max(-1, 90 - zd);
|
|
refr = (1.02 / Math.tan((hd+10.3/(hd+5.11))*DEG2RAD)) / 60;
|
|
|
|
if (refraction === 'normal' && zd > 91) {
|
|
// In "normal" mode we gradually reduce refraction toward the nadir
|
|
// so that we never get an altitude angle less than -90 degrees.
|
|
// When horizon angle is -1 degrees, zd = 91, and the factor is exactly 1.
|
|
// As zd approaches 180 (the nadir), the fraction approaches 0 linearly.
|
|
refr *= (180 - zd) / 89;
|
|
}
|
|
|
|
zd -= refr;
|
|
} else {
|
|
throw 'If specified, refraction must be one of: "normal", "jplhor".';
|
|
}
|
|
|
|
if (refr > 0.0 && zd > 3.0e-4) {
|
|
const sinzd = Math.sin(zd * DEG2RAD);
|
|
const coszd = Math.cos(zd * DEG2RAD);
|
|
const sinzd0 = Math.sin(zd0 * DEG2RAD);
|
|
const coszd0 = Math.cos(zd0 * DEG2RAD);
|
|
var pr = [];
|
|
for (j=0; j<3; ++j) {
|
|
pr.push(((p[j] - coszd0 * uz[j]) / sinzd0)*sinzd + uz[j]*coszd);
|
|
}
|
|
proj = Math.sqrt(pr[0]*pr[0] + pr[1]*pr[1]);
|
|
if (proj > 0) {
|
|
out_ra = Math.atan2(pr[1], pr[0]) * RAD2DEG / 15;
|
|
if (out_ra < 0) {
|
|
out_ra += 24;
|
|
}
|
|
if (out_ra >= 24) {
|
|
out_ra -= 24;
|
|
}
|
|
} else {
|
|
out_ra = 0;
|
|
}
|
|
out_dec = Math.atan2(pr[2], proj) * RAD2DEG;
|
|
}
|
|
}
|
|
|
|
return new HorizontalCoordinates(az, 90-zd, out_ra, out_dec);
|
|
}
|
|
|
|
/**
|
|
* Represents the geographic location of an observer on the surface of the Earth.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} latitude_degrees
|
|
* The observer's geographic latitude in degrees north of the Earth's equator.
|
|
* The value is negative for observers south of the equator.
|
|
* Must be in the range -90 to +90.
|
|
*
|
|
* @property {number} longitude_degrees
|
|
* The observer's geographic longitude in degrees east of the prime meridian
|
|
* passing through Greenwich, England.
|
|
* The value is negative for observers west of the prime meridian.
|
|
* The value should be kept in the range -180 to +180 to minimize floating point errors.
|
|
*
|
|
* @property {number} height_in_meters
|
|
* The observer's elevation above mean sea level, expressed in meters.
|
|
*/
|
|
class Observer {
|
|
constructor(latitude_degrees, longitude_degrees, height_in_meters) {
|
|
this.latitude = latitude_degrees;
|
|
this.longitude = longitude_degrees;
|
|
this.height = height_in_meters;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates an {@link Astronomy.Observer} object that represents a location
|
|
* on the surface of the Earth from which observations are made.
|
|
*
|
|
* @param {number} latitude_degrees
|
|
* The observer's geographic latitude in degrees north of the Earth's equator.
|
|
* The value is negative for observers south of the equator.
|
|
* Must be in the range -90 to +90.
|
|
*
|
|
* @param {number} longitude_degrees
|
|
* The observer's geographic longitude in degrees east of the prime meridian
|
|
* passing through Greenwich, England.
|
|
* The value is negative for observers west of the prime meridian.
|
|
* The value should be kept in the range -180 to +180 to minimize floating point errors.
|
|
*
|
|
* @param {number} height_in_meters
|
|
* The observer's elevation above mean sea level, expressed in meters.
|
|
* If omitted, the elevation is assumed to be 0 meters.
|
|
*/
|
|
Astronomy.MakeObserver = function(latitude_degrees, longitude_degrees, height_in_meters) {
|
|
return new Observer(latitude_degrees, longitude_degrees, height_in_meters || 0);
|
|
}
|
|
|
|
/**
|
|
* Returns apparent geocentric true ecliptic coordinates of date for the Sun.
|
|
* <i>Geocentric</i> means coordinates as the Sun would appear to a hypothetical observer
|
|
* at the center of the Earth.
|
|
* <i>Ecliptic coordinates of date</i> are measured along the plane of the Earth's mean
|
|
* orbit around the Sun, using the
|
|
* <a href="https://en.wikipedia.org/wiki/Equinox_(celestial_coordinates)">equinox</a>
|
|
* of the Earth as adjusted for precession and nutation of the Earth's
|
|
* axis of rotation on the given date.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} date
|
|
* The date and time at which to calculate the Sun's apparent location as seen from
|
|
* the center of the Earth.
|
|
*
|
|
* @returns {Astronomy.EclipticCoordinates}
|
|
*/
|
|
Astronomy.SunPosition = function(date) {
|
|
// Correct for light travel time from the Sun.
|
|
// This is really the same as correcting for aberration.
|
|
// Otherwise season calculations (equinox, solstice) will all be early by about 8 minutes!
|
|
const time = Astronomy.MakeTime(date).AddDays(-1 / C_AUDAY);
|
|
|
|
// Get heliocentric cartesian coordinates of Earth in J2000.
|
|
const earth2000 = CalcVsop(vsop.Earth, time);
|
|
|
|
// Convert to geocentric location of the Sun.
|
|
const sun2000 = [-earth2000.x, -earth2000.y, -earth2000.z];
|
|
|
|
// Convert to equator-of-date equatorial cartesian coordinates.
|
|
const stemp = precession(0, sun2000, time.tt);
|
|
const sun_ofdate = nutation(time, 0, stemp);
|
|
|
|
// Convert to ecliptic coordinates of date.
|
|
const true_obliq = DEG2RAD * e_tilt(time).tobl;
|
|
const cos_ob = Math.cos(true_obliq);
|
|
const sin_ob = Math.sin(true_obliq);
|
|
|
|
const gx = sun_ofdate[0];
|
|
const gy = sun_ofdate[1];
|
|
const gz = sun_ofdate[2];
|
|
|
|
const sun_ecliptic = RotateEquatorialToEcliptic(gx, gy, gz, cos_ob, sin_ob);
|
|
return sun_ecliptic;
|
|
}
|
|
|
|
/**
|
|
* Returns topocentric equatorial coordinates (right ascension and declination)
|
|
* in one of two different systems: J2000 or true-equator-of-date.
|
|
* Allows optional correction for aberration.
|
|
* Always corrects for light travel time (represents the object as seen by the observer
|
|
* with light traveling to the Earth at finite speed, not where the object is right now).
|
|
* <i>Topocentric</i> refers to a position as seen by an observer on the surface of the Earth.
|
|
* This function corrects for
|
|
* <a href="https://en.wikipedia.org/wiki/Parallax">parallax</a>
|
|
* of the object between a geocentric observer and a topocentric observer.
|
|
* This is most significant for the Moon, because it is so close to the Earth.
|
|
* However, it can have a small effect on the apparent positions of other bodies.
|
|
*
|
|
* @param {string} body
|
|
* The name of the body for which to find equatorial coordinates.
|
|
* Not allowed to be <code>"Earth"</code>.
|
|
*
|
|
* @param {(Date | number | Astronomy.Time)} date
|
|
* Specifies the date and time at which the body is to be observed.
|
|
*
|
|
* @param {Astronomy.Observer} observer
|
|
* The location on the Earth of the observer.
|
|
* Call {@link Astronomy.MakeObserver} to create an observer object.
|
|
*
|
|
* @param {bool} ofdate
|
|
* Pass <code>true</code> to return equatorial coordinates of date,
|
|
* i.e. corrected for precession and nutation at the given date.
|
|
* This is needed to get correct horizontal coordinates when you call
|
|
* {@link Astronomy.Horizon}.
|
|
* Pass <code>false</code> to return equatorial coordinates in the J2000 system.
|
|
*
|
|
* @param {bool} aberration
|
|
* Pass <code>true</code> to correct for
|
|
* <a href="https://en.wikipedia.org/wiki/Aberration_of_light">aberration</a>,
|
|
* or <code>false</code> to leave uncorrected.
|
|
*
|
|
* @returns {Astronomy.EquatorialCoordinates}
|
|
* The topocentric coordinates of the body as adjusted for the given observer.
|
|
*/
|
|
Astronomy.Equator = function(body, date, observer, ofdate, aberration) {
|
|
const time = Astronomy.MakeTime(date);
|
|
const gc_observer = geo_pos(time, observer);
|
|
const gc = Astronomy.GeoVector(body, time, aberration);
|
|
const j2000 = [
|
|
gc.x - gc_observer[0],
|
|
gc.y - gc_observer[1],
|
|
gc.z - gc_observer[2]
|
|
];
|
|
|
|
if (!ofdate)
|
|
return vector2radec(j2000);
|
|
|
|
const temp = precession(0, j2000, time.tt);
|
|
const datevect = nutation(time, 0, temp);
|
|
return vector2radec(datevect);
|
|
}
|
|
|
|
function RotateEquatorialToEcliptic(gx, gy, gz, cos_ob, sin_ob) {
|
|
// Rotate equatorial vector to obtain ecliptic vector.
|
|
const ex = gx;
|
|
const ey = gy*cos_ob + gz*sin_ob;
|
|
const ez = -gy*sin_ob + gz*cos_ob;
|
|
|
|
const xyproj = Math.sqrt(ex*ex + ey*ey);
|
|
let elon = 0;
|
|
if (xyproj > 0) {
|
|
elon = RAD2DEG * Math.atan2(ey, ex);
|
|
if (elon < 0) elon += 360;
|
|
}
|
|
let elat = RAD2DEG * Math.atan2(ez, xyproj);
|
|
return new EclipticCoordinates(ex, ey, ez, elat, elon);
|
|
}
|
|
|
|
/**
|
|
* Given J2000 equatorial Cartesian coordinates,
|
|
* returns J2000 ecliptic latitude, longitude, and cartesian coordinates.
|
|
* You can call {@link Astronomy.GeoVector} and use its (x, y, z) return values
|
|
* to pass into this function.
|
|
*
|
|
* @param {number} gx
|
|
* The x-coordinate of a 3D vector in the J2000 equatorial coordinate system.
|
|
*
|
|
* @param {number} gy
|
|
* The y-coordinate of a 3D vector in the J2000 equatorial coordinate system.
|
|
*
|
|
* @param {number} gz
|
|
* The z-coordinate of a 3D vector in the J2000 equatorial coordinate system.
|
|
*
|
|
* @returns {Astronomy.EclipticCoordinates}
|
|
*/
|
|
Astronomy.Ecliptic = function(gx, gy, gz) {
|
|
// Based on NOVAS functions equ2ecl() and equ2ecl_vec().
|
|
if (ob2000 === undefined) {
|
|
// Lazy-evaluate and keep the mean obliquity of the ecliptic at J2000.
|
|
// This way we don't need to crunch the numbers more than once.
|
|
ob2000 = DEG2RAD * e_tilt(Astronomy.MakeTime(J2000)).mobl;
|
|
cos_ob2000 = Math.cos(ob2000);
|
|
sin_ob2000 = Math.sin(ob2000);
|
|
}
|
|
|
|
return RotateEquatorialToEcliptic(gx, gy, gz, cos_ob2000, sin_ob2000);
|
|
}
|
|
|
|
/**
|
|
* Calculates the geocentric Cartesian coordinates for the Moon in the J2000 equatorial system.
|
|
* Based on the Nautical Almanac Office's <i>Improved Lunar Ephemeris</i> of 1954,
|
|
* which in turn derives from E. W. Brown's lunar theories.
|
|
* Adapted from Turbo Pascal code from the book
|
|
* <a href="https://www.springer.com/us/book/9783540672210">Astronomy on the Personal Computer</a>
|
|
* by Montenbruck and Pfleger.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} date
|
|
* The date and time for which to calculate the Moon's geocentric position.
|
|
*
|
|
* @returns {Astronomy.Vector}
|
|
*/
|
|
Astronomy.GeoMoon = function(date) {
|
|
var time = Astronomy.MakeTime(date);
|
|
var moon = CalcMoon(time);
|
|
|
|
// Convert geocentric ecliptic spherical coords to cartesian coords.
|
|
var dist_cos_lat = moon.distance_au * Math.cos(moon.geo_eclip_lat);
|
|
var gepos = [
|
|
dist_cos_lat * Math.cos(moon.geo_eclip_lon),
|
|
dist_cos_lat * Math.sin(moon.geo_eclip_lon),
|
|
moon.distance_au * Math.sin(moon.geo_eclip_lat)
|
|
];
|
|
|
|
// Convert ecliptic coordinates to equatorial coordinates, both in mean equinox of date.
|
|
var mpos1 = ecl2equ_vec(time, gepos);
|
|
|
|
// Convert from mean equinox of date to J2000...
|
|
var mpos2 = precession(time.tt, mpos1, 0);
|
|
|
|
return new Vector(mpos2[0], mpos2[1], mpos2[2], time);
|
|
}
|
|
|
|
function CalcVsop(model, time) {
|
|
var spher = [], eclip, r_coslat;
|
|
var t = time.tt / 365250; // millennia since 2000
|
|
var formula, series, term, tpower, sum, coord;
|
|
for (formula of model) {
|
|
tpower = 1;
|
|
coord = 0;
|
|
for (series of formula) {
|
|
sum = 0;
|
|
for (term of series) {
|
|
sum += term[0] * Math.cos(term[1] + (t * term[2]));
|
|
}
|
|
coord += tpower * sum;
|
|
tpower *= t;
|
|
}
|
|
spher.push(coord);
|
|
}
|
|
|
|
// Convert spherical coordinates to ecliptic cartesian coordinates.
|
|
r_coslat = spher[2] * Math.cos(spher[1]);
|
|
eclip = [
|
|
r_coslat * Math.cos(spher[0]),
|
|
r_coslat * Math.sin(spher[0]),
|
|
spher[2] * Math.sin(spher[1])
|
|
];
|
|
|
|
// Convert ecliptic cartesian coordinates to equatorial cartesian coordinates.
|
|
return new Vector(
|
|
eclip[0] + 0.000000440360*eclip[1] - 0.000000190919*eclip[2],
|
|
-0.000000479966*eclip[0] + 0.917482137087*eclip[1] - 0.397776982902*eclip[2],
|
|
0.397776982902*eclip[1] + 0.917482137087*eclip[2],
|
|
time
|
|
);
|
|
}
|
|
|
|
function ChebScale(t_min, t_max, t) {
|
|
return (2*t - (t_max + t_min)) / (t_max - t_min);
|
|
}
|
|
|
|
function CalcChebyshev(model, time) {
|
|
var record, x, k, d, sum, p0, p1, p2, pos;
|
|
|
|
// Search for a record that overlaps the given time value.
|
|
for (record of model) {
|
|
x = ChebScale(record.tt, record.tt + record.ndays, time.tt);
|
|
if (-1 <= x && x <= +1) {
|
|
pos = [];
|
|
for (d=0; d < 3; ++d) {
|
|
p0 = 1;
|
|
sum = record.coeff[0][d];
|
|
p1 = x;
|
|
sum += record.coeff[1][d] * p1;
|
|
for (k=2; k < record.coeff.length; ++k) {
|
|
p2 = (2 * x * p1) - p0;
|
|
sum += record.coeff[k][d] * p2;
|
|
p0 = p1;
|
|
p1 = p2;
|
|
}
|
|
pos.push(sum - record.coeff[0][d]/2);
|
|
}
|
|
return new Vector(pos[0], pos[1], pos[2], time);
|
|
}
|
|
}
|
|
throw `Cannot extrapolate Chebyshev model for given Terrestrial Time: ${time.tt}`;
|
|
}
|
|
|
|
/**
|
|
* Calculates heliocentric (i.e., with respect to the center of the Sun)
|
|
* Cartesian coordinates in the J2000 equatorial system of a celestial
|
|
* body at a specified time. The position is not corrected for light travel time or aberration.
|
|
*
|
|
* @param {string} body
|
|
* One of the strings
|
|
* <code>"Sun"</code>, <code>"Moon"</code>, <code>"Mercury"</code>, <code>"Venus"</code>,
|
|
* <code>"Earth"</code>, <code>"Mars"</code>, <code>"Jupiter"</code>, <code>"Saturn"</code>,
|
|
* <code>"Uranus"</code>, <code>"Neptune"</code>, or <code>"Pluto"</code>.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} date
|
|
* The date and time for which the body's position is to be calculated.
|
|
*
|
|
* @returns {Astronomy.Vector}
|
|
*/
|
|
Astronomy.HelioVector = function(body, date) {
|
|
var time = Astronomy.MakeTime(date);
|
|
if (body in vsop) {
|
|
return CalcVsop(vsop[body], time);
|
|
}
|
|
if (body in cheb) {
|
|
return CalcChebyshev(cheb[body], time);
|
|
}
|
|
if (body === 'Sun') {
|
|
return new Vector(0, 0, 0, time);
|
|
}
|
|
if (body === 'Moon') {
|
|
var e = CalcVsop(vsop.Earth, time);
|
|
var m = Astronomy.GeoMoon(time);
|
|
return new Vector(e.x+m.x, e.y+m.y, e.z+m.z, time);
|
|
}
|
|
throw `Astronomy.HelioVector: Unknown body "${body}"`;
|
|
};
|
|
|
|
/**
|
|
* Calculates geocentric (i.e., with respect to the center of the Earth)
|
|
* Cartesian coordinates in the J2000 equatorial system of a celestial
|
|
* body at a specified time. The position is always corrected for light travel time:
|
|
* this means the position of the body is "back-dated" based on how long it
|
|
* takes light to travel from the body to an observer on the Earth.
|
|
* Also, the position can optionally be corrected for aberration, an effect
|
|
* causing the apparent direction of the body to be shifted based on
|
|
* transverse movement of the Earth with respect to the rays of light
|
|
* coming from that body.
|
|
*
|
|
* @param {string} body
|
|
* One of the strings
|
|
* <code>"Sun"</code>, <code>"Moon"</code>, <code>"Mercury"</code>, <code>"Venus"</code>,
|
|
* <code>"Earth"</code>, <code>"Mars"</code>, <code>"Jupiter"</code>, <code>"Saturn"</code>,
|
|
* <code>"Uranus"</code>, <code>"Neptune"</code>, or <code>"Pluto"</code>.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} date
|
|
* The date and time for which the body's position is to be calculated.
|
|
*
|
|
* @param {bool} aberration
|
|
* Pass <code>true</code> to correct for
|
|
* <a href="https://en.wikipedia.org/wiki/Aberration_of_light">aberration</a>,
|
|
* or <code>false</code> to leave uncorrected.
|
|
*
|
|
* @returns {Astronomy.Vector}
|
|
*/
|
|
Astronomy.GeoVector = function(body, date, aberration) {
|
|
const time = Astronomy.MakeTime(date);
|
|
if (body === 'Moon') {
|
|
return Astronomy.GeoMoon(time);
|
|
}
|
|
if (body === 'Earth') {
|
|
return new Vector(0, 0, 0, time);
|
|
}
|
|
|
|
let earth;
|
|
if (!aberration) {
|
|
// No aberration, so calculate Earth's position once, at the time of observation.
|
|
earth = CalcVsop(vsop.Earth, time);
|
|
}
|
|
|
|
// Correct for light-travel time, to get position of body as seen from Earth's center.
|
|
let h, geo, dt;
|
|
let ltime = time;
|
|
for (let iter=0; iter < 10; ++iter) {
|
|
h = Astronomy.HelioVector(body, ltime);
|
|
|
|
if (aberration) {
|
|
/*
|
|
Include aberration, so make a good first-order approximation
|
|
by backdating the Earth's position also.
|
|
This is confusing, but it works for objects within the Solar System
|
|
because the distance the Earth moves in that small amount of light
|
|
travel time (a few minutes to a few hours) is well approximated
|
|
by a line segment that substends the angle seen from the remote
|
|
body viewing Earth. That angle is pretty close to the aberration
|
|
angle of the moving Earth viewing the remote body.
|
|
In other words, both of the following approximate the aberration angle:
|
|
(transverse distance Earth moves) / (distance to body)
|
|
(transverse speed of Earth) / (speed of light).
|
|
*/
|
|
earth = CalcVsop(vsop.Earth, ltime);
|
|
}
|
|
|
|
geo = new Vector(h.x-earth.x, h.y-earth.y, h.z-earth.z, time);
|
|
if (body === 'Sun') {
|
|
return geo; // The Sun's heliocentric coordinates are always (0,0,0). No need to correct.
|
|
}
|
|
let ltime2 = time.AddDays(-geo.Length() / C_AUDAY);
|
|
dt = Math.abs(ltime2.tt - ltime.tt);
|
|
if (dt < 1.0e-9) {
|
|
return geo;
|
|
}
|
|
ltime = ltime2;
|
|
}
|
|
throw `Light-travel time solver did not converge: dt=${dt}`;
|
|
}
|
|
|
|
function QuadInterp(tm, dt, fa, fm, fb) {
|
|
let Q = (fb + fa)/2 - fm;
|
|
let R = (fb - fa)/2;
|
|
let S = fm;
|
|
let x;
|
|
|
|
if (Q == 0) {
|
|
// This is a line, not a parabola.
|
|
if (R == 0) {
|
|
// This is a HORIZONTAL line... can't make progress!
|
|
return null;
|
|
}
|
|
x = -S / R;
|
|
if (x < -1 || x > +1) return null; // out of bounds
|
|
} else {
|
|
// It really is a parabola. Find roots x1, x2.
|
|
let u = R*R - 4*Q*S;
|
|
if (u <= 0) return null;
|
|
let ru = Math.sqrt(u);
|
|
let x1 = (-R + ru) / (2 * Q);
|
|
let x2 = (-R - ru) / (2 * Q);
|
|
|
|
if (-1 <= x1 && x1 <= +1) {
|
|
if (-1 <= x2 && x2 <= +1) return null;
|
|
x = x1;
|
|
} else if (-1 <= x2 && x2 <= +1) {
|
|
x = x2;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
let t = tm + x*dt;
|
|
let df_dt = (2*Q*x + R) / dt;
|
|
return { x:x, t:t, df_dt:df_dt };
|
|
}
|
|
|
|
/**
|
|
* A continuous function of time used in a call to the <code>Search</code> function.
|
|
*
|
|
* @callback ContinuousFunction
|
|
* @memberof Astronomy
|
|
* @param {Astronomy.AstroTime} t The time at which to evaluate the function.
|
|
* @returns {number}
|
|
*/
|
|
|
|
/**
|
|
* Options for the {@link Astronomy.Search} function.
|
|
* @typedef {Object} SearchOptions
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {(number|null)} dt_tolerance_seconds
|
|
* The number of seconds for a time window smaller than which the search
|
|
* is considered successful. Using too large a tolerance can result in
|
|
* an inaccurate time estimate. Using too small a tolerance can cause
|
|
* excessive computation, or can even cause the search to fail because of
|
|
* limited floating-point resolution. Defaults to 1 second.
|
|
*
|
|
* @property {(number|null)} init_f1
|
|
* As an optimization, if the caller of {@link Astronomy.Search}
|
|
* has already calculated the value of the function being searched (the parameter <code>func</code>)
|
|
* at the time coordinate <code>t1</code>, it can pass in that value as <code>init_f1</code>.
|
|
* For very expensive calculations, this can measurably improve performance.
|
|
*
|
|
* @property {(number|null)} init_f2
|
|
* The same as <code>init_f1</code>, except this is the optional initial value of <code>func(t2)</code>
|
|
* instead of <code>func(t1)</code>.
|
|
*/
|
|
|
|
/**
|
|
* Search for next time <i>t</i> (such that <i>t</i> is between <code>t1</code> and <code>t2</code>)
|
|
* that <code>func(t)</code> crosses from a negative value to a non-negative value.
|
|
* The given function must have "smooth" behavior over the entire inclusive range [<code>t1</code>, <code>t2</code>],
|
|
* meaning that it behaves like a continuous differentiable function.
|
|
* It is not required that <code>t1</code> < <code>t2</code>; <code>t1</code> > <code>t2</code>
|
|
* allows searching backward in time.
|
|
* Note: <code>t1</code> and <code>t2</code> must be chosen such that there is no possibility
|
|
* of more than one zero-crossing (ascending or descending), or it is possible
|
|
* that the "wrong" event will be found (i.e. not the first event after t1)
|
|
* or even that the function will return null, indicating that no event was found.
|
|
*
|
|
* @param {Astronomy.ContinuousFunction} func
|
|
* The function to find an ascending zero crossing for.
|
|
* The function must accept a single parameter of type {@link Astronomy.AstroTime}
|
|
* and return a numeric value.
|
|
*
|
|
* @param {Astronomy.AstroTime} t1
|
|
* The lower time bound of a search window.
|
|
*
|
|
* @param {Astronomy.AstroTime} t2
|
|
* The upper time bound of a search window.
|
|
*
|
|
* @param {(null | Astronomy.SearchOptions)} options
|
|
* Options that can tune the behavior of the search.
|
|
* Most callers can omit this argument or pass in <code>null</code>.
|
|
*
|
|
* @returns {(null | Astronomy.AstroTime)}
|
|
* If the search is successful, returns the date and time of the solution.
|
|
* If the search fails, returns null.
|
|
*/
|
|
Astronomy.Search = function(func, t1, t2, options) {
|
|
const dt_tolerance_seconds = (options && options.dt_tolerance_seconds) || 1;
|
|
|
|
function f(t) {
|
|
++Perf.search_func;
|
|
return func(t);
|
|
}
|
|
|
|
const dt_days = Math.abs(dt_tolerance_seconds / SECONDS_PER_DAY);
|
|
|
|
++Perf.search;
|
|
|
|
let f1 = (options && options.init_f1) || f(t1);
|
|
let f2 = (options && options.init_f2) || f(t2);
|
|
let fmid;
|
|
|
|
let iter = 0;
|
|
let iter_limit = (options && options.iter_limit) || 20;
|
|
let calc_fmid = true;
|
|
while (true) {
|
|
if (++iter > iter_limit)
|
|
throw `Excessive iteration in Search()`;
|
|
|
|
let tmid = InterpolateTime(t1, t2, 0.5);
|
|
let dt = tmid.ut - t1.ut;
|
|
|
|
if (Math.abs(dt) < dt_days) {
|
|
// We are close enough to the event to stop the search.
|
|
return tmid;
|
|
}
|
|
|
|
if (calc_fmid)
|
|
fmid = f(tmid);
|
|
else
|
|
calc_fmid = true; // we already have the correct value of fmid from the previous loop
|
|
|
|
// Quadratic interpolation:
|
|
// Try to find a parabola that passes through the 3 points we have sampled:
|
|
// (t1,f1), (tmid,fmid), (t2,f2).
|
|
let q = QuadInterp(tmid.ut, t2.ut - tmid.ut, f1, fmid, f2);
|
|
|
|
// Did we find an approximate root-crossing?
|
|
if (q) {
|
|
// Evaluate the function at our candidate solution.
|
|
let tq = Astronomy.MakeTime(q.t);
|
|
let fq = f(tq);
|
|
|
|
if (q.df_dt !== 0) {
|
|
if (Math.abs(fq / q.df_dt) < dt_days) {
|
|
// The estimated time error is small enough that we can quit now.
|
|
return tq;
|
|
}
|
|
|
|
// Try guessing a tighter boundary with the interpolated root at the center.
|
|
let dt_guess = 1.2 * Math.abs(fq / q.df_dt);
|
|
if (dt_guess < dt/10) {
|
|
let tleft = tq.AddDays(-dt_guess);
|
|
let tright = tq.AddDays(+dt_guess);
|
|
if ((tleft.ut - t1.ut)*(tleft.ut - t2.ut) < 0) {
|
|
if ((tright.ut - t1.ut)*(tright.ut - t2.ut) < 0) {
|
|
let fleft = f(tleft);
|
|
let fright = f(tright);
|
|
if (fleft<0 && fright>=0) {
|
|
f1 = fleft;
|
|
f2 = fright;
|
|
t1 = tleft;
|
|
t2 = tright;
|
|
fmid = fq;
|
|
calc_fmid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (f1<0 && fmid>=0) {
|
|
t2 = tmid;
|
|
f2 = fmid;
|
|
continue;
|
|
}
|
|
|
|
if (fmid<0 && f2>=0) {
|
|
t1 = tmid;
|
|
f1 = fmid;
|
|
continue;
|
|
}
|
|
|
|
// Either there is no ascending zero-crossing in this range
|
|
// or the search window is too wide.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function LongitudeOffset(diff) {
|
|
let offset = diff;
|
|
while (offset <= -180) offset += 360;
|
|
while (offset > 180) offset -= 360;
|
|
return offset;
|
|
}
|
|
|
|
function NormalizeLongitude(lon) {
|
|
while (lon < 0) lon += 360;
|
|
while (lon >= 360) lon -= 360;
|
|
return lon;
|
|
}
|
|
|
|
/**
|
|
* Searches for the moment in time when the center of the Sun reaches a given apparent
|
|
* ecliptic longitude, as seen from the center of the Earth, within a given range of dates.
|
|
* This function can be used to determine equinoxes and solstices.
|
|
* However, it is usually more convenient and efficient to call {@link Astronomy.Seasons}
|
|
* to calculate equinoxes and solstices for a given calendar year.
|
|
* <code>SearchSunLongitude</code> is more general in that it allows searching for arbitrary longitude values.
|
|
*
|
|
* @param {number} targetLon
|
|
* The desired ecliptic longitude of date in degrees.
|
|
* This may be any value in the range [0, 360), although certain
|
|
* values have conventional meanings:
|
|
*
|
|
* When <code>targetLon</code> is 0, finds the March equinox,
|
|
* which is the moment spring begins in the northern hemisphere
|
|
* and the beginning of autumn in the southern hemisphere.
|
|
*
|
|
* When <code>targetLon</code> is 180, finds the September equinox,
|
|
* which is the moment autumn begins in the northern hemisphere and
|
|
* spring begins in the southern hemisphere.
|
|
*
|
|
* When <code>targetLon</code> is 90, finds the northern solstice, which is the
|
|
* moment summer begins in the northern hemisphere and winter
|
|
* begins in the southern hemisphere.
|
|
*
|
|
* When <code>targetLon</code> is 270, finds the southern solstice, which is the
|
|
* moment winter begins in the northern hemisphere and summer
|
|
* begins in the southern hemisphere.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} dateStart
|
|
* A date and time known to be earlier than the desired longitude event.
|
|
*
|
|
* @param {number} limitDays
|
|
* A floating point number of days, which when added to <code>dateStart</code>,
|
|
* yields a date and time known to be after the desired longitude event.
|
|
*
|
|
* @returns {Astronomy.AstroTime | null}
|
|
* The date and time when the Sun reaches the apparent ecliptic longitude <code>targetLon</code>
|
|
* within the range of times specified by <code>dateStart</code> and <code>limitDays</code>.
|
|
* If the Sun does not reach the target longitude within the specified time range, or the
|
|
* time range is excessively wide, the return value is <code>null</code>.
|
|
* To avoid a <code>null</code> return value, the caller must pick a time window around
|
|
* the event that is within a few days but not so small that the event might fall outside the window.
|
|
*/
|
|
Astronomy.SearchSunLongitude = function(targetLon, dateStart, limitDays) {
|
|
function sun_offset(t) {
|
|
let pos = Astronomy.SunPosition(t);
|
|
return LongitudeOffset(pos.elon - targetLon);
|
|
}
|
|
let t1 = Astronomy.MakeTime(dateStart);
|
|
let t2 = t1.AddDays(limitDays);
|
|
return Astronomy.Search(sun_offset, t1, t2);
|
|
}
|
|
|
|
/**
|
|
* Calculates the ecliptic longitude difference
|
|
* between the given body and the Sun as seen from
|
|
* the Earth at a given moment in time.
|
|
* The returned value ranges [0, 360) degrees.
|
|
* By definition, the Earth and the Sun are both in the plane of the ecliptic.
|
|
* Ignores the height of the <code>body</code> above or below the ecliptic plane;
|
|
* the resulting angle is measured around the ecliptic plane for the "shadow"
|
|
* of the body onto that plane.
|
|
*
|
|
* @param {string} body
|
|
* The name of a supported celestial body other than the Earth.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} date
|
|
* The time at which the relative longitude is to be found.
|
|
*
|
|
* @returns {number}
|
|
* An angle in degrees in the range [0, 360).
|
|
* Values less than 180 indicate that the body is to the east
|
|
* of the Sun as seen from the Earth; that is, the body sets after
|
|
* the Sun does and is visible in the evening sky.
|
|
* Values greater than 180 indicate that the body is to the west of
|
|
* the Sun and is visible in the morning sky.
|
|
*/
|
|
Astronomy.LongitudeFromSun = function(body, date) {
|
|
if (body === 'Earth')
|
|
throw 'The Earth does not have a longitude as seen from itself.';
|
|
|
|
const t = Astronomy.MakeTime(date);
|
|
let gb = Astronomy.GeoVector(body, t, true);
|
|
const eb = Astronomy.Ecliptic(gb.x, gb.y, gb.z);
|
|
|
|
let gs = Astronomy.GeoVector('Sun', t, true);
|
|
const es = Astronomy.Ecliptic(gs.x, gs.y, gs.z);
|
|
|
|
return NormalizeLongitude(eb.elon - es.elon);
|
|
}
|
|
|
|
/**
|
|
* Returns the full angle seen from
|
|
* the Earth, between the given body and the Sun.
|
|
* Unlike {@link Astronomy.LongitudeFromSun}, this function does not
|
|
* project the body's "shadow" onto the ecliptic;
|
|
* the angle is measured in 3D space around the plane that
|
|
* contains the centers of the Earth, the Sun, and <code>body</code>.
|
|
*
|
|
* @param {string} body
|
|
* The name of a supported celestial body other than the Earth.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} date
|
|
* The time at which the angle from the Sun is to be found.
|
|
*
|
|
* @returns {number}
|
|
* An angle in degrees in the range [0, 180].
|
|
*/
|
|
Astronomy.AngleFromSun = function(body, date) {
|
|
if (body == 'Earth')
|
|
throw 'The Earth does not have an angle as seen from itself.';
|
|
|
|
let sv = Astronomy.GeoVector('Sun', date, true);
|
|
let bv = Astronomy.GeoVector(body, date, true);
|
|
let angle = AngleBetween(sv, bv);
|
|
return angle;
|
|
}
|
|
|
|
/**
|
|
* Calculates heliocentric ecliptic longitude based on the J2000 equinox.
|
|
*
|
|
* @param {string} body
|
|
* The name of a celestial body other than the Sun.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} date
|
|
* The date and time for which to calculate the ecliptic longitude.
|
|
*
|
|
* @returns {number}
|
|
* The ecliptic longitude angle of the body in degrees measured counterclockwise around the mean
|
|
* plane of the Earth's orbit, as seen from above the Sun's north pole.
|
|
* Ecliptic longitude starts at 0 at the J2000
|
|
* <a href="https://en.wikipedia.org/wiki/Equinox_(celestial_coordinates)">equinox</a> and
|
|
* increases in the same direction the Earth orbits the Sun.
|
|
* The returned value is always in the range [0, 360).
|
|
*/
|
|
Astronomy.EclipticLongitude = function(body, date) {
|
|
if (body === 'Sun')
|
|
throw 'Cannot calculate heliocentric longitude of the Sun.';
|
|
|
|
let hv = Astronomy.HelioVector(body, date);
|
|
let eclip = Astronomy.Ecliptic(hv.x, hv.y, hv.z);
|
|
return eclip.elon;
|
|
}
|
|
|
|
function VisualMagnitude(body, phase, helio_dist, geo_dist) {
|
|
// For Mercury and Venus, see: https://iopscience.iop.org/article/10.1086/430212
|
|
let c0, c1=0, c2=0, c3=0;
|
|
switch (body) {
|
|
case 'Mercury': c0 = -0.60; c1 = +4.98; c2 = -4.88; c3 = +3.02; break;
|
|
case 'Venus':
|
|
if (phase < 163.6) {
|
|
c0 = -4.47; c1 = +1.03; c2 = +0.57; c3 = +0.13;
|
|
} else {
|
|
c0 = 0.98; c1 = -1.02;
|
|
}
|
|
break;
|
|
case 'Mars': c0 = -1.52; c1 = +1.60; break;
|
|
case 'Jupiter': c0 = -9.40; c1 = +0.50; break;
|
|
case 'Uranus': c0 = -7.19; c1 = +0.25; break;
|
|
case 'Neptune': c0 = -6.87; break;
|
|
case 'Pluto': c0 = -1.00; c1 = +4.00; break;
|
|
default: throw `VisualMagnitude: unsupported body ${body}`;
|
|
}
|
|
|
|
const x = phase / 100;
|
|
let mag = c0 + x*(c1 + x*(c2 + x*c3));
|
|
mag += 5*Math.log10(helio_dist * geo_dist);
|
|
return mag;
|
|
}
|
|
|
|
function SaturnMagnitude(phase, helio_dist, geo_dist, gc, time) {
|
|
// Based on formulas by Paul Schlyter found here:
|
|
// http://www.stjarnhimlen.se/comp/ppcomp.html#15
|
|
|
|
// We must handle Saturn's rings as a major component of its visual magnitude.
|
|
// Find geocentric ecliptic coordinates of Saturn.
|
|
const eclip = Astronomy.Ecliptic(gc.x, gc.y, gc.z);
|
|
const ir = DEG2RAD * 28.06; // tilt of Saturn's rings to the ecliptic, in radians
|
|
const Nr = DEG2RAD * (169.51 + (3.82e-5 * time.tt)); // ascending node of Saturn's rings, in radians
|
|
|
|
// Find tilt of Saturn's rings, as seen from Earth.
|
|
const lat = DEG2RAD * eclip.elat;
|
|
const lon = DEG2RAD * eclip.elon;
|
|
const tilt = Math.asin(Math.sin(lat)*Math.cos(ir) - Math.cos(lat)*Math.sin(ir)*Math.sin(lon-Nr));
|
|
const sin_tilt = Math.sin(Math.abs(tilt));
|
|
|
|
let mag = -9.0 + 0.044*phase;
|
|
mag += sin_tilt*(-2.6 + 1.2*sin_tilt);
|
|
mag += 5*Math.log10(helio_dist * geo_dist);
|
|
return { mag:mag, ring_tilt:RAD2DEG*tilt };
|
|
}
|
|
|
|
function MoonMagnitude(phase, helio_dist, geo_dist) {
|
|
// https://astronomy.stackexchange.com/questions/10246/is-there-a-simple-analytical-formula-for-the-lunar-phase-brightness-curve
|
|
let rad = phase * DEG2RAD;
|
|
let rad2 = rad * rad;
|
|
let rad4 = rad2 * rad2;
|
|
let mag = -12.717 + 1.49*Math.abs(rad) + 0.0431*rad4;
|
|
|
|
const moon_mean_distance_au = 385000.6 / KM_PER_AU;
|
|
let geo_au = geo_dist / moon_mean_distance_au;
|
|
mag += 5*Math.log10(helio_dist * geo_au);
|
|
return mag;
|
|
}
|
|
|
|
/**
|
|
* Contains information about the apparent brightness and sunlit phase of a celestial object.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {Astronomy.AstroTime} time
|
|
* The date and time pertaining to the other calculated values in this object.
|
|
*
|
|
* @property {number} mag
|
|
* The <a href="https://en.wikipedia.org/wiki/Apparent_magnitude">apparent visual magnitude</a> of the celestial body.
|
|
*
|
|
* @property {number} phase_angle
|
|
* The angle in degrees as seen from the center of the celestial body between the Sun and the Earth.
|
|
* The value is always in the range 0 to 180.
|
|
* The phase angle provides a measure of what fraction of the body's face appears
|
|
* illuminated by the Sun as seen from the Earth.
|
|
* When the observed body is the Sun, the <code>phase</code> property is set to 0,
|
|
* although this has no physical meaning because the Sun emits, rather than reflects, light.
|
|
* When the phase is near 0 degrees, the body appears "full".
|
|
* When it is 90 degrees, the body appears "half full".
|
|
* And when it is 180 degrees, the body appears "new" and is very difficult to see
|
|
* because it is both dim and lost in the Sun's glare as seen from the Earth.
|
|
*
|
|
* @property {number} phase_fraction
|
|
* The fraction of the body's face that is illuminated by the Sun, as seen from the Earth.
|
|
* Calculated from <code>phase_angle</code> for convenience.
|
|
* This value ranges from 0 to 1.
|
|
*
|
|
* @property {number} helio_dist
|
|
* The distance between the center of the Sun and the center of the body in
|
|
* <a href="https://en.wikipedia.org/wiki/Astronomical_unit">astronomical units</a> (AU).
|
|
*
|
|
* @property {number} geo_dist
|
|
* The distance between the center of the Earth and the center of the body in AU.
|
|
*
|
|
* @property {Astronomy.Vector} gc
|
|
* Geocentric coordinates: the 3D vector from the center of the Earth to the center of the body.
|
|
* The components are in expressed in AU and are oriented with respect to the J2000 equatorial plane.
|
|
*
|
|
* @property {Astronomy.Vector} hc
|
|
* Heliocentric coordinates: The 3D vector from the center of the Sun to the center of the body.
|
|
* Like <code>gc</code>, <code>hc</code> is expressed in AU and oriented with respect
|
|
* to the J2000 equatorial plane.
|
|
*
|
|
* @property {number | null} ring_tilt
|
|
* For Saturn, this is the angular tilt of the planet's rings in degrees away
|
|
* from the line of sight from the Earth. When the value is near 0, the rings
|
|
* appear edge-on from the Earth and are therefore difficult to see.
|
|
* When <code>ring_tilt</code> approaches its maximum value (about 27 degrees),
|
|
* the rings appear widest and brightest from the Earth.
|
|
* Unlike the <a href="https://ssd.jpl.nasa.gov/horizons.cgi">JPL Horizons</a> online tool,
|
|
* this library includes the effect of the ring tilt angle in the calculated value
|
|
* for Saturn's visual magnitude.
|
|
* For all bodies other than Saturn, the value of <code>ring_tilt</code> is <code>null</code>.
|
|
*/
|
|
class IlluminationInfo {
|
|
constructor(time, mag, phase, helio_dist, geo_dist, gc, hc, ring_tilt) {
|
|
this.time = time;
|
|
this.mag = mag;
|
|
this.phase_angle = phase;
|
|
this.phase_fraction = (1 + Math.cos(DEG2RAD * phase)) / 2;
|
|
this.helio_dist = helio_dist;
|
|
this.geo_dist = geo_dist;
|
|
this.gc = gc;
|
|
this.hc = hc;
|
|
this.ring_tilt = ring_tilt;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the phase angle, visual maginitude,
|
|
* and other values relating to the body's illumination
|
|
* at the given date and time, as seen from the Earth.
|
|
*
|
|
* @param {string} body
|
|
* The name of the celestial body being observed.
|
|
* Not allowed to be <code>"Earth"</code>.
|
|
*
|
|
* @param {Date | number | Astronomy.AstroTime} date
|
|
* The date and time for which to calculate the illumination data for the given body.
|
|
*
|
|
* @returns {Astronomy.IlluminationInfo}
|
|
*/
|
|
Astronomy.Illumination = function(body, date) {
|
|
if (body === 'Earth')
|
|
throw `The illumination of the Earth is not defined.`;
|
|
|
|
const time = Astronomy.MakeTime(date);
|
|
const earth = CalcVsop(vsop.Earth, time);
|
|
let phase; // phase angle in degrees between Earth and Sun as seen from body
|
|
let hc; // vector from Sun to body
|
|
let gc; // vector from Earth to body
|
|
let mag; // visual magnitude
|
|
|
|
if (body === 'Sun') {
|
|
gc = new Vector(-earth.x, -earth.y, -earth.z, time);
|
|
hc = new Vector(0, 0, 0, time);
|
|
phase = 0; // a placeholder value; the Sun does not have an illumination phase because it emits, rather than reflects, light.
|
|
} else {
|
|
if (body === 'Moon') {
|
|
// For extra numeric precision, use geocentric moon formula directly.
|
|
gc = Astronomy.GeoMoon(time);
|
|
hc = new Vector(earth.x + gc.x, earth.y + gc.y, earth.z + gc.z, time);
|
|
} else {
|
|
// For planets, heliocentric vector is most direct to calculate.
|
|
hc = Astronomy.HelioVector(body, date);
|
|
gc = new Vector(hc.x - earth.x, hc.y - earth.y, hc.z - earth.z, time);
|
|
}
|
|
phase = AngleBetween(gc, hc);
|
|
}
|
|
|
|
let geo_dist = gc.Length(); // distance from body to center of Earth
|
|
let helio_dist = hc.Length(); // distance from body to center of Sun
|
|
let ring_tilt = null; // only reported for Saturn
|
|
|
|
if (body === 'Sun') {
|
|
mag = SUN_MAG_1AU + 5*Math.log10(geo_dist);
|
|
} else if (body === 'Moon') {
|
|
mag = MoonMagnitude(phase, helio_dist, geo_dist);
|
|
} else if (body === 'Saturn') {
|
|
const saturn = SaturnMagnitude(phase, helio_dist, geo_dist, gc, time);
|
|
mag = saturn.mag;
|
|
ring_tilt = saturn.ring_tilt;
|
|
} else {
|
|
mag = VisualMagnitude(body, phase, helio_dist, geo_dist);
|
|
}
|
|
|
|
return new IlluminationInfo(time, mag, phase, helio_dist, geo_dist, gc, hc, ring_tilt);
|
|
}
|
|
|
|
function SynodicPeriod(body) {
|
|
if (body === 'Earth')
|
|
throw 'The Earth does not have a synodic period as seen from itself.';
|
|
|
|
if (body === 'Moon')
|
|
return MEAN_SYNODIC_MONTH;
|
|
|
|
// Calculate the synodic period of the planet from its and the Earth's sidereal periods.
|
|
// The sidereal period of a planet is how long it takes to go around the Sun in days, on average.
|
|
// The synodic period of a planet is how long it takes between consecutive oppositions
|
|
// or conjunctions, on average.
|
|
|
|
let planet = Planet[body];
|
|
if (!planet)
|
|
throw `Not a valid planet name: ${body}`;
|
|
|
|
// See here for explanation of the formula:
|
|
// https://en.wikipedia.org/wiki/Elongation_(astronomy)#Elongation_period
|
|
|
|
const Te = Planet.Earth.OrbitalPeriod;
|
|
const Tp = planet.OrbitalPeriod;
|
|
const synodicPeriod = Math.abs(Te / (Te/Tp - 1));
|
|
|
|
return synodicPeriod;
|
|
}
|
|
|
|
/**
|
|
* Searches for the date and time the relative ecliptic longitudes of
|
|
* the specified body and the Earth, as seen from the Sun, reach a certain
|
|
* difference. This function is useful for finding conjunctions and oppositions
|
|
* of the planets. For the opposition of a superior planet (Mars, Jupiter, ..., Pluto),
|
|
* or the inferior conjunction of an inferior planet (Mercury, Venus),
|
|
* call with <code>targetRelLon</code> = 0. The 0 value indicates that both
|
|
* planets are on the same ecliptic longitude line, ignoring the other planet's
|
|
* distance above or below the plane of the Earth's orbit.
|
|
* For superior conjunctions, call with <code>targetRelLon</code> = 180.
|
|
* This means the Earth and the other planet are on opposite sides of the Sun.
|
|
*
|
|
* @param {string} body
|
|
* The name of a planet other than the Earth.
|
|
*
|
|
* @param {number} targetRelLon
|
|
* The desired angular difference in degrees between the ecliptic longitudes
|
|
* of <code>body</code> and the Earth. Must be in the range (-180, +180].
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} startDate
|
|
* The date and time after which to find the next occurrence of the
|
|
* body and the Earth reaching the desired relative longitude.
|
|
*
|
|
* @returns {Astronomy.AstroTime}
|
|
* The time when the Earth and the body next reach the specified relative longitudes.
|
|
*/
|
|
Astronomy.SearchRelativeLongitude = function(body, targetRelLon, startDate) {
|
|
const planet = Planet[body];
|
|
if (!planet)
|
|
throw `Cannot search relative longitude because body is not a planet: ${body}`;
|
|
|
|
if (body === 'Earth')
|
|
throw 'Cannot search relative longitude for the Earth (it is always 0)';
|
|
|
|
// Determine whether the Earth "gains" (+1) on the planet or "loses" (-1)
|
|
// as both race around the Sun.
|
|
const direction = (planet.OrbitalPeriod > Planet.Earth.OrbitalPeriod) ? +1 : -1;
|
|
|
|
function offset(t) {
|
|
const plon = Astronomy.EclipticLongitude(body, t);
|
|
const elon = Astronomy.EclipticLongitude('Earth', t);
|
|
const diff = direction * (elon - plon);
|
|
return LongitudeOffset(diff - targetRelLon);
|
|
}
|
|
|
|
let syn = SynodicPeriod(body);
|
|
let time = Astronomy.MakeTime(startDate);
|
|
|
|
// Iterate until we converge on the desired event.
|
|
// Calculate the error angle, which will be a negative number of degrees,
|
|
// meaning we are "behind" the target relative longitude.
|
|
let error_angle = offset(time);
|
|
if (error_angle > 0) error_angle -= 360; // force searching forward in time
|
|
|
|
++Perf.longitude_search;
|
|
|
|
for (let iter=0; iter < 100; ++iter) {
|
|
++Perf.longitude_iter;
|
|
|
|
// Estimate how many days in the future (positive) or past (negative)
|
|
// we have to go to get closer to the target relative longitude.
|
|
let day_adjust = (-error_angle/360) * syn;
|
|
time = time.AddDays(day_adjust);
|
|
if (Math.abs(day_adjust) * SECONDS_PER_DAY < 1)
|
|
return time;
|
|
|
|
let prev_angle = error_angle;
|
|
error_angle = offset(time);
|
|
|
|
if (Math.abs(prev_angle) < 30) {
|
|
// Improve convergence for Mercury/Mars (eccentric orbits)
|
|
// by adjusting the synodic period to more closely match the
|
|
// variable speed of both planets in this part of their respective orbits.
|
|
if (prev_angle !== error_angle) {
|
|
let ratio = prev_angle / (prev_angle - error_angle);
|
|
if (ratio > 0.5 && ratio < 2.0)
|
|
syn *= ratio;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw `Relative longitude search failed to converge for ${body} near ${time.toString()} (error_angle = ${error_angle}).`;
|
|
}
|
|
|
|
/**
|
|
* Determines the moon's phase expressed as an ecliptic longitude.
|
|
*
|
|
* @param {Date | number | Astronomy.AstroTime} date
|
|
* The date and time for which to calculate the moon's phase.
|
|
*
|
|
* @returns {number}
|
|
* A value in the range [0, 360) indicating the difference
|
|
* in ecliptic longitude between the center of the Sun and the
|
|
* center of the Moon, as seen from the center of the Earth.
|
|
* Certain longitude values have conventional meanings:
|
|
*
|
|
* * 0 = new moon
|
|
* * 90 = first quarter
|
|
* * 180 = full moon
|
|
* * 270 = third quarter
|
|
*/
|
|
Astronomy.MoonPhase = function(date) {
|
|
return Astronomy.LongitudeFromSun('Moon', date);
|
|
}
|
|
|
|
/**
|
|
* Searches for the date and time that the Moon reaches a specified phase.
|
|
* Lunar phases are defined in terms of geocentric ecliptic longitudes
|
|
* with respect to the Sun. When the Moon and the Sun have the same ecliptic
|
|
* longitude, that is defined as a new moon. When the two ecliptic longitudes
|
|
* are 180 degrees apart, that is defined as a full moon.
|
|
* To enumerate quarter lunar phases, it is simpler to call
|
|
* {@link Astronomy.SearchMoonQuarter} once, followed by repeatedly calling
|
|
* {@link Astronomy.NextMoonQuarter}. <code>SearchMoonPhase</code> is only
|
|
* necessary for finding other lunar phases than the usual quarter phases.
|
|
*
|
|
* @param {number} targetLon
|
|
* The difference in geocentric ecliptic longitude between the Sun and Moon
|
|
* that specifies the lunar phase being sought. This can be any value
|
|
* in the range [0, 360). Here are some helpful examples:
|
|
* 0 = new moon,
|
|
* 90 = first quarter,
|
|
* 180 = full moon,
|
|
* 270 = third quarter.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} dateStart
|
|
* The beginning of the window of time in which to search.
|
|
*
|
|
* @param {number} limitDays
|
|
* The floating point number of days after <code>dateStart</code>
|
|
* that limits the window of time in which to search.
|
|
*
|
|
* @returns {(Astronomy.AstroTime|null)}
|
|
* If the specified lunar phase occurs after <code>dateStart</code>
|
|
* and before <code>limitDays</code> days after <code>dateStart</code>,
|
|
* this function returns the date and time of the first such occurrence.
|
|
* Otherwise, it returns <code>null</code>.
|
|
*/
|
|
Astronomy.SearchMoonPhase = function(targetLon, dateStart, limitDays) {
|
|
function moon_offset(t) {
|
|
let mlon = Astronomy.MoonPhase(t);
|
|
return LongitudeOffset(mlon - targetLon);
|
|
}
|
|
|
|
// To avoid discontinuities in the moon_offset function causing problems,
|
|
// we need to approximate when that function will next return 0.
|
|
// We probe it with the start time and take advantage of the fact
|
|
// that every lunar phase repeats roughly every 29.5 days.
|
|
// There is a surprising uncertainty in the quarter timing,
|
|
// due to the eccentricity of the moon's orbit.
|
|
// I have seen up to 0.826 days away from the simple prediction.
|
|
// To be safe, we take the predicted time of the event and search
|
|
// +/-0.9 days around it (a 1.8-day wide window).
|
|
// But we must return null if the final result goes beyond limitDays after dateStart.
|
|
const uncertainty = 0.9;
|
|
|
|
let ta = Astronomy.MakeTime(dateStart);
|
|
let ya = moon_offset(ta);
|
|
if (ya > 0) ya -= 360; // force searching forward in time, not backward
|
|
let est_dt = -(MEAN_SYNODIC_MONTH*ya)/360;
|
|
let dt1 = est_dt - uncertainty;
|
|
if (dt1 > limitDays) return null; // not possible for moon phase to occur within the specified window
|
|
let dt2 = Math.min(limitDays, est_dt + uncertainty);
|
|
let t1 = ta.AddDays(dt1);
|
|
let t2 = ta.AddDays(dt2);
|
|
return Astronomy.Search(moon_offset, t1, t2);
|
|
}
|
|
|
|
/**
|
|
* Represents a quarter lunar phase, along with when it occurs.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {number} quarter
|
|
* An integer as follows:
|
|
* 0 = new moon,
|
|
* 1 = first quarter,
|
|
* 2 = full moon,
|
|
* 3 = third quarter.
|
|
*
|
|
* @property {Astronomy.AstroTime} time
|
|
* The date and time of the quarter lunar phase.
|
|
*/
|
|
class MoonQuarter {
|
|
constructor(quarter, time) {
|
|
this.quarter = quarter;
|
|
this.time = time;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the first quarter lunar phase after the specified date and time.
|
|
* The quarter lunar phases are: new moon, first quarter, full moon, and third quarter.
|
|
* To enumerate quarter lunar phases, call <code>SearchMoonQuarter</code> once,
|
|
* then pass its return value to {@link Astronomy.NextMoonQuarter} to find the next
|
|
* <code>MoonQuarter</code>. Keep calling <code>NextMoonQuarter</code> in a loop,
|
|
* passing the previous return value as the argument to the next call.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} dateStart
|
|
* The date and time after which to find the first quarter lunar phase.
|
|
*
|
|
* @returns {Astronomy.MoonQuarter}
|
|
*/
|
|
Astronomy.SearchMoonQuarter = function(dateStart) {
|
|
// Determine what the next quarter phase will be.
|
|
let phaseStart = Astronomy.MoonPhase(dateStart);
|
|
let quarterStart = Math.floor(phaseStart / 90);
|
|
let quarter = (quarterStart + 1) % 4;
|
|
let time = Astronomy.SearchMoonPhase(90 * quarter, dateStart, 10);
|
|
return time && new MoonQuarter(quarter, time);
|
|
}
|
|
|
|
/**
|
|
* Given a {@link Astronomy.MoonQuarter} object, finds the next consecutive
|
|
* quarter lunar phase. See remarks in {@link Astronomy.SearchMoonQuarter}
|
|
* for explanation of usage.
|
|
*
|
|
* @param {Astronomy.MoonQuarter} mq
|
|
* The return value of a prior call to {@link Astronomy.MoonQuarter} or <code>NextMoonQuarter</code>.
|
|
*/
|
|
Astronomy.NextMoonQuarter = function(mq) {
|
|
// Skip 6 days past the previous found moon quarter to find the next one.
|
|
// This is less than the minimum possible increment.
|
|
// So far I have seen the interval well contained by the range (6.5, 8.3) days.
|
|
let date = new Date(mq.time.date.getTime() + 6*MILLIS_PER_DAY);
|
|
return Astronomy.SearchMoonQuarter(date);
|
|
}
|
|
|
|
/**
|
|
* Finds a rise or set time for the given body as
|
|
* seen by an observer at the specified location on the Earth.
|
|
* Rise time is defined as the moment when the top of the body
|
|
* is observed to first appear above the horizon in the east.
|
|
* Set time is defined as the moment the top of the body
|
|
* is observed to sink below the horizon in the west.
|
|
* The times are adjusted for typical atmospheric refraction conditions.
|
|
*
|
|
* @param {string} body
|
|
* The name of the body to find the rise or set time for.
|
|
*
|
|
* @param {Astronomy.Observer} observer
|
|
* Specifies the geographic coordinates and elevation above sea level of the observer.
|
|
* Call {@link Astronomy.MakeObserver} to create an observer object.
|
|
*
|
|
* @param {number} direction
|
|
* Either +1 to find rise time or -1 to find set time.
|
|
* Any other value will cause an exception to be thrown.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} dateStart
|
|
* The date and time after which the specified rise or set time is to be found.
|
|
*
|
|
* @param {number} limitDays
|
|
* The fractional number of days after <code>dateStart</code> that limits
|
|
* when the rise or set time is to be found.
|
|
*
|
|
* @returns {(Astronomy.AstroTime|null)}
|
|
* The date and time of the rise or set event, or null if no such event
|
|
* occurs within the specified time window.
|
|
*/
|
|
Astronomy.SearchRiseSet = function(body, observer, direction, dateStart, limitDays) {
|
|
// We calculate the apparent angular radius of the Sun and Moon,
|
|
// but treat all other bodies as points.
|
|
let body_radius_au = { Sun:SUN_RADIUS_AU, Moon:MOON_RADIUS_AU }[body] || 0;
|
|
|
|
function peak_altitude(t) {
|
|
// Return the angular altitude above or below the horizon
|
|
// of the highest part (the peak) of the given object.
|
|
// This is defined as the apparent altitude of the center of the body plus
|
|
// the body's angular radius.
|
|
// The 'direction' variable in the enclosing function controls
|
|
// whether the angle is measured positive above the horizon or
|
|
// positive below the horizon, depending on whether the caller
|
|
// wants rise times or set times, respectively.
|
|
|
|
const ofdate = Astronomy.Equator(body, t, observer, true, true);
|
|
const hor = Astronomy.Horizon(t, observer, ofdate.ra, ofdate.dec);
|
|
const alt = hor.altitude + RAD2DEG*(body_radius_au / ofdate.dist) + REFRACTION_NEAR_HORIZON;
|
|
return direction * alt;
|
|
}
|
|
|
|
if (body === 'Earth')
|
|
throw 'Cannot find rise or set time of the Earth.';
|
|
|
|
// See if the body is currently above/below the horizon.
|
|
// If we are looking for next rise time and the body is below the horizon,
|
|
// we use the current time as the lower time bound and the next culmination
|
|
// as the upper bound.
|
|
// If the body is above the horizon, we search for the next bottom and use it
|
|
// as the lower bound and the next culmination after that bottom as the upper bound.
|
|
// The same logic applies for finding set times, only we swap the hour angles.
|
|
// The peak_altitude() function already considers the 'direction' parameter.
|
|
|
|
let ha_before, ha_after;
|
|
if (direction === +1) {
|
|
ha_before = 12; // minimum altitude (bottom) happens BEFORE the body rises.
|
|
ha_after = 0; // maximum altitude (culmination) happens AFTER the body rises.
|
|
} else if (direction === -1) {
|
|
ha_before = 0; // culmination happens BEFORE the body sets.
|
|
ha_after = 12; // bottom happens AFTER the body sets.
|
|
} else {
|
|
throw `Astronomy.SearchRiseSet: Invalid direction parameter ${direction} -- must be +1 or -1`;
|
|
}
|
|
|
|
let time_start = Astronomy.MakeTime(dateStart);
|
|
let time_before;
|
|
let evt_before, evt_after;
|
|
let alt_before = peak_altitude(time_start);
|
|
let alt_after;
|
|
if (alt_before > 0) {
|
|
// We are past the sought event, so we have to wait for the next "before" event (culm/bottom).
|
|
evt_before = Astronomy.SearchHourAngle(body, observer, ha_before, time_start);
|
|
time_before = evt_before.time;
|
|
alt_before = peak_altitude(time_before);
|
|
} else {
|
|
// We are before or at the sought event, so we find the next "after" event (bottom/culm),
|
|
// and use the current time as the "before" event.
|
|
time_before = time_start;
|
|
}
|
|
evt_after = Astronomy.SearchHourAngle(body, observer, ha_after, time_before);
|
|
alt_after = peak_altitude(evt_after.time);
|
|
|
|
while (true) {
|
|
if (alt_before <= 0 && alt_after > 0) {
|
|
// Search between evt_before and evt_after for the desired event.
|
|
let tx = Astronomy.Search(peak_altitude, time_before, evt_after.time, {init_f1:alt_before, init_f2:alt_after});
|
|
if (tx)
|
|
return tx;
|
|
}
|
|
|
|
// If we didn't find the desired event, use time_after to find the next before-event.
|
|
evt_before = Astronomy.SearchHourAngle(body, observer, ha_before, evt_after.time);
|
|
evt_after = Astronomy.SearchHourAngle(body, observer, ha_after, evt_before.time);
|
|
if (evt_before.time.ut >= time_start.ut + limitDays)
|
|
return null;
|
|
|
|
time_before = evt_before.time;
|
|
alt_before = peak_altitude(evt_before.time);
|
|
alt_after = peak_altitude(evt_after.time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns information about an occurrence of a celestial body
|
|
* reaching a given hour angle as seen by an observer at a given
|
|
* location on the surface of the Earth.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {Astronomy.AstroTime} time
|
|
* The date and time of the celestial body reaching the hour angle.
|
|
*
|
|
* @property {Astronomy.HorizontalCoordinates} hor
|
|
* Topocentric horizontal coordinates for the body
|
|
* at the time indicated by the <code>time</code> property.
|
|
*/
|
|
class HourAngleEvent {
|
|
constructor(time, hor) {
|
|
this.time = time;
|
|
this.hor = hor;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the next time the given body is seen to reach the specified
|
|
* <a href="https://en.wikipedia.org/wiki/Hour_angle">hour angle</a>
|
|
* by the given observer.
|
|
* Providing <code>hourAngle</code> = 0 finds the next maximum altitude event (culmination).
|
|
* Providing <code>hourAngle</code> = 12 finds the next minimum altitude event.
|
|
* Note that, especially close to the Earth's poles, a body as seen on a given day
|
|
* may always be above the horizon or always below the horizon, so the caller cannot
|
|
* assume that a culminating object is visible nor that an object is below the horizon
|
|
* at its minimum altitude.
|
|
*
|
|
* @param {string} body
|
|
* The name of a celestial body other than the Earth.
|
|
*
|
|
* @param {Astronomy.Observer} observer
|
|
* Specifies the geographic coordinates and elevation above sea level of the observer.
|
|
* Call {@link Astronomy.MakeObserver} to create an observer object.
|
|
*
|
|
* @param {number} hourAngle
|
|
* The hour angle expressed in
|
|
* <a href="https://en.wikipedia.org/wiki/Sidereal_time">sidereal</a>
|
|
* hours for which the caller seeks to find the body attain.
|
|
* The value must be in the range [0, 24).
|
|
* The hour angle represents the number of sidereal hours that have
|
|
* elapsed since the most recent time the body crossed the observer's local
|
|
* <a href="https://en.wikipedia.org/wiki/Meridian_(astronomy)">meridian</a>.
|
|
* This specifying <code>hourAngle</code> = 0 finds the moment in time
|
|
* the body reaches the highest angular altitude in a given sidereal day.
|
|
*
|
|
* @param {(Date|number|Astronomy.AstroTime)} dateStart
|
|
* The date and time after which the desired hour angle crossing event
|
|
* is to be found.
|
|
*
|
|
* @returns {Astronomy.HourAngleEvent}
|
|
*/
|
|
Astronomy.SearchHourAngle = function(body, observer, hourAngle, dateStart) {
|
|
let time = Astronomy.MakeTime(dateStart);
|
|
let iter = 0;
|
|
|
|
if (body === 'Earth')
|
|
throw 'Cannot search for hour angle of the Earth.';
|
|
|
|
if (hourAngle < 0.0 || hourAngle >= 24.0)
|
|
throw `Invalid hour angle ${hourAngle}`;
|
|
|
|
while (true) {
|
|
++iter;
|
|
|
|
// Calculate Greenwich Apparent Sidereal Time (GAST) at the given time.
|
|
let gast = sidereal_time(time);
|
|
|
|
let ofdate = Astronomy.Equator(body, time, observer, true, true);
|
|
|
|
// Calculate the adjustment needed in sidereal time to bring
|
|
// the hour angle to the desired value.
|
|
let delta_sidereal_hours = ((hourAngle + ofdate.ra - observer.longitude/15) - gast) % 24;
|
|
if (iter === 1) {
|
|
// On the first iteration, always search forward in time.
|
|
if (delta_sidereal_hours < 0)
|
|
delta_sidereal_hours += 24;
|
|
} else {
|
|
// On subsequent iterations, we make the smallest possible adjustment,
|
|
// either forward or backward in time.
|
|
if (delta_sidereal_hours < -12)
|
|
delta_sidereal_hours += 24;
|
|
else if (delta_sidereal_hours > +12)
|
|
delta_sidereal_hours -= 24;
|
|
}
|
|
|
|
// If the error is tolerable (less than 0.1 seconds), stop searching.
|
|
if (Math.abs(delta_sidereal_hours) * 3600 < 0.1) {
|
|
const hor = Astronomy.Horizon(time, observer, ofdate.ra, ofdate.dec, 'normal');
|
|
return new HourAngleEvent(time, hor);
|
|
}
|
|
|
|
// We need to loop another time to get more accuracy.
|
|
// Update the terrestrial time adjusting by sidereal time.
|
|
let delta_days = (delta_sidereal_hours / 24) * SOLAR_DAYS_PER_SIDEREAL_DAY;
|
|
time = time.AddDays(delta_days);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents the dates and times of the two solstices
|
|
* and the two equinoxes in a given calendar year.
|
|
* These four events define the changing of the seasons on the Earth.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {Astronomy.AstroTime} mar_equinox
|
|
* The date and time of the March equinox in the given calendar year.
|
|
* This is the moment in March that the plane of the Earth's equator passes
|
|
* through the center of the Sun; thus the Sun's declination
|
|
* changes from a negative number to a positive number.
|
|
* The March equinox defines
|
|
* the beginning of spring in the northern hemisphere and
|
|
* the beginning of autumn in the southern hemisphere.
|
|
*
|
|
* @property {Astronomy.AstroTime} jun_solstice
|
|
* The date and time of the June solstice in the given calendar year.
|
|
* This is the moment in June that the Sun reaches its most positive
|
|
* declination value.
|
|
* At this moment the Earth's north pole is most tilted most toward the Sun.
|
|
* The June solstice defines
|
|
* the beginning of summer in the northern hemisphere and
|
|
* the beginning of winter in the southern hemisphere.
|
|
*
|
|
* @property {Astronomy.AstroTime} sep_equinox
|
|
* The date and time of the September equinox in the given calendar year.
|
|
* This is the moment in September that the plane of the Earth's equator passes
|
|
* through the center of the Sun; thus the Sun's declination
|
|
* changes from a positive number to a negative number.
|
|
* The September equinox defines
|
|
* the beginning of autumn in the northern hemisphere and
|
|
* the beginning of spring in the southern hemisphere.
|
|
*
|
|
* @property {Astronomy.AstroTime} dec_solstice
|
|
* The date and time of the December solstice in the given calendar year.
|
|
* This is the moment in December that the Sun reaches its most negative
|
|
* declination value.
|
|
* At this moment the Earth's south pole is tilted most toward the Sun.
|
|
* The December solstice defines
|
|
* the beginning of winter in the northern hemisphere and
|
|
* the beginning of summer in the southern hemisphere.
|
|
*/
|
|
class SeasonInfo {
|
|
constructor(mar_equinox, jun_solstice, sep_equinox, dec_solstice) {
|
|
this.mar_equinox = mar_equinox;
|
|
this.jun_solstice = jun_solstice;
|
|
this.sep_equinox = sep_equinox;
|
|
this.dec_solstice = dec_solstice;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the equinoxes and solstices for a given calendar year.
|
|
*
|
|
* @param {(number | Astronomy.AstroTime)} year
|
|
* The integer value or <code>AstroTime</code> object that specifies
|
|
* the UTC calendar year for which to find equinoxes and solstices.
|
|
*
|
|
* @returns {Astronomy.SeasonInfo}
|
|
*/
|
|
Astronomy.Seasons = function(year) {
|
|
function find(targetLon, month, day) {
|
|
let startDate = new Date(Date.UTC(year, month-1, day));
|
|
let time = Astronomy.SearchSunLongitude(targetLon, startDate, 4);
|
|
if (!time)
|
|
throw `Cannot find season change near ${startDate.toISOString()}`;
|
|
return time;
|
|
}
|
|
|
|
if (year instanceof Date) {
|
|
year = year.getUTCFullYear();
|
|
}
|
|
|
|
let mar_equinox = find( 0, 3, 19);
|
|
let jun_solstice = find( 90, 6, 19);
|
|
let sep_equinox = find(180, 9, 21);
|
|
let dec_solstice = find(270, 12, 20);
|
|
|
|
return new SeasonInfo(mar_equinox, jun_solstice, sep_equinox, dec_solstice);
|
|
}
|
|
|
|
/**
|
|
* Represents the angular separation of a body from the Sun as seen from the Earth
|
|
* and the relative ecliptic longitudes between that body and the Earth as seen from the Sun.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {Astronomy.AstroTime} time
|
|
* The date and time of the observation.
|
|
*
|
|
* @property {string} visibility
|
|
* Either <code>"morning"</code> or <code>"evening"</code>,
|
|
* indicating when the body is most easily seen.
|
|
*
|
|
* @property {number} elongation
|
|
* The angle in degrees, as seen from the center of the Earth,
|
|
* of the apparent separation between the body and the Sun.
|
|
* This angle is measured in 3D space and is not projected onto the ecliptic plane.
|
|
* When <code>elongation</code> is less than a few degrees, the body is very
|
|
* difficult to see from the Earth because it is lost in the Sun's glare.
|
|
* The elongation is always in the range [0, 180].
|
|
*
|
|
* @property {number} ecliptic_separation
|
|
* The absolute value of the difference between the body's ecliptic longitude
|
|
* and the Sun's ecliptic longitude, both as seen from the center of the Earth.
|
|
* This angle measures around the plane of the Earth's orbit (the ecliptic),
|
|
* and ignores how far above or below that plane the body is.
|
|
* The ecliptic separation is measured in degrees and is always in the range [0, 180].
|
|
*
|
|
* @see {@link Astronomy.Elongation}
|
|
*/
|
|
class ElongationEvent {
|
|
constructor(time, visibility, elongation, ecliptic_separation) {
|
|
this.time = time;
|
|
this.visibility = visibility;
|
|
this.elongation = elongation;
|
|
this.ecliptic_separation = ecliptic_separation;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates angular separation of a body from the Sun as seen from the Earth
|
|
* and the relative ecliptic longitudes between that body and the Earth as seen from the Sun.
|
|
* See the return type {@link Astronomy.ElongationEvent} for details.
|
|
*
|
|
* This function is helpful for determining how easy
|
|
* it is to view a planet away from the Sun's glare on a given date.
|
|
* It also determines whether the object is visible in the morning or evening;
|
|
* this is more important the smaller the elongation is.
|
|
* It is also used to determine how far a planet is from opposition, conjunction, or quadrature.
|
|
*
|
|
* @param {string} body
|
|
* The name of the observed body. Not allowed to be <code>"Earth"</code>.
|
|
*
|
|
* @returns {Astronomy.ElongationEvent}
|
|
*/
|
|
Astronomy.Elongation = function(body, date) {
|
|
let time = Astronomy.MakeTime(date);
|
|
|
|
let lon = Astronomy.LongitudeFromSun(body, time);
|
|
let vis;
|
|
if (lon > 180) {
|
|
vis = 'morning';
|
|
lon = 360 - lon;
|
|
} else {
|
|
vis = 'evening';
|
|
}
|
|
let angle = Astronomy.AngleFromSun(body, time);
|
|
return new ElongationEvent(time, vis, angle, lon);
|
|
}
|
|
|
|
/**
|
|
* Searches for the next maximum elongation event for Mercury or Venus
|
|
* that occurs after the given start date. Calling with other values
|
|
* of <code>body</code> will result in an exception.
|
|
* Maximum elongation occurs when the body has the greatest
|
|
* angular separation from the Sun, as seen from the Earth.
|
|
* Returns an <code>ElongationEvent</code> object containing the date and time of the next
|
|
* maximum elongation, the elongation in degrees, and whether
|
|
* the body is visible in the morning or evening.
|
|
*
|
|
* @param {string} body Either <code>"Mercury"</code> or <code>"Venus"</code>.
|
|
* @param {Date} startDate The date and time after which to search for the next maximum elongation event.
|
|
*
|
|
* @returns {Astronomy.ElongationEvent}
|
|
*/
|
|
Astronomy.SearchMaxElongation = function(body, startDate) {
|
|
const dt = 0.01;
|
|
|
|
function neg_slope(t) {
|
|
// The slope de/dt goes from positive to negative at the maximum elongation event.
|
|
// But Search() is designed for functions that ascend through zero.
|
|
// So this function returns the negative slope.
|
|
const t1 = t.AddDays(-dt/2);
|
|
const t2 = t.AddDays(+dt/2);
|
|
let e1 = Astronomy.AngleFromSun(body, t1);
|
|
let e2 = Astronomy.AngleFromSun(body, t2);
|
|
let m = (e1-e2)/dt;
|
|
return m;
|
|
}
|
|
|
|
let startTime = Astronomy.MakeTime(startDate);
|
|
|
|
const table = {
|
|
Mercury : { s1:50.0, s2:85.0 },
|
|
Venus : { s1:40.0, s2:50.0 }
|
|
};
|
|
|
|
const planet = table[body];
|
|
if (!planet)
|
|
throw 'SearchMaxElongation works for Mercury and Venus only.';
|
|
|
|
let iter = 0;
|
|
while (++iter <= 2) {
|
|
// Find current heliocentric relative longitude between the
|
|
// inferior planet and the Earth.
|
|
let plon = Astronomy.EclipticLongitude(body, startTime);
|
|
let elon = Astronomy.EclipticLongitude('Earth', startTime);
|
|
let rlon = LongitudeOffset(plon - elon); // clamp to (-180, +180]
|
|
|
|
// The slope function is not well-behaved when rlon is near 0 degrees or 180 degrees
|
|
// because there is a cusp there that causes a discontinuity in the derivative.
|
|
// So we need to guard against searching near such times.
|
|
|
|
let t1, t2;
|
|
let rlon_lo, rlon_hi, adjust_days;
|
|
if (rlon >= -planet.s1 && rlon < +planet.s1 ) {
|
|
// Seek to the window [+s1, +s2].
|
|
adjust_days = 0;
|
|
// Search forward for the time t1 when rel lon = +s1.
|
|
rlon_lo = +planet.s1;
|
|
// Search forward for the time t2 when rel lon = +s2.
|
|
rlon_hi = +planet.s2;
|
|
} else if (rlon >= +planet.s2 || rlon < -planet.s2 ) {
|
|
// Seek to the next search window at [-s2, -s1].
|
|
adjust_days = 0;
|
|
// Search forward for the time t1 when rel lon = -s2.
|
|
rlon_lo = -planet.s2;
|
|
// Search forward for the time t2 when rel lon = -s1.
|
|
rlon_hi = -planet.s1;
|
|
} else if (rlon >= 0) {
|
|
// rlon must be in the middle of the window [+s1, +s2].
|
|
// Search BACKWARD for the time t1 when rel lon = +s1.
|
|
adjust_days = -SynodicPeriod(body) / 4;
|
|
rlon_lo = +planet.s1;
|
|
rlon_hi = +planet.s2;
|
|
// Search forward from t1 to find t2 such that rel lon = +s2.
|
|
} else {
|
|
// rlon must be in the middle of the window [-s2, -s1].
|
|
// Search BACKWARD for the time t1 when rel lon = -s2.
|
|
adjust_days = -SynodicPeriod(body) / 4;
|
|
rlon_lo = -planet.s2;
|
|
// Search forward from t1 to find t2 such that rel lon = -s1.
|
|
rlon_hi = -planet.s1;
|
|
}
|
|
|
|
let t_start = startTime.AddDays(adjust_days);
|
|
t1 = Astronomy.SearchRelativeLongitude(body, rlon_lo, t_start);
|
|
t2 = Astronomy.SearchRelativeLongitude(body, rlon_hi, t1);
|
|
|
|
// Now we have a time range [t1,t2] that brackets a maximum elongation event.
|
|
// Confirm the bracketing.
|
|
let m1 = neg_slope(t1);
|
|
if (m1 >= 0)
|
|
throw `SearchMaxElongation: internal error: m1 = ${m1}`;
|
|
|
|
let m2 = neg_slope(t2);
|
|
if (m2 <= 0)
|
|
throw `SearchMaxElongation: internal error: m2 = ${m2}`;
|
|
|
|
// Use the generic search algorithm to home in on where the slope crosses from negative to positive.
|
|
let tx = Astronomy.Search(neg_slope, t1, t2, {init_f1:m1, init_f2:m2, dt_tolerance_seconds:10});
|
|
if (!tx)
|
|
throw `SearchMaxElongation: failed search iter ${iter} (t1=${t1.toString()}, t2=${t2.toString()})`;
|
|
|
|
if (tx.tt >= startTime.tt)
|
|
return Astronomy.Elongation(body, tx);
|
|
|
|
// This event is in the past (earlier than startDate).
|
|
// We need to search forward from t2 to find the next possible window.
|
|
// We never need to search more than twice.
|
|
startTime = t2.AddDays(1);
|
|
}
|
|
|
|
throw `SearchMaxElongation: failed to find event after 2 tries.`;
|
|
}
|
|
|
|
/**
|
|
* Searches for the date and time Venus will next appear brightest as seen from the Earth.
|
|
*
|
|
* @param {string} body
|
|
* Currently only <code>"Venus"</code> is supported.
|
|
* Mercury's peak magnitude occurs at superior conjunction, when it is virtually impossible to see from Earth,
|
|
* so peak magnitude events have little practical value for that planet.
|
|
* The Moon reaches peak magnitude very close to full moon, which can be found using
|
|
* {@link Astronomy.SearchMoonQuarter} or {@link Astronomy.SearchMoonPhase}.
|
|
* The other planets reach peak magnitude very close to opposition,
|
|
* which can be found using {@link Astronomy.SearchRelativeLongitude}.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} startDate
|
|
* The date and time after which to find the next peak magnitude event.
|
|
*
|
|
* @returns {Astronomy.IlluminationInfo}
|
|
*/
|
|
Astronomy.SearchPeakMagnitude = function(body, startDate) {
|
|
if (body !== 'Venus')
|
|
throw 'SearchPeakMagnitude currently works for Venus only.';
|
|
|
|
const dt = 0.01;
|
|
|
|
function slope(t) {
|
|
// The Search() function finds a transition from negative to positive values.
|
|
// The derivative of magnitude y with respect to time t (dy/dt)
|
|
// is negative as an object gets brighter, because the magnitude numbers
|
|
// get smaller. At peak magnitude dy/dt = 0, then as the object gets dimmer,
|
|
// dy/dt > 0.
|
|
const t1 = t.AddDays(-dt/2);
|
|
const t2 = t.AddDays(+dt/2);
|
|
const y1 = Astronomy.Illumination(body, t1).mag;
|
|
const y2 = Astronomy.Illumination(body, t2).mag;
|
|
const m = (y2-y1) / dt;
|
|
return m;
|
|
}
|
|
|
|
let startTime = Astronomy.MakeTime(startDate);
|
|
|
|
// s1 and s2 are relative longitudes within which peak magnitude of Venus can occur.
|
|
const s1 = 10.0;
|
|
const s2 = 30.0;
|
|
|
|
let iter = 0;
|
|
while (++iter <= 2) {
|
|
// Find current heliocentric relative longitude between the
|
|
// inferior planet and the Earth.
|
|
let plon = Astronomy.EclipticLongitude(body, startTime);
|
|
let elon = Astronomy.EclipticLongitude('Earth', startTime);
|
|
let rlon = LongitudeOffset(plon - elon); // clamp to (-180, +180]
|
|
|
|
// The slope function is not well-behaved when rlon is near 0 degrees or 180 degrees
|
|
// because there is a cusp there that causes a discontinuity in the derivative.
|
|
// So we need to guard against searching near such times.
|
|
|
|
let t1, t2;
|
|
let rlon_lo, rlon_hi, adjust_days;
|
|
if (rlon >= -s1 && rlon < +s1) {
|
|
// Seek to the window [+s1, +s2].
|
|
adjust_days = 0;
|
|
// Search forward for the time t1 when rel lon = +s1.
|
|
rlon_lo = +s1;
|
|
// Search forward for the time t2 when rel lon = +s2.
|
|
rlon_hi = +s2;
|
|
} else if (rlon >= +s2 || rlon < -s2 ) {
|
|
// Seek to the next search window at [-s2, -s1].
|
|
adjust_days = 0;
|
|
// Search forward for the time t1 when rel lon = -s2.
|
|
rlon_lo = -s2;
|
|
// Search forward for the time t2 when rel lon = -s1.
|
|
rlon_hi = -s1;
|
|
} else if (rlon >= 0) {
|
|
// rlon must be in the middle of the window [+s1, +s2].
|
|
// Search BACKWARD for the time t1 when rel lon = +s1.
|
|
adjust_days = -SynodicPeriod(body) / 4;
|
|
rlon_lo = +s1;
|
|
// Search forward from t1 to find t2 such that rel lon = +s2.
|
|
rlon_hi = +s2;
|
|
} else {
|
|
// rlon must be in the middle of the window [-s2, -s1].
|
|
// Search BACKWARD for the time t1 when rel lon = -s2.
|
|
adjust_days = -SynodicPeriod(body) / 4;
|
|
rlon_lo = -s2;
|
|
// Search forward from t1 to find t2 such that rel lon = -s1.
|
|
rlon_hi = -s1;
|
|
}
|
|
|
|
let t_start = startTime.AddDays(adjust_days);
|
|
t1 = Astronomy.SearchRelativeLongitude(body, rlon_lo, t_start);
|
|
t2 = Astronomy.SearchRelativeLongitude(body, rlon_hi, t1);
|
|
|
|
// Now we have a time range [t1,t2] that brackets a maximum magnitude event.
|
|
// Confirm the bracketing.
|
|
let m1 = slope(t1);
|
|
if (m1 >= 0)
|
|
throw `SearchPeakMagnitude: internal error: m1 = ${m1}`;
|
|
|
|
let m2 = slope(t2);
|
|
if (m2 <= 0)
|
|
throw `SearchPeakMagnitude: internal error: m2 = ${m2}`;
|
|
|
|
// Use the generic search algorithm to home in on where the slope crosses from negative to positive.
|
|
let tx = Astronomy.Search(slope, t1, t2, {init_f1:m1, init_f2:m2, dt_tolerance_seconds:10});
|
|
if (!tx)
|
|
throw `SearchPeakMagnitude: failed search iter ${iter} (t1=${t1.toString()}, t2=${t2.toString()})`;
|
|
|
|
if (tx.tt >= startTime.tt)
|
|
return Astronomy.Illumination(body, tx);
|
|
|
|
// This event is in the past (earlier than startDate).
|
|
// We need to search forward from t2 to find the next possible window.
|
|
// We never need to search more than twice.
|
|
startTime = t2.AddDays(1);
|
|
}
|
|
|
|
throw `SearchPeakMagnitude: failed to find event after 2 tries.`;
|
|
}
|
|
|
|
/**
|
|
* Represents a closest or farthest point in a body's orbit around its primary.
|
|
* For a planet orbiting the Sun, this is a perihelion or aphelion, respectively.
|
|
* For the Moon orbiting the Earth, this is a perigee or apogee, respectively.
|
|
*
|
|
* @class
|
|
* @memberof Astronomy
|
|
*
|
|
* @property {Astronomy.AstroTime} time
|
|
* The date and time of the apsis.
|
|
*
|
|
* @property {number} kind
|
|
* For a closest approach (perigee or perihelion), <code>kind</code> is 0.
|
|
* For a farthest distance event (apogee or aphelion), <code>kind</code> is 1.
|
|
*
|
|
* @property {number} dist_au
|
|
* The distance between the centers of the two bodies in astronomical units (AU).
|
|
*
|
|
* @property {number} dist_km
|
|
* The distance between the centers of the two bodies in kilometers.
|
|
*
|
|
* @see {@link Astronomy.SearchLunarApsis}
|
|
* @see {@link Astronomy.NextLunarApsis}
|
|
*/
|
|
class Apsis {
|
|
constructor(time, kind, dist_au) {
|
|
this.time = time;
|
|
this.kind = kind;
|
|
this.dist_au = dist_au;
|
|
this.dist_km = dist_au * KM_PER_AU;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the next perigee (closest approach) or apogee (farthest remove) of the Moon
|
|
* that occurs after the specified date and time.
|
|
*
|
|
* @param {(Date | number | Astronomy.AstroTime)} startDate
|
|
* The date and time after which to find the next perigee or apogee.
|
|
*
|
|
* @returns {Astronomy.Apsis}
|
|
*/
|
|
Astronomy.SearchLunarApsis = function(startDate) {
|
|
const dt = 0.001;
|
|
|
|
function distance_slope(t) {
|
|
let t1 = t.AddDays(-dt/2);
|
|
let t2 = t.AddDays(+dt/2);
|
|
|
|
let r1 = CalcMoon(t1).distance_au;
|
|
let r2 = CalcMoon(t2).distance_au;
|
|
|
|
let m = (r2-r1) / dt;
|
|
return m;
|
|
}
|
|
|
|
function negative_distance_slope(t) {
|
|
return -distance_slope(t);
|
|
}
|
|
|
|
// Check the rate of change of the distance dr/dt at the start time.
|
|
// If it is positive, the Moon is currently getting farther away,
|
|
// so start looking for apogee.
|
|
// Conversely, if dr/dt < 0, start looking for perigee.
|
|
// Either way, the polarity of the slope will change, so the product will be negative.
|
|
// Handle the crazy corner case of exactly touching zero by checking for m1*m2 <= 0.
|
|
|
|
let t1 = Astronomy.MakeTime(startDate);
|
|
let m1 = distance_slope(t1);
|
|
const increment = 5; // number of days to skip in each iteration
|
|
|
|
++Perf.lunar_apsis_calls;
|
|
for (var iter = 0; iter * increment < 2 * MEAN_SYNODIC_MONTH; ++iter) {
|
|
++Perf.lunar_apsis_iter;
|
|
let t2 = t1.AddDays(increment);
|
|
let m2 = distance_slope(t2);
|
|
|
|
if (m1 * m2 <= 0) {
|
|
// The time range [t1, t2] contains an apsis.
|
|
// Figure out whether it is perigee or apogee.
|
|
|
|
if (m1 < 0 || m2 > 0) {
|
|
// We found a minimum distance event: perigee.
|
|
// Search the time range [t1, t2] for the time when the slope goes
|
|
// from negative to positive.
|
|
let tx = Astronomy.Search(distance_slope, t1, t2, {init_f1:m1, init_f2:m2});
|
|
if (tx == null)
|
|
throw 'SearchLunarApsis INTERNAL ERROR: perigee search failed!';
|
|
|
|
let dist = CalcMoon(tx).distance_au;
|
|
return new Apsis(tx, 0, dist);
|
|
}
|
|
|
|
if (m1 > 0 || m2 < 0) {
|
|
// We found a maximum distance event: apogee.
|
|
// Search the time range [t1, t2] for the time when the slope goes
|
|
// from positive to negative.
|
|
let tx = Astronomy.Search(negative_distance_slope, t1, t2, {init_f1:-m1, init_f2:-m2});
|
|
if (tx == null)
|
|
throw 'SearchLunarApsis INTERNAL ERROR: apogee search failed!';
|
|
|
|
let dist = CalcMoon(tx).distance_au;
|
|
return new Apsis(tx, 1, dist);
|
|
}
|
|
|
|
// This should never happen; it should not be possible for consecutive
|
|
// times t1 and t2 to both have zero slope.
|
|
throw 'SearchLunarApsis INTERNAL ERROR: cannot classify apsis event!';
|
|
}
|
|
|
|
t1 = t2;
|
|
m1 = m2;
|
|
}
|
|
|
|
// It should not be possible to fail to find an apsis within 2 synodic months.
|
|
throw 'SearchLunarApsis INTERNAL ERROR: could not find apsis within 2 synodic months of start date.';
|
|
}
|
|
|
|
/**
|
|
* Given a lunar apsis returned by an initial call to {@link SearchLunarApsis},
|
|
* or a previous call to <code>NextLunarApsis</code>, finds the next lunar apsis.
|
|
* If the given apsis is a perigee, this function finds the next apogee, and vice versa.
|
|
*
|
|
* @param {Astronomy.Apsis} apsis
|
|
* A lunar perigee or apogee event.
|
|
*
|
|
* @returns {Astronomy.Apsis}
|
|
* The successor apogee for the given perigee, or the successor perigee for the given apogee.
|
|
*/
|
|
Astronomy.NextLunarApsis = function(apsis) {
|
|
const skip = 11; // number of days to skip to start looking for next apsis event
|
|
let next = Astronomy.SearchLunarApsis(apsis.time.AddDays(skip));
|
|
if (next.kind + apsis.kind !== 1) {
|
|
throw `NextLunarApsis INTERNAL ERROR: did not find alternating apogee/perigee: prev=${apsis.kind} @ ${apsis.time.toString()}, next=${next.kind} @ ${next.time.toString()}`;
|
|
}
|
|
return next;
|
|
}
|
|
|
|
})(typeof exports==='undefined' ? (this.Astronomy={}) : exports);
|