celestial-chart 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/kepler.js ADDED
@@ -0,0 +1,481 @@
1
+ import { Moon } from "./moon.js";
2
+ import { deg2rad, halfπ, τ } from "./transform.js";
3
+ import { Trig, dateParse, has } from "./util.js";
4
+
5
+ /* global transform, Trig, Moon, deg2rad, dateParse, has, halfπ, τ */
6
+ var gmdat = {
7
+ "sol": 0.0002959122082855911025, // AU^3/d^2
8
+ "mer": 164468599544771, //km^3/d^2
9
+ "ven": 2425056445892137,
10
+ "ter": 2975536307796296,
11
+ "lun": 36599199229256,
12
+ "mar": 319711652803400,
13
+ "cer": 467549107200,
14
+ "ves": 129071530155,
15
+ "jup": 945905718740635000,
16
+ "sat": 283224952705891000,
17
+ "ura": 43256077238632300,
18
+ "nep": 51034401552155700,
19
+ "plu": 7327611364884,
20
+ "eri": 8271175680000
21
+ },
22
+
23
+
24
+ symbols = {
25
+ "sol":"\u2609", "mer":"\u263f", "ven":"\u2640", "ter":"\u2295", "lun":"\u25cf", "mar":"\u2642", "cer":"\u26b3",
26
+ "ves":"\u26b6", "jup":"\u2643", "sat":"\u2644", "ura":"\u2645", "nep":"\u2646", "plu":"\u2647", "eri":"\u26aa"
27
+ },
28
+
29
+ ε = 23.43928 * deg2rad,
30
+ sinε = Math.sin(ε),
31
+ cosε = Math.cos(ε),
32
+ kelements = ["a","e","i","w","M","L","W","N","n","ep","ref","lecl","becl","Tilt"];
33
+ /*
34
+ ep = epoch (iso-date)
35
+ N = longitude of the ascending node (deg) Ω
36
+ i = inclination to the refrence plane, default:ecliptic (deg)
37
+ w = argument of periapsis (deg) ω
38
+ a = semi-major axis, or mean distance from parent body (AU,km)
39
+ e = eccentricity (0=circle, 0-1=ellipse, 1=parabola, >1=hyperbola ) (-)
40
+ M = mean anomaly (0 at periapsis; increases uniformly with time) (deg)
41
+ n = mean daily motion = 360/P (deg/day)
42
+
43
+ W = N + w = longitude of periapsis ϖ
44
+ L = M + W = mean longitude
45
+ q = a*(1-e) = periapsis distance
46
+ Q = a*(1+e) = apoapsis distance
47
+ P = 2π * sqrt(a^3/GM) = orbital period (years)
48
+ T = Epoch_of_M - (M(deg)/360_deg) / P = time of periapsis
49
+ v = true anomaly (angle between position and periapsis) ν
50
+ E = eccentric anomaly
51
+
52
+ Mandatory: a, e, i, N, w|W, M|L, dM|n
53
+
54
+ */
55
+
56
+ var Kepler = function () {
57
+ var gm = gmdat.sol,
58
+ parentBody = "sol",
59
+ elem = {}, dat = {},
60
+ id, name, symbol;
61
+
62
+
63
+ function kepler(date) {
64
+ dates(date);
65
+ if (id === "sol") {
66
+ dat.ephemeris.x = 0;
67
+ dat.ephemeris.y = 0;
68
+ dat.ephemeris.z = 0;
69
+ dat.ephemeris.mag = -6;
70
+ return kepler;
71
+ }
72
+ coordinates();
73
+ return kepler;
74
+ }
75
+
76
+ var dates = function(date) {
77
+ var dt, de = dat.ephemeris = {};
78
+ if (date) {
79
+ if (date instanceof Date) { dt = new Date(date.valueOf()); }
80
+ else { dt = dateParse(date); }
81
+ }
82
+ if (!dt) { dt = new Date(); }
83
+ de.jd = JD(dt);
84
+
85
+ dt = dateParse(elem.ep);
86
+ if (!dt) dt = dateParse("2000-01-01");
87
+ de.jd0 = JD(dt);
88
+ de.d = de.jd - de.jd0;
89
+ de.cy = de.d / 36525;
90
+ };
91
+
92
+ var coordinates = function() {
93
+ var key, de = dat.ephemeris;
94
+ if (id === "lun") {
95
+ de = moon_elements(de);
96
+ if (!de) return;
97
+ } else {
98
+ for (var i=0; i<kelements.length; i++) {
99
+ key = kelements[i];
100
+ if (!has(elem, key)) continue;
101
+ if (has(elem, "d"+key)) {
102
+ de[key] = elem[key] + elem["d"+key] * de.cy;
103
+ } else if (has(elem, key)) {
104
+ de[key] = elem[key];
105
+ }
106
+ }
107
+ if (has(de, "M") && !has(de, "dM") && has(de, "n")) {
108
+ de.M += (de.n * de.d);
109
+ }
110
+ }
111
+ derive();
112
+ trueAnomaly();
113
+ cartesian();
114
+ };
115
+
116
+ kepler.cartesian = function() {
117
+ return dat;
118
+ };
119
+
120
+ kepler.spherical = function() {
121
+ spherical();
122
+ return dat;
123
+ };
124
+
125
+ kepler.equatorial = function(pos) {
126
+ equatorial(pos);
127
+ return dat;
128
+ };
129
+
130
+ kepler.transpose = function() {
131
+ transpose(dat);
132
+ return dat;
133
+ };
134
+
135
+ kepler.elements = function(_) {
136
+ var key;
137
+
138
+ if (!arguments.length || arguments[0] === undefined) return kepler;
139
+
140
+ for (var i=0; i<kelements.length; i++) {
141
+ key = kelements[i];
142
+ if (!has(_, key)) continue;
143
+ elem[key] = _[key];
144
+
145
+ if (key === "a" || key === "e") elem[key] *= 1.0;
146
+ else if (key !== "ref" && key !== "ep") elem[key] *= deg2rad;
147
+
148
+ if (has(_, "d" + key)) {
149
+ key = "d" + key;
150
+ elem[key] = _[key];
151
+ if (key === "da" || key === "de") elem[key] *= 1.0;
152
+ else elem[key] *= deg2rad;
153
+ }
154
+ }
155
+ return kepler;
156
+ };
157
+
158
+ kepler.params = function(_) {
159
+ if (!arguments.length) return kepler;
160
+ for (var par in _) {
161
+ if (!has(_, par)) continue;
162
+ if (_[par] === "elements") continue;
163
+ dat[par] = _[par];
164
+ }
165
+ return kepler;
166
+ };
167
+
168
+
169
+ kepler.parentBody = function(_) {
170
+ if (!arguments.length) return parentBody;
171
+ parentBody = _;
172
+ gm = gmdat[parentBody];
173
+ return kepler;
174
+ };
175
+
176
+ kepler.id = function(_) {
177
+ if (!arguments.length) return id;
178
+ id = _;
179
+ symbol = symbols[_];
180
+ return kepler;
181
+ };
182
+
183
+ kepler.Name = function(_) {
184
+ if (!arguments.length) return name;
185
+ name = _;
186
+ return kepler;
187
+ };
188
+
189
+ kepler.symbol = function(_) {
190
+ if (!arguments.length) return symbol;
191
+ symbol = symbols[_];
192
+ return kepler;
193
+ };
194
+
195
+
196
+ function near_parabolic(E, e) {
197
+ var anom2 = e > 1.0 ? E*E : -E*E,
198
+ term = e * anom2 * E / 6.0,
199
+ rval = (1.0 - e) * E - term,
200
+ n = 4;
201
+
202
+ while(Math.abs(term) > 1e-15) {
203
+ term *= anom2 / (n * (n + 1));
204
+ rval -= term;
205
+ n += 2;
206
+ }
207
+ return(rval);
208
+ }
209
+
210
+ function anomaly() {
211
+ var de = dat.ephemeris,
212
+ curr, err, trial, tmod,
213
+ e = de.e, M = de.M,
214
+ thresh = 1e-8,
215
+ offset = 0.0,
216
+ delta_curr = 1.9,
217
+ is_negative = false,
218
+ n_iter = 0;
219
+
220
+ if (!M) return(0.0);
221
+
222
+ if (e < 1.0) {
223
+ if (M < -Math.PI || M > Math.PI) {
224
+ tmod = Trig.normalize0(M);
225
+ offset = M - tmod;
226
+ M = tmod;
227
+ }
228
+
229
+ if (e < 0.9) {
230
+ curr = Math.atan2(Math.sin(M), Math.cos(M) - e);
231
+ do {
232
+ err = (curr - e * Math.sin(curr) - M) / (1.0 - e * Math.cos(curr));
233
+ curr -= err;
234
+ } while (Math.abs(err) > thresh);
235
+ return curr; // + offset;
236
+ }
237
+ }
238
+
239
+ if ( M < 0.0) {
240
+ M = -M;
241
+ is_negative = true;
242
+ }
243
+
244
+ curr = M;
245
+ thresh = thresh * Math.abs(1.0 - e);
246
+ /* Due to roundoff error, there's no way we can hope to */
247
+ /* get below a certain minimum threshhold anyway: */
248
+ if ( thresh < 1e-15) { thresh = 1e-15; }
249
+ if ( (e > 0.8 && M < Math.PI / 3.0) || e > 1.0) { /* up to 60 degrees */
250
+ trial = M / Math.abs( 1.0 - e);
251
+
252
+ if (trial * trial > 6.0 * Math.abs(1.0 - e)) { /* cubic term is dominant */
253
+ if (M < Math.PI) {
254
+ trial = Math.pow(6.0 * M, 1/3);
255
+ } else { /* hyperbolic w/ 5th & higher-order terms predominant */
256
+ trial = Trig.asinh( M / e);
257
+ }
258
+ }
259
+ curr = trial;
260
+ }
261
+ if (e > 1.0 && M > 4.0) { /* hyperbolic, large-mean-anomaly case */
262
+ curr = Math.log(M);
263
+ }
264
+ if (e < 1.0) {
265
+ while(Math.abs(delta_curr) > thresh) {
266
+ if ( n_iter++ > 8) {
267
+ err = near_parabolic(curr, e) - M;
268
+ } else {
269
+ err = curr - e * Math.sin(curr) - M;
270
+ }
271
+ delta_curr = -err / (1.0 - e * Math.cos(curr));
272
+ curr += delta_curr;
273
+ }
274
+ } else {
275
+ while (Math.abs(delta_curr) > thresh) {
276
+ if (n_iter++ > 7) {
277
+ err = -near_parabolic(curr, e) - M;
278
+ } else {
279
+ err = e * Trig.sinh(curr) - curr - M;
280
+ }
281
+ delta_curr = -err / (e * Trig.cosh(curr) - 1.0);
282
+ curr += delta_curr;
283
+ }
284
+ }
285
+ return( is_negative ? offset - curr : offset + curr);
286
+ }
287
+
288
+ function trueAnomaly() {
289
+ var x, y, r0, g, t, de = dat.ephemeris;
290
+
291
+ if (de.e === 1.0) { /* parabolic */
292
+ t = de.jd0 - de.T;
293
+ g = de.w0 * t * 0.5;
294
+
295
+ y = Math.pow(g + Math.sqrt(g * g + 1.0), 1/3);
296
+ de.v = 2.0 * Math.atan(y - 1.0 / y);
297
+ } else { /* got the mean anomaly; compute eccentric, then true */
298
+ de.E = anomaly();
299
+ if (de.e > 1.0) { /* hyperbolic case */
300
+ x = (de.e - Trig.cosh(de.E));
301
+ y = Trig.sinh(de.E);
302
+ } else { /* elliptical case */
303
+ x = (Math.cos(de.E) - de.e);
304
+ y = Math.sin(de.E);
305
+ }
306
+ y *= Math.sqrt(Math.abs(1.0 - de.e * de.e));
307
+ de.v = Math.atan2(y, x);
308
+ }
309
+
310
+ r0 = de.q * (1.0 + de.e);
311
+ de.r = r0 / (1.0 + de.e * Math.cos(de.v));
312
+ }
313
+
314
+ function derive() {
315
+ var de = dat.ephemeris;
316
+ if (!de.hasOwnProperty("w")) {
317
+ de.w = de.W - de.N;
318
+ }
319
+ if (!de.hasOwnProperty("M")) {
320
+ de.M = de.L - de.W;
321
+ }
322
+ if (de.e < 1.0) { de.M = Trig.normalize0(de.M); }
323
+ //de.P = Math.pow(Math.abs(de.a), 1.5);
324
+ de.P = τ * Math.sqrt(Math.pow(de.a, 3) / gm) / 365.25;
325
+ de.T = de.jd0 - (de.M / halfπ) / de.P;
326
+
327
+ if (de.e !== 1.0) { /* for non-parabolic orbits: */
328
+ de.q = de.a * (1.0 - de.e);
329
+ de.t0 = de.a * Math.sqrt(Math.abs(de.a) / gm);
330
+ } else {
331
+ de.w0 = (3.0 / Math.sqrt(2)) / (de.q * Math.sqrt(de.q / gm));
332
+ de.a = 0.0;
333
+ de.t0 = 0.0;
334
+ }
335
+ de.am = Math.sqrt(gm * de.q * (1.0 + de.e));
336
+ }
337
+
338
+ function transpose() {
339
+ var de = dat.ephemeris;
340
+ if (!de.ref || de.ref === "ecl") {
341
+ de.tx = de.x;
342
+ de.ty = de.y;
343
+ de.tz = de.z;
344
+ return;
345
+ }
346
+ var a0 = de.lecl,// - Math.PI/2,
347
+ a1 = Math.PI/2 - de.becl,
348
+ angles = [0, a1, -a0];
349
+ transform(de, angles);
350
+ var tp = Trig.cartesian([de.tl, de.tb, de.r]);
351
+ de.tx = tp.x;
352
+ de.ty = tp.y;
353
+ de.tz = tp.z;
354
+ }
355
+
356
+ function equatorial(pos) {
357
+ var de = dat.ephemeris, pe = pos.ephemeris;
358
+ ε = (23.439292 - 0.0130042 * de.cy - 1.667e-7 * de.cy * de.cy + 5.028e-7 * de.cy * de.cy * de.cy) * deg2rad;
359
+ sinε = Math.sin(ε);
360
+ cosε = Math.cos(ε);
361
+ var o = (id === "lun") ? {x:0, y:0, z:0} : {x:pe.x, y:pe.y, z:pe.z};
362
+ de.xeq = de.x - o.x;
363
+ de.yeq = (de.y - o.y) * cosε - (de.z - o.z) * sinε;
364
+ de.zeq = (de.y - o.y) * sinε + (de.z - o.z) * cosε;
365
+
366
+ de.ra = Trig.normalize(Math.atan2(de.yeq, de.xeq));
367
+ de.dec = Math.atan2(de.zeq, Math.sqrt(de.xeq*de.xeq + de.yeq*de.yeq));
368
+ if (id === "lun") de = moon_corr(de, pe);
369
+ de.pos = [de.ra / deg2rad, de.dec / deg2rad];
370
+ de.rt = Math.sqrt(de.xeq*de.xeq + de.yeq*de.yeq + de.zeq*de.zeq);
371
+ if (id !== "sol") de.mag = magnitude();
372
+ }
373
+
374
+ function magnitude() {
375
+ var de = dat.ephemeris,
376
+ rs = de.r, rt = de.rt,
377
+ a = Math.acos((rs*rs + rt*rt - 1) / (2 * rs * rt)),
378
+ q = 0.666 *((1-a/Math.PI) * Math.cos(a) + 1 / Math.PI * Math.sin(a)),
379
+ m = dat.H * 1 + 5 * Math.log(rs*rt) * Math.LOG10E - 2.5 * Math.log(q) * Math.LOG10E;
380
+
381
+ return m;
382
+ }
383
+
384
+ function cartesian() {
385
+ var de = dat.ephemeris,
386
+ u = de.v + de.w;
387
+ de.x = de.r * (Math.cos(de.N) * Math.cos(u) - Math.sin(de.N) * Math.sin(u) * Math.cos(de.i));
388
+ de.y = de.r * (Math.sin(de.N) * Math.cos(u) + Math.cos(de.N) * Math.sin(u) * Math.cos(de.i));
389
+ de.z = de.r * (Math.sin(u) * Math.sin(de.i));
390
+ return dat;
391
+ }
392
+
393
+ function spherical() {
394
+ var de = dat.ephemeris,
395
+ lon = Math.atan2(de.y, de.x),
396
+ lat = Math.atan2(de.z, Math.sqrt(de.x*de.x + de.y*de.y));
397
+ de.l = Trig.normalize(lon);
398
+ de.b = lat;
399
+ return dat;
400
+ }
401
+
402
+ function transform(angles) {
403
+
404
+ }
405
+
406
+ function polar2cart(pos) {
407
+ var rclat = Math.cos(pos.lat) * pos.r;
408
+ pos.x = rclat * Math.cos(pos.lon);
409
+ pos.y = rclat * Math.sin(pos.lon);
410
+ pos.z = pos.r * Math.sin(pos.lat);
411
+ return pos;
412
+ }
413
+
414
+
415
+ function JD(dt) {
416
+ var yr = dt.getUTCFullYear(),
417
+ mo = dt.getUTCMonth() + 1,
418
+ dy = dt.getUTCDate(),
419
+ frac = (dt.getUTCHours() - 12 + dt.getUTCMinutes()/60.0 + dt.getUTCSeconds()/3600.0) / 24,
420
+ IYMIN = -4799; /* Earliest year allowed (4800BC) */
421
+
422
+ if (yr < IYMIN) return -1;
423
+ var a = Math.floor((14 - mo) / 12),
424
+ y = yr + 4800 - a,
425
+ m = mo + (12 * a) - 3;
426
+ var jdn = dy + Math.floor((153 * m + 2)/5) + (365 * y) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
427
+ return jdn + frac;
428
+ }
429
+
430
+ function mst(lon) {
431
+ var l = lon || 0; // lon=0 -> gmst
432
+ return (18.697374558 + 24.06570982441908 * dat.ephemeris.d) * 15 + l;
433
+ }
434
+
435
+
436
+ function observer(pos) {
437
+ var flat = 298.257223563, // WGS84 flattening of earth
438
+ re = 6378.137, // GRS80/WGS84 semi major axis of earth ellipsoid
439
+ h = pos.h || 0,
440
+ cart = {},
441
+ gmst = mst();
442
+
443
+ var cosl = Math.cos(pos.lat),
444
+ sinl = Math.sin(pos.lat),
445
+ fl = 1.0 - 1.0 / flat;
446
+ var fl2 = fl * fl;
447
+
448
+ var u = 1.0 / Math.sqrt (cosl * cosl + fl2 * sinl * sinl),
449
+ a = re * u + h,
450
+ b = re * fl2 * u + h,
451
+ r = Math.sqrt (a * a * cosl * cosl + b * b * sinl * sinl); // geocentric distance from earth center
452
+
453
+ cart.lat = Math.acos (a * cosl / r);
454
+ cart.lon = pos.lon;
455
+ cart.r = h;
456
+
457
+ if (pos.lat < 0.0) cart.lat *= -1;
458
+
459
+ polar2cart(cart);
460
+
461
+ // rotate around earth's polar axis to align coordinate system from Greenwich to vernal equinox
462
+ var angle = gmst * deg2rad; // sideral time gmst given in hours. Convert to radians
463
+
464
+ cart.x = cart.x * Math.cos(angle) - cart.y * Math.sin(angle);
465
+ cart.y = cart.x * Math.sin(angle) + cart.y * Math.cos(angle);
466
+ return(cart);
467
+ }
468
+
469
+ function moon_elements(dat) {
470
+ if ((typeof Moon !== "undefined")) return Moon.elements(dat);
471
+ }
472
+
473
+ function moon_corr(dat, pos) {
474
+ spherical();
475
+ if ((typeof Moon !== "undefined")) return Moon.corr(dat, pos);
476
+ }
477
+
478
+ return kepler;
479
+ };
480
+
481
+ export { Kepler };