caelus 0.2.1 → 0.4.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/README.md +14 -6
- package/accuracy.json +234 -25
- package/dist/src/chart.d.ts +59 -11
- package/dist/src/chart.js +238 -46
- package/dist/src/core.d.ts +57 -1
- package/dist/src/core.js +168 -21
- package/dist/src/events.d.ts +22 -0
- package/dist/src/events.js +156 -0
- package/dist/src/houses.d.ts +31 -0
- package/dist/src/houses.js +205 -2
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/node-loader.js +21 -2
- package/dist/src/pheno.d.ts +35 -0
- package/dist/src/pheno.js +152 -0
- package/package.json +1 -1
package/dist/src/chart.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/** astroengine chart -- public API: natal charts, aspects, retrogrades. */
|
|
2
|
-
import { DEG, mod, jdTT, julianDay, ChebSeries, planetApparent, sunApparent, moonApparentSeries, moonApparentPrecise, plutoApparent, chironApparent, meanNode, trueNodeSeries, trueNodePrecise, } from "./core.js";
|
|
2
|
+
import { DEG, mod, jdTT, julianDay, ChebSeries, planetApparent, sunApparent, moonApparentSeries, moonApparentPrecise, plutoApparent, chironApparent, meanNode, trueNodeSeries, trueNodePrecise, equatorial, ayanamsa, AYANAMSA_J2000, meanLilith, topocentricEcl, oscApogeePrecise, oscApogeeSeries, KeplerOrbit, trueObliquity, nutation, plutoHeliocentric, vsopHeliocentric, precessEcliptic, J2000, } from "./core.js";
|
|
3
3
|
import * as H from "./houses.js";
|
|
4
|
+
const TWO_PI = 2 * Math.PI;
|
|
4
5
|
export const BODIES = [
|
|
5
6
|
"sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn",
|
|
6
7
|
"uranus", "neptune", "pluto", "chiron", "mean_node", "true_node",
|
|
7
8
|
];
|
|
9
|
+
/** Computable on request (not in the default chart set). */
|
|
10
|
+
export const EXTRA_BODIES = ["mean_lilith", "true_lilith"];
|
|
11
|
+
/** Points: excluded from aspect search by default. */
|
|
12
|
+
const NOT_ASPECTABLE = new Set([
|
|
13
|
+
"mean_node", "true_node", "mean_lilith", "true_lilith",
|
|
14
|
+
]);
|
|
8
15
|
export const SIGNS = [
|
|
9
16
|
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra",
|
|
10
17
|
"Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
|
@@ -15,106 +22,291 @@ export const ASPECTS = {
|
|
|
15
22
|
export const DEFAULT_ORBS = {
|
|
16
23
|
conjunction: 8, sextile: 4, square: 7, trine: 7, opposition: 8,
|
|
17
24
|
};
|
|
25
|
+
const KM_PER_AU = 149597870.7;
|
|
26
|
+
function parseZodiac(zodiac) {
|
|
27
|
+
if (zodiac === "tropical")
|
|
28
|
+
return null;
|
|
29
|
+
if (zodiac.startsWith("sidereal:")) {
|
|
30
|
+
const mode = zodiac.slice("sidereal:".length);
|
|
31
|
+
if (AYANAMSA_J2000[mode] !== undefined)
|
|
32
|
+
return mode;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`unknown zodiac ${JSON.stringify(zodiac)}`);
|
|
35
|
+
}
|
|
36
|
+
const VSOP_BODIES = new Set([
|
|
37
|
+
"mercury", "venus", "earth", "mars", "jupiter", "saturn", "uranus", "neptune",
|
|
38
|
+
]);
|
|
18
39
|
export class Engine {
|
|
19
40
|
data;
|
|
20
41
|
moonCheb;
|
|
21
42
|
chironCheb;
|
|
43
|
+
packs = new Map();
|
|
22
44
|
constructor(data) {
|
|
23
45
|
this.data = data;
|
|
24
46
|
this.moonCheb = data.moonCheb ? new ChebSeries(data.moonCheb) : null;
|
|
25
47
|
this.chironCheb = data.chiron ? new ChebSeries(data.chiron) : null;
|
|
26
48
|
}
|
|
49
|
+
pack(body) {
|
|
50
|
+
let s = this.packs.get(body);
|
|
51
|
+
if (!s) {
|
|
52
|
+
const raw = this.data.chebPacks?.[body];
|
|
53
|
+
const kp = this.data.keplerPack;
|
|
54
|
+
if (raw)
|
|
55
|
+
s = new ChebSeries(raw);
|
|
56
|
+
else if (kp?.bodies[body])
|
|
57
|
+
s = new KeplerOrbit(kp.bodies[body], kp.epoch);
|
|
58
|
+
else
|
|
59
|
+
throw new Error(`no data loaded for body '${body}'`);
|
|
60
|
+
this.packs.set(body, s);
|
|
61
|
+
}
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
27
64
|
moonInRange(jde) {
|
|
28
65
|
return !!this.moonCheb
|
|
29
66
|
&& this.moonCheb.jd0 <= jde - 0.1 && jde + 0.1 <= this.moonCheb.jd1;
|
|
30
67
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
/** Body ids this engine can compute, given the data it was handed. */
|
|
69
|
+
bodies() {
|
|
70
|
+
return [
|
|
71
|
+
...[...BODIES, ...EXTRA_BODIES].filter((b) => b !== "chiron" || this.chironCheb),
|
|
72
|
+
...Object.keys(this.data.chebPacks ?? {}),
|
|
73
|
+
...Object.keys(this.data.keplerPack?.bodies ?? {}),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
/** Apparent geocentric [lon rad, lat rad, dist AU | null] at TT jde.
|
|
77
|
+
* Building block for the events module; chart consumers want
|
|
78
|
+
* position() instead. */
|
|
79
|
+
ecliptic(body, jde) {
|
|
80
|
+
if (body === "sun")
|
|
81
|
+
return sunApparent(this.data, jde);
|
|
82
|
+
if (body === "moon") {
|
|
83
|
+
const [lon, lat, km] = this.moonInRange(jde)
|
|
40
84
|
? moonApparentPrecise(this.data, this.moonCheb, jde)
|
|
41
85
|
: moonApparentSeries(this.data, jde);
|
|
86
|
+
return [lon, lat, km / KM_PER_AU];
|
|
87
|
+
}
|
|
88
|
+
if (body === "pluto")
|
|
89
|
+
return plutoApparent(this.data, jde);
|
|
90
|
+
if (body === "chiron") {
|
|
91
|
+
if (!this.chironCheb)
|
|
92
|
+
throw new Error("chiron data not loaded");
|
|
93
|
+
return chironApparent(this.data, this.chironCheb, jde);
|
|
94
|
+
}
|
|
95
|
+
if (body === "mean_node")
|
|
96
|
+
return [meanNode(this.data, jde), 0.0, null];
|
|
97
|
+
if (body === "true_node") {
|
|
98
|
+
return [
|
|
99
|
+
this.moonInRange(jde)
|
|
100
|
+
? trueNodePrecise(this.data, this.moonCheb, jde)
|
|
101
|
+
: trueNodeSeries(this.data, jde),
|
|
102
|
+
0.0, null,
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
if (body === "mean_lilith") {
|
|
106
|
+
const [lon, lat] = meanLilith(this.data, jde);
|
|
107
|
+
return [lon, lat, null];
|
|
42
108
|
}
|
|
43
|
-
|
|
44
|
-
[lon] =
|
|
109
|
+
if (body === "true_lilith") {
|
|
110
|
+
const [lon, lat, km] = this.moonInRange(jde)
|
|
111
|
+
? oscApogeePrecise(this.data, this.moonCheb, jde)
|
|
112
|
+
: oscApogeeSeries(this.data, jde);
|
|
113
|
+
return [lon, lat, km / KM_PER_AU];
|
|
114
|
+
}
|
|
115
|
+
if (this.data.chebPacks?.[body] || this.data.keplerPack?.bodies[body]) {
|
|
116
|
+
// same heliocentric pipeline as Chiron (Chebyshev or Kepler source)
|
|
117
|
+
return chironApparent(this.data, this.pack(body), jde);
|
|
118
|
+
}
|
|
119
|
+
if (this.data.vsop[body])
|
|
120
|
+
return planetApparent(this.data, body, jde);
|
|
121
|
+
throw new Error(`no data loaded for body '${body}'`);
|
|
122
|
+
}
|
|
123
|
+
lonOnly(body, jdUt, mode, topo) {
|
|
124
|
+
const jde = jdTT(jdUt);
|
|
125
|
+
let [lon, lat, dist] = this.ecliptic(body, jde);
|
|
126
|
+
if (topo !== null && dist !== null) {
|
|
127
|
+
const lst = mod(H.gast(this.data, jdUt) + topo.lonEast * DEG, TWO_PI);
|
|
128
|
+
[lon, lat, dist] = topocentricEcl(lon, lat, dist, lst, topo.lat * DEG, topo.altM ?? 0.0, trueObliquity(this.data, jde));
|
|
129
|
+
}
|
|
130
|
+
let lonDeg = lon / DEG;
|
|
131
|
+
if (mode !== null) {
|
|
132
|
+
lonDeg = mod(lonDeg - nutation(this.data, jde)[0] / DEG - ayanamsa(jde, mode), 360);
|
|
133
|
+
}
|
|
134
|
+
return lonDeg;
|
|
135
|
+
}
|
|
136
|
+
/** Apparent geocentric ecliptic longitude (deg). Tropical: true equinox
|
|
137
|
+
* of date. Sidereal: mean equinox minus ayanamsa. */
|
|
138
|
+
longitude(body, jdUt, opts = {}) {
|
|
139
|
+
const mode = parseZodiac(opts.zodiac ?? "tropical");
|
|
140
|
+
const topo = opts.topocentric ? opts.observer ?? null : null;
|
|
141
|
+
return this.lonOnly(body, jdUt, mode, topo);
|
|
142
|
+
}
|
|
143
|
+
/** Geometric heliocentric ecliptic of date (deg, deg, AU). */
|
|
144
|
+
heliocentric(body, jdUt) {
|
|
145
|
+
const jde = jdTT(jdUt);
|
|
146
|
+
let l;
|
|
147
|
+
let b;
|
|
148
|
+
let r;
|
|
149
|
+
if (body === "pluto") {
|
|
150
|
+
[l, b, r] = plutoHeliocentric(this.data, jde);
|
|
151
|
+
[l, b] = precessEcliptic(l, b, J2000, jde);
|
|
45
152
|
}
|
|
46
153
|
else if (body === "chiron") {
|
|
47
154
|
if (!this.chironCheb)
|
|
48
155
|
throw new Error("chiron data not loaded");
|
|
49
|
-
[
|
|
156
|
+
const [x, y, z] = this.chironCheb.xyz(jde);
|
|
157
|
+
r = Math.sqrt(x * x + y * y + z * z);
|
|
158
|
+
l = mod(Math.atan2(y, x), TWO_PI);
|
|
159
|
+
b = Math.atan2(z, Math.hypot(x, y));
|
|
160
|
+
[l, b] = precessEcliptic(l, b, J2000, jde);
|
|
50
161
|
}
|
|
51
|
-
else if (body
|
|
52
|
-
|
|
162
|
+
else if (this.data.chebPacks?.[body] || this.data.keplerPack?.bodies[body]) {
|
|
163
|
+
const [x, y, z] = this.pack(body).xyz(jde);
|
|
164
|
+
r = Math.sqrt(x * x + y * y + z * z);
|
|
165
|
+
l = mod(Math.atan2(y, x), TWO_PI);
|
|
166
|
+
b = Math.atan2(z, Math.hypot(x, y));
|
|
167
|
+
[l, b] = precessEcliptic(l, b, J2000, jde);
|
|
53
168
|
}
|
|
54
|
-
else if (body
|
|
55
|
-
|
|
56
|
-
? trueNodePrecise(this.data, this.moonCheb, jde)
|
|
57
|
-
: trueNodeSeries(this.data, jde);
|
|
169
|
+
else if (VSOP_BODIES.has(body) && this.data.vsop[body]) {
|
|
170
|
+
[l, b, r] = vsopHeliocentric(this.data.vsop[body], jde);
|
|
58
171
|
}
|
|
59
172
|
else {
|
|
60
|
-
|
|
173
|
+
throw new Error(`no heliocentric position for '${body}'`);
|
|
61
174
|
}
|
|
62
|
-
return lon / DEG;
|
|
175
|
+
return { lon: l / DEG, lat: b / DEG, dist: r };
|
|
63
176
|
}
|
|
64
|
-
/**
|
|
65
|
-
position(body, jdUt) {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
177
|
+
/** Full position: lon/speed/retrograde/sign + lat, dist (AU), ra, dec. */
|
|
178
|
+
position(body, jdUt, opts = {}) {
|
|
179
|
+
const mode = parseZodiac(opts.zodiac ?? "tropical");
|
|
180
|
+
const topo = opts.topocentric ? opts.observer ?? null : null;
|
|
181
|
+
const jde = jdTT(jdUt);
|
|
182
|
+
let [lonR, latR, dist] = this.ecliptic(body, jde);
|
|
183
|
+
if (topo !== null && dist !== null) {
|
|
184
|
+
const lst = mod(H.gast(this.data, jdUt) + topo.lonEast * DEG, TWO_PI);
|
|
185
|
+
[lonR, latR, dist] = topocentricEcl(lonR, latR, dist, lst, topo.lat * DEG, topo.altM ?? 0.0, trueObliquity(this.data, jde));
|
|
186
|
+
}
|
|
187
|
+
const [ra, dec] = equatorial(lonR, latR, trueObliquity(this.data, jde));
|
|
188
|
+
let lon = lonR / DEG;
|
|
189
|
+
if (mode !== null) {
|
|
190
|
+
lon = mod(lon - nutation(this.data, jde)[0] / DEG - ayanamsa(jde, mode), 360);
|
|
191
|
+
}
|
|
192
|
+
const h = 0.25; // days; central difference
|
|
193
|
+
const l0 = this.lonOnly(body, jdUt - h, mode, topo);
|
|
194
|
+
const l1 = this.lonOnly(body, jdUt + h, mode, topo);
|
|
70
195
|
const speed = (mod(l1 - l0 + 540, 360) - 180) / (2 * h);
|
|
71
196
|
return {
|
|
72
197
|
lon, speed, retrograde: speed < 0,
|
|
73
198
|
sign: SIGNS[Math.floor(lon / 30)], signDeg: mod(lon, 30),
|
|
199
|
+
lat: latR / DEG, dist,
|
|
200
|
+
ra: ra / DEG, dec: dec / DEG,
|
|
74
201
|
};
|
|
75
202
|
}
|
|
76
|
-
/** Full natal chart. Time is UT. East longitude positive.
|
|
77
|
-
|
|
203
|
+
/** Full natal chart. Time is UT. East longitude positive. The ninth
|
|
204
|
+
* argument takes a house system name (0.2.x form) or a ChartOptions bag. */
|
|
205
|
+
chart(y, mo, d, h, mi, s, lat, lonEast, opts = "placidus") {
|
|
206
|
+
const o = typeof opts === "string" ? { houseSystem: opts } : opts;
|
|
207
|
+
const houseSystem = o.houseSystem ?? "placidus";
|
|
208
|
+
const zodiac = o.zodiac ?? "tropical";
|
|
209
|
+
const mode = parseZodiac(zodiac);
|
|
78
210
|
const jdUt = julianDay(y, mo, d, h, mi, s);
|
|
211
|
+
const calc = {
|
|
212
|
+
zodiac,
|
|
213
|
+
topocentric: o.topocentric,
|
|
214
|
+
observer: o.topocentric ? o.observer ?? { lat, lonEast, altM: 0.0 } : undefined,
|
|
215
|
+
};
|
|
216
|
+
const names = [
|
|
217
|
+
...BODIES, ...(o.bodies ?? []).filter((b) => !BODIES.includes(b)),
|
|
218
|
+
];
|
|
79
219
|
const bodies = {};
|
|
80
|
-
for (const b of
|
|
81
|
-
bodies[b] = this.position(b, jdUt);
|
|
220
|
+
for (const b of names)
|
|
221
|
+
bodies[b] = this.position(b, jdUt, calc);
|
|
82
222
|
const [asc, mc, armc, eps] = H.angles(this.data, jdUt, lat, lonEast);
|
|
223
|
+
const [vtx, east] = H.vertexEastPoint(armc, lat * DEG, eps);
|
|
83
224
|
const phi = lat * DEG;
|
|
84
|
-
let cusps;
|
|
85
225
|
let used = houseSystem;
|
|
86
|
-
|
|
87
|
-
|
|
226
|
+
let cusps;
|
|
227
|
+
try {
|
|
228
|
+
if (houseSystem === "placidus") {
|
|
229
|
+
if (Math.abs(lat) >= 66.0) {
|
|
230
|
+
throw new RangeError("placidus undefined above polar circles");
|
|
231
|
+
}
|
|
88
232
|
cusps = H.housesPlacidus(armc, phi, eps);
|
|
89
233
|
}
|
|
90
|
-
else {
|
|
91
|
-
|
|
234
|
+
else if (houseSystem === "porphyry") {
|
|
235
|
+
cusps = H.housesPorphyry(asc, mc);
|
|
236
|
+
}
|
|
237
|
+
else if (houseSystem === "equal") {
|
|
238
|
+
cusps = H.housesEqual(asc);
|
|
239
|
+
}
|
|
240
|
+
else if (houseSystem === "whole_sign") {
|
|
92
241
|
cusps = H.housesWholeSign(asc);
|
|
93
242
|
}
|
|
243
|
+
else if (houseSystem === "koch") {
|
|
244
|
+
cusps = H.housesKoch(armc, phi, eps);
|
|
245
|
+
}
|
|
246
|
+
else if (houseSystem === "regiomontanus") {
|
|
247
|
+
cusps = H.housesRegiomontanus(armc, phi, eps);
|
|
248
|
+
}
|
|
249
|
+
else if (houseSystem === "campanus") {
|
|
250
|
+
cusps = H.housesCampanus(armc, phi, eps);
|
|
251
|
+
}
|
|
252
|
+
else if (houseSystem === "alcabitius") {
|
|
253
|
+
cusps = H.housesAlcabitius(armc, phi, eps);
|
|
254
|
+
}
|
|
255
|
+
else if (houseSystem === "morinus") {
|
|
256
|
+
cusps = H.housesMorinus(armc, phi, eps);
|
|
257
|
+
}
|
|
258
|
+
else if (houseSystem === "meridian") {
|
|
259
|
+
cusps = H.housesMeridian(armc, phi, eps);
|
|
260
|
+
}
|
|
261
|
+
else if (houseSystem === "polich_page") {
|
|
262
|
+
cusps = H.housesPolichPage(armc, phi, eps);
|
|
263
|
+
}
|
|
264
|
+
else if (houseSystem === "vehlow") {
|
|
265
|
+
cusps = H.housesVehlow(armc, phi, eps);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
throw new Error(`unknown house system '${houseSystem}'`);
|
|
269
|
+
}
|
|
94
270
|
}
|
|
95
|
-
|
|
96
|
-
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (!(err instanceof RangeError))
|
|
273
|
+
throw err;
|
|
274
|
+
used = "whole_sign"; // Placidus/Koch undefined above polar circles
|
|
275
|
+
cusps = H.housesWholeSign(asc);
|
|
97
276
|
}
|
|
98
|
-
|
|
99
|
-
|
|
277
|
+
const jde = jdTT(jdUt);
|
|
278
|
+
let shift = 0.0;
|
|
279
|
+
if (mode !== null) {
|
|
280
|
+
shift = nutation(this.data, jde)[0] / DEG + ayanamsa(jde, mode);
|
|
281
|
+
}
|
|
282
|
+
const outDeg = (rad) => mod(rad / DEG - shift, 360);
|
|
283
|
+
let cuspsDeg;
|
|
284
|
+
if (mode !== null && used === "whole_sign") {
|
|
285
|
+
// whole-sign cusps must stay sign-aligned in the sidereal zodiac
|
|
286
|
+
const first = Math.floor(outDeg(asc) / 30) * 30.0;
|
|
287
|
+
cuspsDeg = Array.from({ length: 12 }, (_, i) => mod(first + i * 30.0, 360));
|
|
100
288
|
}
|
|
101
289
|
else {
|
|
102
|
-
|
|
290
|
+
cuspsDeg = cusps.map(outDeg);
|
|
103
291
|
}
|
|
104
292
|
return {
|
|
105
293
|
jdUt,
|
|
294
|
+
zodiac,
|
|
106
295
|
houseSystem: used,
|
|
107
296
|
houseSystemRequested: houseSystem,
|
|
108
297
|
bodies,
|
|
109
|
-
angles: {
|
|
110
|
-
|
|
111
|
-
|
|
298
|
+
angles: {
|
|
299
|
+
asc: outDeg(asc), mc: outDeg(mc),
|
|
300
|
+
vertex: outDeg(vtx), eastPoint: outDeg(east),
|
|
301
|
+
},
|
|
302
|
+
cusps: cuspsDeg,
|
|
303
|
+
aspects: findAspects(bodies, o.orbs ?? DEFAULT_ORBS),
|
|
112
304
|
};
|
|
113
305
|
}
|
|
114
306
|
}
|
|
115
307
|
export function findAspects(bodies, orbs = DEFAULT_ORBS) {
|
|
116
308
|
const out = [];
|
|
117
|
-
const names = Object.keys(bodies).filter((b) => !
|
|
309
|
+
const names = Object.keys(bodies).filter((b) => !NOT_ASPECTABLE.has(b));
|
|
118
310
|
for (let i = 0; i < names.length; i++) {
|
|
119
311
|
for (let j = i + 1; j < names.length; j++) {
|
|
120
312
|
const a = names[i];
|
|
@@ -134,5 +326,5 @@ export function fmtLon(deg) {
|
|
|
134
326
|
const sign = SIGNS[Math.floor(deg / 30)];
|
|
135
327
|
const d = mod(deg, 30);
|
|
136
328
|
const m = mod(d, 1) * 60;
|
|
137
|
-
return `${String(Math.floor(d)).padStart(2)}
|
|
329
|
+
return `${String(Math.floor(d)).padStart(2)}°${String(Math.floor(m)).padStart(2, "0")}' ${sign}`;
|
|
138
330
|
}
|
package/dist/src/core.d.ts
CHANGED
|
@@ -26,6 +26,24 @@ export type ChebData = {
|
|
|
26
26
|
scale?: number;
|
|
27
27
|
segments: number[][][];
|
|
28
28
|
};
|
|
29
|
+
export type KeplerElements = {
|
|
30
|
+
a: number;
|
|
31
|
+
e: number;
|
|
32
|
+
i: number;
|
|
33
|
+
node: number;
|
|
34
|
+
peri: number;
|
|
35
|
+
M0: number;
|
|
36
|
+
n: number;
|
|
37
|
+
};
|
|
38
|
+
export type KeplerPack = {
|
|
39
|
+
epoch: number;
|
|
40
|
+
bodies: Record<string, KeplerElements>;
|
|
41
|
+
};
|
|
42
|
+
/** Anything that yields heliocentric ecliptic-J2000 xyz (AU) at a TT jd:
|
|
43
|
+
* ChebSeries (fitted small bodies) or KeplerOrbit (Uranian bodies). */
|
|
44
|
+
export interface XyzSource {
|
|
45
|
+
xyz(jd: number): [number, number, number];
|
|
46
|
+
}
|
|
29
47
|
export interface EngineData {
|
|
30
48
|
vsop: Record<string, VsopSeries>;
|
|
31
49
|
nutation: number[][];
|
|
@@ -33,6 +51,11 @@ export interface EngineData {
|
|
|
33
51
|
pluto: number[][];
|
|
34
52
|
chiron?: ChebData;
|
|
35
53
|
moonCheb?: ChebData;
|
|
54
|
+
/** Heliocentric ecliptic-J2000 Chebyshev packs by body id (ceres,
|
|
55
|
+
* pallas, juno, vesta, pholus, ...). Same pipeline as Chiron. */
|
|
56
|
+
chebPacks?: Record<string, ChebData>;
|
|
57
|
+
/** Hamburg-school (Uranian) constant-element orbits; see fit_uranian.py. */
|
|
58
|
+
keplerPack?: KeplerPack;
|
|
36
59
|
}
|
|
37
60
|
export declare function julianDay(y: number, mo: number, d: number, h?: number, mi?: number, s?: number): number;
|
|
38
61
|
/** TT - UT1 in seconds. Observed IERS 1955-2025, E&M polynomials before,
|
|
@@ -68,5 +91,38 @@ export declare function trueNodePrecise(data: EngineData, cheb: ChebSeries, jde:
|
|
|
68
91
|
export declare function meanNode(data: EngineData, jde: number): number;
|
|
69
92
|
/** Osculating node from the series moon (fallback outside Chebyshev range). */
|
|
70
93
|
export declare function trueNodeSeries(data: EngineData, jde: number): number;
|
|
94
|
+
/** Ecliptic lon/lat -> right ascension, declination (all radians). */
|
|
95
|
+
export declare function equatorial(lon: number, lat: number, eps: number): [number, number];
|
|
96
|
+
/** Mean ayanamsa at J2000.0 (degrees) per mode. Standard epoch anchors
|
|
97
|
+
* (matched to Swiss Ephemeris 2.10 to 1e-9 deg); propagation uses IAU 1976
|
|
98
|
+
* ecliptic precession. Agreement with Swiss Ephemeris over 1900-2099 is
|
|
99
|
+
* <=0.30 arcsec (precession-model difference: SE uses Vondrak 2011). */
|
|
100
|
+
export declare const AYANAMSA_J2000: Record<string, number>;
|
|
101
|
+
/** Mean ayanamsa in degrees. Sidereal longitude = (tropical true-equinox
|
|
102
|
+
* longitude - nutation in longitude) - ayanamsa: the sidereal zodiac is
|
|
103
|
+
* anchored to the mean equinox. */
|
|
104
|
+
export declare function ayanamsa(jde: number, mode: string): number;
|
|
105
|
+
/** Mean lunar apogee (Black Moon Lilith) on the inclined lunar orbit:
|
|
106
|
+
* apparent lon (true equinox) and orbital latitude, radians. */
|
|
107
|
+
export declare function meanLilith(data: EngineData, jde: number): [number, number];
|
|
108
|
+
/** Osculating lunar apogee (True Lilith) from the Chebyshev moon. */
|
|
109
|
+
export declare function oscApogeePrecise(data: EngineData, cheb: ChebSeries, jde: number): [number, number, number];
|
|
110
|
+
/** Series fallback outside the Chebyshev range (same finite-difference
|
|
111
|
+
* state as the true-node fallback). */
|
|
112
|
+
export declare function oscApogeeSeries(data: EngineData, jde: number): [number, number, number];
|
|
113
|
+
/** Constant-element two-body orbit with the same xyz(jde) interface as
|
|
114
|
+
* ChebSeries, so chironApparent takes either. */
|
|
115
|
+
export declare class KeplerOrbit implements XyzSource {
|
|
116
|
+
private els;
|
|
117
|
+
private epoch;
|
|
118
|
+
constructor(els: KeplerElements, epoch: number);
|
|
119
|
+
xyz(jde: number): [number, number, number];
|
|
120
|
+
}
|
|
121
|
+
export declare const EARTH_RADIUS_AU: number;
|
|
122
|
+
/** Diurnal parallax in ecliptic coordinates (Meeus ch. 11/40).
|
|
123
|
+
* lst = local apparent sidereal time (rad). Returns [lon, lat, distAu]. */
|
|
124
|
+
export declare function topocentricEcl(lon: number, lat: number, distAu: number, lst: number, obsLat: number, altM: number, eps: number): [number, number, number];
|
|
125
|
+
/** Meeus ch.37 heliocentric Pluto, ecliptic J2000: [l rad, b rad, r AU]. */
|
|
126
|
+
export declare function plutoHeliocentric(data: EngineData, jde: number): [number, number, number];
|
|
71
127
|
export declare function plutoApparent(data: EngineData, jde: number): [number, number, number];
|
|
72
|
-
export declare function chironApparent(data: EngineData, cheb:
|
|
128
|
+
export declare function chironApparent(data: EngineData, cheb: XyzSource, jde: number): [number, number, number];
|
package/dist/src/core.js
CHANGED
|
@@ -377,30 +377,177 @@ export function trueNodeSeries(data, jde) {
|
|
|
377
377
|
const node = mod(Math.atan2(hx, -hy), TWO_PI);
|
|
378
378
|
return mod(node + nutation(data, jde)[0], TWO_PI);
|
|
379
379
|
}
|
|
380
|
-
// ----------------------------------------------------------------
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
380
|
+
// ---------------------------------------------------------------- frames+
|
|
381
|
+
/** Ecliptic lon/lat -> right ascension, declination (all radians). */
|
|
382
|
+
export function equatorial(lon, lat, eps) {
|
|
383
|
+
const ra = mod(Math.atan2(Math.sin(lon) * Math.cos(eps) - Math.tan(lat) * Math.sin(eps), Math.cos(lon)), TWO_PI);
|
|
384
|
+
const dec = Math.asin(Math.sin(lat) * Math.cos(eps) + Math.cos(lat) * Math.sin(eps) * Math.sin(lon));
|
|
385
|
+
return [ra, dec];
|
|
386
|
+
}
|
|
387
|
+
/** Mean ayanamsa at J2000.0 (degrees) per mode. Standard epoch anchors
|
|
388
|
+
* (matched to Swiss Ephemeris 2.10 to 1e-9 deg); propagation uses IAU 1976
|
|
389
|
+
* ecliptic precession. Agreement with Swiss Ephemeris over 1900-2099 is
|
|
390
|
+
* <=0.30 arcsec (precession-model difference: SE uses Vondrak 2011). */
|
|
391
|
+
export const AYANAMSA_J2000 = {
|
|
392
|
+
lahiri: 23.857092325,
|
|
393
|
+
fagan_bradley: 24.740299966,
|
|
394
|
+
krishnamurti: 23.760240012,
|
|
395
|
+
raman: 22.410791012,
|
|
396
|
+
yukteshwar: 22.478803000,
|
|
397
|
+
};
|
|
398
|
+
/** Mean ayanamsa in degrees. Sidereal longitude = (tropical true-equinox
|
|
399
|
+
* longitude - nutation in longitude) - ayanamsa: the sidereal zodiac is
|
|
400
|
+
* anchored to the mean equinox. */
|
|
401
|
+
export function ayanamsa(jde, mode) {
|
|
402
|
+
const a0 = AYANAMSA_J2000[mode];
|
|
403
|
+
if (a0 === undefined)
|
|
404
|
+
throw new Error(`unknown ayanamsa ${mode}`);
|
|
405
|
+
const [lon] = precessEcliptic(a0 * DEG, 0.0, J2000, jde);
|
|
406
|
+
return lon / DEG;
|
|
407
|
+
}
|
|
408
|
+
/** Mean lunar apogee (Black Moon Lilith) on the inclined lunar orbit:
|
|
409
|
+
* apparent lon (true equinox) and orbital latitude, radians. */
|
|
410
|
+
export function meanLilith(data, jde) {
|
|
411
|
+
const T = (jde - J2000) / 36525.0;
|
|
412
|
+
const [Lp, , , Mp] = moonFundamental(T);
|
|
413
|
+
const apog = Lp - Mp + Math.PI; // mean perigee + 180
|
|
414
|
+
const om = (125.0445479 - 1934.1362891 * T + 0.0020754 * T * T
|
|
415
|
+
+ T ** 3 / 467441 - T ** 4 / 60616000) * DEG;
|
|
416
|
+
const inc = 5.145396374 * DEG;
|
|
417
|
+
const u = apog - om;
|
|
418
|
+
const lat = Math.asin(Math.sin(inc) * Math.sin(u));
|
|
419
|
+
let lon = om + Math.atan2(Math.cos(inc) * Math.sin(u), Math.cos(u));
|
|
420
|
+
lon = mod(lon + nutation(data, jde)[0], TWO_PI);
|
|
421
|
+
return [lon, lat];
|
|
422
|
+
}
|
|
423
|
+
const GM_EARTH_MOON = 403503.2356 * 86400.0 ** 2; // km^3/day^2
|
|
424
|
+
/** Osculating apogee point from a geocentric lunar state vector (km,
|
|
425
|
+
* km/day): apparent ecliptic lon/lat of date (rad) + distance (km).
|
|
426
|
+
* Hypersensitive to the lunar theory: the eccentricity vector amplifies
|
|
427
|
+
* position/velocity differences ~1/e (~18x). Swiss Ephemeris in Moshier
|
|
428
|
+
* mode differs from our DE423 fit by up to ~3 arcmin here; published
|
|
429
|
+
* 'True Lilith' values disagree across software at that scale. */
|
|
430
|
+
function oscApogeeFromState(data, x, y, z, vx, vy, vz, jde, frameJ2000) {
|
|
431
|
+
const mu = GM_EARTH_MOON;
|
|
432
|
+
const r = Math.sqrt(x * x + y * y + z * z);
|
|
433
|
+
const v2 = vx * vx + vy * vy + vz * vz;
|
|
434
|
+
const rv = x * vx + y * vy + z * vz;
|
|
435
|
+
const ex = (v2 * x - rv * vx) / mu - x / r;
|
|
436
|
+
const ey = (v2 * y - rv * vy) / mu - y / r;
|
|
437
|
+
const ez = (v2 * z - rv * vz) / mu - z / r;
|
|
438
|
+
const e = Math.sqrt(ex * ex + ey * ey + ez * ez);
|
|
439
|
+
const a = 1.0 / (2.0 / r - v2 / mu);
|
|
440
|
+
const s = (a * (1 + e)) / e;
|
|
441
|
+
let px = -ex * s;
|
|
442
|
+
let py = -ey * s;
|
|
443
|
+
let pz = -ez * s;
|
|
444
|
+
if (frameJ2000)
|
|
445
|
+
[px, py, pz] = eclJ2000ToEclDate([px, py, pz], jde);
|
|
446
|
+
const lon = mod(Math.atan2(py, px) + nutation(data, jde)[0], TWO_PI);
|
|
447
|
+
const lat = Math.atan2(pz, Math.hypot(px, py));
|
|
448
|
+
return [lon, lat, Math.sqrt(px * px + py * py + pz * pz)];
|
|
449
|
+
}
|
|
450
|
+
/** Osculating lunar apogee (True Lilith) from the Chebyshev moon. */
|
|
451
|
+
export function oscApogeePrecise(data, cheb, jde) {
|
|
452
|
+
const [[x, y, z], [vx, vy, vz]] = cheb.xyzVel(jde);
|
|
453
|
+
return oscApogeeFromState(data, x, y, z, vx, vy, vz, jde, true);
|
|
454
|
+
}
|
|
455
|
+
/** Series fallback outside the Chebyshev range (same finite-difference
|
|
456
|
+
* state as the true-node fallback). */
|
|
457
|
+
export function oscApogeeSeries(data, jde) {
|
|
458
|
+
const h = 0.01;
|
|
459
|
+
const xyz = (t) => {
|
|
460
|
+
const [lon, lat, dist] = moonGeometric(data, t);
|
|
398
461
|
return [
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
462
|
+
dist * Math.cos(lat) * Math.cos(lon),
|
|
463
|
+
dist * Math.cos(lat) * Math.sin(lon),
|
|
464
|
+
dist * Math.sin(lat),
|
|
402
465
|
];
|
|
403
466
|
};
|
|
467
|
+
const [x0, y0, z0] = xyz(jde - h);
|
|
468
|
+
const [x1, y1, z1] = xyz(jde + h);
|
|
469
|
+
const [x, y, z] = xyz(jde);
|
|
470
|
+
return oscApogeeFromState(data, x, y, z, (x1 - x0) / (2 * h), (y1 - y0) / (2 * h), (z1 - z0) / (2 * h), jde, false);
|
|
471
|
+
}
|
|
472
|
+
/** Constant-element two-body orbit with the same xyz(jde) interface as
|
|
473
|
+
* ChebSeries, so chironApparent takes either. */
|
|
474
|
+
export class KeplerOrbit {
|
|
475
|
+
els;
|
|
476
|
+
epoch;
|
|
477
|
+
constructor(els, epoch) {
|
|
478
|
+
this.els = els;
|
|
479
|
+
this.epoch = epoch;
|
|
480
|
+
}
|
|
481
|
+
xyz(jde) {
|
|
482
|
+
const { a, e, i, node, peri: w, M0, n } = this.els;
|
|
483
|
+
const M = M0 + n * (jde - this.epoch);
|
|
484
|
+
let E = M;
|
|
485
|
+
for (let k = 0; k < 30; k++) {
|
|
486
|
+
E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
|
|
487
|
+
}
|
|
488
|
+
const xv = a * (Math.cos(E) - e);
|
|
489
|
+
const yv = a * Math.sqrt(1 - e * e) * Math.sin(E);
|
|
490
|
+
const cw = Math.cos(w);
|
|
491
|
+
const sw = Math.sin(w);
|
|
492
|
+
const cn = Math.cos(node);
|
|
493
|
+
const sn = Math.sin(node);
|
|
494
|
+
const ci = Math.cos(i);
|
|
495
|
+
const si = Math.sin(i);
|
|
496
|
+
const xp = xv * cw - yv * sw;
|
|
497
|
+
const yp = xv * sw + yv * cw;
|
|
498
|
+
return [xp * cn - yp * sn * ci, xp * sn + yp * cn * ci, yp * si];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
export const EARTH_RADIUS_AU = 6378.14 / 149597870.7;
|
|
502
|
+
const EARTH_FLAT = 0.99664719; // 1 - f, IAU 1976 figure
|
|
503
|
+
/** Diurnal parallax in ecliptic coordinates (Meeus ch. 11/40).
|
|
504
|
+
* lst = local apparent sidereal time (rad). Returns [lon, lat, distAu]. */
|
|
505
|
+
export function topocentricEcl(lon, lat, distAu, lst, obsLat, altM, eps) {
|
|
506
|
+
const u = Math.atan(EARTH_FLAT * Math.tan(obsLat));
|
|
507
|
+
const rs = EARTH_FLAT * Math.sin(u) + (altM / 6378140.0) * Math.sin(obsLat);
|
|
508
|
+
const rc = Math.cos(u) + (altM / 6378140.0) * Math.cos(obsLat);
|
|
509
|
+
const ox = EARTH_RADIUS_AU * rc * Math.cos(lst);
|
|
510
|
+
const oy = EARTH_RADIUS_AU * rc * Math.sin(lst);
|
|
511
|
+
const oz = EARTH_RADIUS_AU * rs;
|
|
512
|
+
const [ra, dec] = equatorial(lon, lat, eps);
|
|
513
|
+
const bx = distAu * Math.cos(dec) * Math.cos(ra);
|
|
514
|
+
const by = distAu * Math.cos(dec) * Math.sin(ra);
|
|
515
|
+
const bz = distAu * Math.sin(dec);
|
|
516
|
+
const tx = bx - ox;
|
|
517
|
+
const ty = by - oy;
|
|
518
|
+
const tz = bz - oz;
|
|
519
|
+
const ra2 = Math.atan2(ty, tx);
|
|
520
|
+
const dec2 = Math.atan2(tz, Math.hypot(tx, ty));
|
|
521
|
+
const lon2 = mod(Math.atan2(Math.sin(ra2) * Math.cos(eps) + Math.tan(dec2) * Math.sin(eps), Math.cos(ra2)), TWO_PI);
|
|
522
|
+
const lat2 = Math.asin(Math.sin(dec2) * Math.cos(eps) - Math.cos(dec2) * Math.sin(eps) * Math.sin(ra2));
|
|
523
|
+
return [lon2, lat2, Math.sqrt(tx * tx + ty * ty + tz * tz)];
|
|
524
|
+
}
|
|
525
|
+
// ---------------------------------------------------------------- pluto
|
|
526
|
+
/** Meeus ch.37 heliocentric Pluto, ecliptic J2000: [l rad, b rad, r AU]. */
|
|
527
|
+
export function plutoHeliocentric(data, jde) {
|
|
528
|
+
const T = (jde - J2000) / 36525.0;
|
|
529
|
+
const J = (34.35 + 3034.9057 * T) * DEG;
|
|
530
|
+
const S = (50.08 + 1222.1138 * T) * DEG;
|
|
531
|
+
const P = (238.96 + 144.96 * T) * DEG;
|
|
532
|
+
let l = 0.0;
|
|
533
|
+
let b = 0.0;
|
|
534
|
+
let r = 0.0;
|
|
535
|
+
for (const [i, j, k, lA, lB, bA, bB, rA, rB] of data.pluto) {
|
|
536
|
+
const a = i * J + j * S + k * P;
|
|
537
|
+
const sa = Math.sin(a);
|
|
538
|
+
const ca = Math.cos(a);
|
|
539
|
+
l += lA * sa + lB * ca;
|
|
540
|
+
b += bA * sa + bB * ca;
|
|
541
|
+
r += rA * sa + rB * ca;
|
|
542
|
+
}
|
|
543
|
+
return [
|
|
544
|
+
(l + 238.958116 + 144.96 * T) * DEG,
|
|
545
|
+
(b - 3.908239) * DEG,
|
|
546
|
+
r + 40.7241346,
|
|
547
|
+
];
|
|
548
|
+
}
|
|
549
|
+
export function plutoApparent(data, jde) {
|
|
550
|
+
const helioJ2000 = (tJde) => plutoHeliocentric(data, tJde);
|
|
404
551
|
const [L0d, B0d, R0d] = vsopHeliocentric(data.vsop.earth, jde);
|
|
405
552
|
const [Lj, Bj] = precessEcliptic(L0d, B0d, jde, J2000);
|
|
406
553
|
const ex = R0d * Math.cos(Bj) * Math.cos(Lj);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
2
|
+
export type RiseKind = "rise" | "set" | "mtransit" | "itransit";
|
|
3
|
+
export interface RiseSetOptions {
|
|
4
|
+
altM?: number;
|
|
5
|
+
pressure?: number;
|
|
6
|
+
tempC?: number;
|
|
7
|
+
searchDays?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Next rise/set/meridian transit (UT JD) after jdStart, or null when the
|
|
10
|
+
* event does not occur in the window (polar day/night). */
|
|
11
|
+
export declare function riseSet(engine: Engine, body: BodyId, jdStart: number, latDeg: number, lonDeg: number, kind?: RiseKind, opts?: RiseSetOptions): number | null;
|
|
12
|
+
/** UT JDs where the body's apparent longitude crosses targetLon (degrees)
|
|
13
|
+
* in [jdStart, jdEnd]. Retrograde bodies can cross a degree three times;
|
|
14
|
+
* every crossing is returned in time order. */
|
|
15
|
+
export declare function crossings(engine: Engine, body: BodyId, targetLon: number, jdStart: number, jdEnd: number, zodiac?: Zodiac, maxHits?: number): number[];
|
|
16
|
+
export type PhaseName = "new" | "first_quarter" | "full" | "last_quarter";
|
|
17
|
+
/** New/first-quarter/full/last-quarter times in [jdStart, jdEnd], sorted. */
|
|
18
|
+
export declare function lunarPhases(engine: Engine, jdStart: number, jdEnd: number, maxHits?: number): Array<[number, PhaseName]>;
|
|
19
|
+
/** Times the body stations (speed crosses zero): [jdUt, direction the body
|
|
20
|
+
* turns]. Sun and Moon never station. Station timing is ill-conditioned:
|
|
21
|
+
* expect minute-level differences between ephemerides. */
|
|
22
|
+
export declare function stations(engine: Engine, body: BodyId, jdStart: number, jdEnd: number, maxHits?: number): Array<[number, "retrograde" | "direct"]>;
|