caelus 0.1.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.
Files changed (40) hide show
  1. package/README.md +84 -0
  2. package/accuracy.json +32 -0
  3. package/data/chiron_cheb.json +1 -0
  4. package/data/moon_cheb.embedded.json +1 -0
  5. package/data/moon_meeus47.json +1 -0
  6. package/data/nutation_iau1980.json +1 -0
  7. package/data/pluto_meeus37.json +1 -0
  8. package/data/vsop87d_earth.embedded.json +1 -0
  9. package/data/vsop87d_jupiter.embedded.json +1 -0
  10. package/data/vsop87d_mars.embedded.json +1 -0
  11. package/data/vsop87d_mercury.embedded.json +1 -0
  12. package/data/vsop87d_neptune.embedded.json +1 -0
  13. package/data/vsop87d_saturn.embedded.json +1 -0
  14. package/data/vsop87d_uranus.embedded.json +1 -0
  15. package/data/vsop87d_venus.embedded.json +1 -0
  16. package/dist/data/chiron_cheb.json +1 -0
  17. package/dist/data/moon_meeus47.json +1 -0
  18. package/dist/data/nutation_iau1980.json +1 -0
  19. package/dist/data/pluto_meeus37.json +1 -0
  20. package/dist/data/vsop87d_earth.embedded.json +1 -0
  21. package/dist/data/vsop87d_jupiter.embedded.json +1 -0
  22. package/dist/data/vsop87d_mars.embedded.json +1 -0
  23. package/dist/data/vsop87d_mercury.embedded.json +1 -0
  24. package/dist/data/vsop87d_neptune.embedded.json +1 -0
  25. package/dist/data/vsop87d_saturn.embedded.json +1 -0
  26. package/dist/data/vsop87d_uranus.embedded.json +1 -0
  27. package/dist/data/vsop87d_venus.embedded.json +1 -0
  28. package/dist/src/chart.d.ts +50 -0
  29. package/dist/src/chart.js +138 -0
  30. package/dist/src/core.d.ts +72 -0
  31. package/dist/src/core.js +463 -0
  32. package/dist/src/data-embedded.d.ts +2 -0
  33. package/dist/src/data-embedded.js +20 -0
  34. package/dist/src/houses.d.ts +18 -0
  35. package/dist/src/houses.js +96 -0
  36. package/dist/src/index.d.ts +3 -0
  37. package/dist/src/index.js +3 -0
  38. package/dist/src/node-loader.d.ts +4 -0
  39. package/dist/src/node-loader.js +35 -0
  40. package/package.json +60 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * astroengine core -- clean-room ephemeris engine (TypeScript port).
3
+ *
4
+ * 1:1 port of the validated Python implementation. Environment-agnostic:
5
+ * all coefficient data is injected (works in browser via fetch/bundling,
6
+ * in Node via the loader in node-loader.ts).
7
+ */
8
+ export declare const DEG: number;
9
+ export declare const ARCSEC: number;
10
+ export declare const J2000 = 2451545;
11
+ export declare const LIGHT_TIME_AU = 0.0057755183;
12
+ /** Python-style modulo: result has the sign of the divisor. */
13
+ export declare function mod(a: number, b: number): number;
14
+ export type VsopSeries = {
15
+ L: number[][][];
16
+ B: number[][][];
17
+ R: number[][][];
18
+ };
19
+ export type MoonSeries = {
20
+ ta: number[][];
21
+ tb: number[][];
22
+ };
23
+ export type ChebData = {
24
+ jd0: number;
25
+ seg_days: number;
26
+ scale?: number;
27
+ segments: number[][][];
28
+ };
29
+ export interface EngineData {
30
+ vsop: Record<string, VsopSeries>;
31
+ nutation: number[][];
32
+ moonMeeus: MoonSeries;
33
+ pluto: number[][];
34
+ chiron?: ChebData;
35
+ moonCheb?: ChebData;
36
+ }
37
+ export declare function julianDay(y: number, mo: number, d: number, h?: number, mi?: number, s?: number): number;
38
+ /** TT - UT1 in seconds. Observed IERS 1955-2025, E&M polynomials before,
39
+ * gentle extrapolation after (Earth's rotation sped up post-2016). */
40
+ export declare function deltaT(jdUt: number): number;
41
+ export declare function jdTT(jdUt: number): number;
42
+ export declare function vsopHeliocentric(series: VsopSeries, jde: number): [number, number, number];
43
+ /** IAU 1980 nutation: [dPsi, dEps] in radians. */
44
+ export declare function nutation(data: EngineData, jde: number): [number, number];
45
+ export declare function meanObliquity(jde: number): number;
46
+ export declare function trueObliquity(data: EngineData, jde: number): number;
47
+ /** Precession of ecliptic coordinates (Meeus 21.7). */
48
+ export declare function precessEcliptic(lon: number, lat: number, jdeFrom: number, jdeTo: number): [number, number];
49
+ /** Apparent geocentric ecliptic lon/lat (true equinox of date), distance. */
50
+ export declare function planetApparent(data: EngineData, name: string, jde: number): [number, number, number];
51
+ export declare function sunApparent(data: EngineData, jde: number): [number, number, number];
52
+ /** Geocentric Moon, mean equinox of date (Meeus ch.47): lon, lat, dist km. */
53
+ export declare function moonGeometric(data: EngineData, jde: number): [number, number, number];
54
+ export declare function moonApparentSeries(data: EngineData, jde: number): [number, number, number];
55
+ export declare class ChebSeries {
56
+ jd0: number;
57
+ seg: number;
58
+ segments: number[][][];
59
+ jd1: number;
60
+ scale: number;
61
+ constructor(data: ChebData);
62
+ private locate;
63
+ xyz(jd: number): [number, number, number];
64
+ xyzVel(jd: number): [[number, number, number], [number, number, number]];
65
+ }
66
+ export declare function moonApparentPrecise(data: EngineData, cheb: ChebSeries, jde: number): [number, number, number];
67
+ export declare function trueNodePrecise(data: EngineData, cheb: ChebSeries, jde: number): number;
68
+ export declare function meanNode(data: EngineData, jde: number): number;
69
+ /** Osculating node from the series moon (fallback outside Chebyshev range). */
70
+ export declare function trueNodeSeries(data: EngineData, jde: number): number;
71
+ export declare function plutoApparent(data: EngineData, jde: number): [number, number, number];
72
+ export declare function chironApparent(data: EngineData, cheb: ChebSeries, jde: number): [number, number, number];
@@ -0,0 +1,463 @@
1
+ /**
2
+ * astroengine core -- clean-room ephemeris engine (TypeScript port).
3
+ *
4
+ * 1:1 port of the validated Python implementation. Environment-agnostic:
5
+ * all coefficient data is injected (works in browser via fetch/bundling,
6
+ * in Node via the loader in node-loader.ts).
7
+ */
8
+ export const DEG = Math.PI / 180.0;
9
+ export const ARCSEC = DEG / 3600.0;
10
+ export const J2000 = 2451545.0;
11
+ export const LIGHT_TIME_AU = 0.0057755183; // days per AU
12
+ const TWO_PI = 2 * Math.PI;
13
+ const C_KM_PER_DAY = 299792.458 * 86400.0;
14
+ /** Python-style modulo: result has the sign of the divisor. */
15
+ export function mod(a, b) {
16
+ const r = a % b;
17
+ return r !== 0 && (r < 0) !== (b < 0) ? r + b : r;
18
+ }
19
+ // ---------------------------------------------------------------- timescale
20
+ export function julianDay(y, mo, d, h = 0, mi = 0, s = 0) {
21
+ const frac = (h + mi / 60.0 + s / 3600.0) / 24.0;
22
+ if (mo <= 2) {
23
+ y -= 1;
24
+ mo += 12;
25
+ }
26
+ const a = Math.floor(y / 100);
27
+ const b = 2 - a + Math.floor(a / 4);
28
+ return (Math.floor(365.25 * (y + 4716)) + Math.floor(30.6001 * (mo + 1))
29
+ + d + b - 1524.5 + frac);
30
+ }
31
+ const DT_OBS = [
32
+ [1955, 31.1], [1960, 33.2], [1965, 35.7], [1970, 40.2], [1975, 45.5],
33
+ [1980, 50.5], [1985, 54.3], [1990, 56.9], [1995, 60.8], [2000, 63.8],
34
+ [2005, 64.7], [2010, 66.1], [2015, 67.6], [2020, 69.4], [2025, 69.2],
35
+ ];
36
+ /** TT - UT1 in seconds. Observed IERS 1955-2025, E&M polynomials before,
37
+ * gentle extrapolation after (Earth's rotation sped up post-2016). */
38
+ export function deltaT(jdUt) {
39
+ const y = 2000.0 + (jdUt - J2000) / 365.25;
40
+ if (y >= 1955 && y <= 2025) {
41
+ for (let i = 0; i < DT_OBS.length - 1; i++) {
42
+ const [y0, d0] = DT_OBS[i];
43
+ const [y1, d1] = DT_OBS[i + 1];
44
+ if (y >= y0 && y <= y1)
45
+ return d0 + ((d1 - d0) * (y - y0)) / (y1 - y0);
46
+ }
47
+ }
48
+ if (y > 2025) {
49
+ // ΔT is flat-to-falling (69.4 -> 69.2 s over 2020-2025; Earth's spin
50
+ // sped up post-2016). Continue the observed slope (-0.04 s/yr) plus the
51
+ // long-term tidal quadratic (+32 s/cy², same coefficient as the
52
+ // deep-time parabola below) so it rejoins the secular rise. An 80-year
53
+ // ΔT forecast carries ~±37 s uncertainty (Huber 2006); anything steeper
54
+ // is false precision.
55
+ const dy = y - 2025;
56
+ return 69.2 - 0.04 * dy + 32 * (dy / 100) ** 2;
57
+ }
58
+ let t;
59
+ if (y >= 1941 && y < 1955) {
60
+ t = y - 1950;
61
+ return 29.07 + 0.407 * t - (t * t) / 233 + t ** 3 / 2547;
62
+ }
63
+ if (y >= 1920 && y < 1941) {
64
+ t = y - 1920;
65
+ return 21.2 + 0.84493 * t - 0.0761 * t * t + 0.0020936 * t ** 3;
66
+ }
67
+ if (y >= 1900 && y < 1920) {
68
+ t = y - 1900;
69
+ return -2.79 + 1.494119 * t - 0.0598939 * t * t + 0.0061966 * t ** 3
70
+ - 0.000197 * t ** 4;
71
+ }
72
+ if (y >= 1860 && y < 1900) {
73
+ t = y - 1860;
74
+ return 7.62 + 0.5737 * t - 0.251754 * t * t + 0.01680668 * t ** 3
75
+ - 0.0004473624 * t ** 4 + t ** 5 / 233174;
76
+ }
77
+ if (y >= 1800 && y < 1860) {
78
+ t = y - 1800;
79
+ return 13.72 - 0.332447 * t + 0.0068612 * t * t + 0.0041116 * t ** 3
80
+ - 0.00037436 * t ** 4 + 0.0000121272 * t ** 5
81
+ - 0.0000001699 * t ** 6 + 0.000000000875 * t ** 7;
82
+ }
83
+ const u = (y - 1820) / 100;
84
+ return -20 + 32 * u * u;
85
+ }
86
+ export function jdTT(jdUt) {
87
+ return jdUt + deltaT(jdUt) / 86400.0;
88
+ }
89
+ // ---------------------------------------------------------------- VSOP87D
90
+ export function vsopHeliocentric(series, jde) {
91
+ const t = (jde - J2000) / 365250.0;
92
+ const out = [];
93
+ for (const v of [series.L, series.B, series.R]) {
94
+ let total = 0.0;
95
+ let tn = 1.0;
96
+ for (const orderTerms of v) {
97
+ let acc = 0.0;
98
+ for (const [A, B, C] of orderTerms)
99
+ acc += A * Math.cos(B + C * t);
100
+ total += acc * tn;
101
+ tn *= t;
102
+ }
103
+ out.push(total);
104
+ }
105
+ return [mod(out[0], TWO_PI), out[1], out[2]];
106
+ }
107
+ // ---------------------------------------------------------------- nutation
108
+ /** IAU 1980 nutation: [dPsi, dEps] in radians. */
109
+ export function nutation(data, jde) {
110
+ const T = (jde - J2000) / 36525.0;
111
+ const D = (297.85036 + 445267.11148 * T - 0.0019142 * T * T + T ** 3 / 189474) * DEG;
112
+ const M = (357.52772 + 35999.05034 * T - 0.0001603 * T * T - T ** 3 / 300000) * DEG;
113
+ const N = (134.96298 + 477198.867398 * T + 0.0086972 * T * T + T ** 3 / 5620) * DEG;
114
+ const F = (93.27191 + 483202.017538 * T - 0.0036825 * T * T + T ** 3 / 327270) * DEG;
115
+ const Om = (125.04452 - 1934.136261 * T + 0.0020708 * T * T + T ** 3 / 450000) * DEG;
116
+ let dpsi = 0.0;
117
+ let deps = 0.0;
118
+ const tbl = data.nutation;
119
+ for (let i = tbl.length - 1; i >= 0; i--) {
120
+ const [d, m, n, f, om, s0, s1, c0, c1] = tbl[i];
121
+ const arg = d * D + m * M + n * N + f * F + om * Om;
122
+ dpsi += Math.sin(arg) * (s0 + s1 * T);
123
+ deps += Math.cos(arg) * (c0 + c1 * T);
124
+ }
125
+ return [dpsi * 1e-4 * ARCSEC, deps * 1e-4 * ARCSEC];
126
+ }
127
+ export function meanObliquity(jde) {
128
+ const T = (jde - J2000) / 36525.0;
129
+ return (84381.448 - 46.815 * T - 0.00059 * T * T + 0.001813 * T ** 3) * ARCSEC;
130
+ }
131
+ export function trueObliquity(data, jde) {
132
+ return meanObliquity(jde) + nutation(data, jde)[1];
133
+ }
134
+ // ---------------------------------------------------------------- frame fix
135
+ function fk5Correction(L, B, jde) {
136
+ const T = (jde - J2000) / 36525.0;
137
+ const Lp = L - (1.397 + 0.00031 * T) * T * DEG;
138
+ const dL = -0.09033 * ARCSEC
139
+ + 0.03916 * ARCSEC * (Math.cos(Lp) + Math.sin(Lp)) * Math.tan(B);
140
+ const dB = 0.03916 * ARCSEC * (Math.cos(Lp) - Math.sin(Lp));
141
+ return [L + dL, B + dB];
142
+ }
143
+ /** Precession of ecliptic coordinates (Meeus 21.7). */
144
+ export function precessEcliptic(lon, lat, jdeFrom, jdeTo) {
145
+ const T = (jdeFrom - J2000) / 36525.0;
146
+ const t = (jdeTo - jdeFrom) / 36525.0;
147
+ const eta = ((47.0029 - 0.06603 * T + 0.000598 * T * T) * t
148
+ + (-0.03302 + 0.000598 * T) * t * t + 0.00006 * t ** 3) * ARCSEC;
149
+ const Pi = (174.876384 * 3600 + 3289.4789 * T + 0.60622 * T * T) * ARCSEC
150
+ - ((869.8089 + 0.50491 * T) * t - 0.03536 * t * t) * ARCSEC;
151
+ const p = ((5029.0966 + 2.22226 * T - 0.000042 * T * T) * t
152
+ + (1.11113 - 0.000042 * T) * t * t - 0.000006 * t ** 3) * ARCSEC;
153
+ const se = Math.sin(eta);
154
+ const ce = Math.cos(eta);
155
+ const A = ce * Math.cos(lat) * Math.sin(Pi - lon) - se * Math.sin(lat);
156
+ const Bv = Math.cos(lat) * Math.cos(Pi - lon);
157
+ const C = ce * Math.sin(lat) + se * Math.cos(lat) * Math.sin(Pi - lon);
158
+ return [mod(p + Pi - Math.atan2(A, Bv), TWO_PI), Math.asin(C)];
159
+ }
160
+ /** Rotate a vector from ecliptic-J2000 to ecliptic-of-date frame. */
161
+ function eclJ2000ToEclDate(v, jde) {
162
+ let [x, y, z] = v;
163
+ const e0 = 84381.448 * ARCSEC;
164
+ [y, z] = [y * Math.cos(e0) - z * Math.sin(e0), y * Math.sin(e0) + z * Math.cos(e0)];
165
+ const T = (jde - J2000) / 36525.0;
166
+ const zeta = (2306.2181 * T + 0.30188 * T * T + 0.017998 * T ** 3) * ARCSEC;
167
+ const zz = (2306.2181 * T + 1.09468 * T * T + 0.018203 * T ** 3) * ARCSEC;
168
+ const th = (2004.3109 * T - 0.42665 * T * T - 0.041833 * T ** 3) * ARCSEC;
169
+ const rz = (a) => {
170
+ const c = Math.cos(a);
171
+ const s = Math.sin(a);
172
+ [x, y] = [c * x + s * y, -s * x + c * y];
173
+ };
174
+ const ry = (a) => {
175
+ const c = Math.cos(a);
176
+ const s = Math.sin(a);
177
+ [x, z] = [c * x - s * z, s * x + c * z];
178
+ };
179
+ rz(-zeta);
180
+ ry(th);
181
+ rz(-zz);
182
+ const e = meanObliquity(jde);
183
+ [y, z] = [y * Math.cos(e) + z * Math.sin(e), -y * Math.sin(e) + z * Math.cos(e)];
184
+ return [x, y, z];
185
+ }
186
+ // ---------------------------------------------------------------- planets
187
+ function geoVector(data, name, jde) {
188
+ const [L0, B0, R0] = vsopHeliocentric(data.vsop.earth, jde);
189
+ const [L, B, R] = vsopHeliocentric(data.vsop[name], jde);
190
+ return [
191
+ R * Math.cos(B) * Math.cos(L) - R0 * Math.cos(B0) * Math.cos(L0),
192
+ R * Math.cos(B) * Math.sin(L) - R0 * Math.cos(B0) * Math.sin(L0),
193
+ R * Math.sin(B) - R0 * Math.sin(B0),
194
+ ];
195
+ }
196
+ /** Apparent geocentric ecliptic lon/lat (true equinox of date), distance. */
197
+ export function planetApparent(data, name, jde) {
198
+ let [x, y, z] = geoVector(data, name, jde);
199
+ let delta = Math.sqrt(x * x + y * y + z * z);
200
+ for (let i = 0; i < 2; i++) {
201
+ const tau = LIGHT_TIME_AU * delta;
202
+ [x, y, z] = geoVector(data, name, jde - tau);
203
+ delta = Math.sqrt(x * x + y * y + z * z);
204
+ }
205
+ let lon = mod(Math.atan2(y, x), TWO_PI);
206
+ let lat = Math.atan2(z, Math.sqrt(x * x + y * y));
207
+ [lon, lat] = fk5Correction(lon, lat, jde);
208
+ lon = mod(lon + nutation(data, jde)[0], TWO_PI);
209
+ return [lon, lat, delta];
210
+ }
211
+ export function sunApparent(data, jde) {
212
+ const [L0, B0, R0] = vsopHeliocentric(data.vsop.earth, jde);
213
+ let lon = mod(L0 + Math.PI, TWO_PI);
214
+ let lat = -B0;
215
+ [lon, lat] = fk5Correction(lon, lat, jde);
216
+ lon -= (20.4898 * ARCSEC) / R0;
217
+ lon = mod(lon + nutation(data, jde)[0], TWO_PI);
218
+ return [lon, lat, R0];
219
+ }
220
+ // ---------------------------------------------------------------- moon
221
+ function moonFundamental(T) {
222
+ const Lp = (218.3164477 + 481267.88123421 * T - 0.0015786 * T * T
223
+ + T ** 3 / 538841 - T ** 4 / 65194000) * DEG;
224
+ const D = (297.8501921 + 445267.1114034 * T - 0.0018819 * T * T
225
+ + T ** 3 / 545868 - T ** 4 / 113065000) * DEG;
226
+ const M = (357.5291092 + 35999.0502909 * T - 0.0001535 * T * T
227
+ + T ** 3 / 24490000) * DEG;
228
+ const Mp = (134.9633964 + 477198.8675055 * T + 0.0087414 * T * T
229
+ + T ** 3 / 69699 - T ** 4 / 14712000) * DEG;
230
+ const F = (93.272095 + 483202.0175233 * T - 0.0036539 * T * T
231
+ - T ** 3 / 3526000 + T ** 4 / 863310000) * DEG;
232
+ return [Lp, D, M, Mp, F];
233
+ }
234
+ /** Geocentric Moon, mean equinox of date (Meeus ch.47): lon, lat, dist km. */
235
+ export function moonGeometric(data, jde) {
236
+ const T = (jde - J2000) / 36525.0;
237
+ const [Lp, D, M, Mp, F] = moonFundamental(T);
238
+ const A1 = (119.75 + 131.849 * T) * DEG;
239
+ const A2 = (53.09 + 479264.29 * T) * DEG;
240
+ const A3 = (313.45 + 481266.484 * T) * DEG;
241
+ const E = 1 - 0.002516 * T - 0.0000074 * T * T;
242
+ const E2 = E * E;
243
+ let sl = 3958 * Math.sin(A1) + 1962 * Math.sin(Lp - F) + 318 * Math.sin(A2);
244
+ let sr = 0.0;
245
+ let sb = -2235 * Math.sin(Lp) + 382 * Math.sin(A3) + 175 * Math.sin(A1 - F)
246
+ + 175 * Math.sin(A1 + F) + 127 * Math.sin(Lp - Mp) - 115 * Math.sin(Lp + Mp);
247
+ for (const [d, m, mp, f, lC, rC] of data.moonMeeus.ta) {
248
+ const arg = d * D + m * M + mp * Mp + f * F;
249
+ const e = Math.abs(m) === 1 ? E : Math.abs(m) === 2 ? E2 : 1.0;
250
+ sl += lC * Math.sin(arg) * e;
251
+ sr += rC * Math.cos(arg) * e;
252
+ }
253
+ for (const [d, m, mp, f, bC] of data.moonMeeus.tb) {
254
+ const arg = d * D + m * M + mp * Mp + f * F;
255
+ const e = Math.abs(m) === 1 ? E : Math.abs(m) === 2 ? E2 : 1.0;
256
+ sb += bC * Math.sin(arg) * e;
257
+ }
258
+ return [
259
+ mod(Lp + sl * 1e-6 * DEG, TWO_PI),
260
+ sb * 1e-6 * DEG,
261
+ 385000.56 + sr * 1e-3,
262
+ ];
263
+ }
264
+ export function moonApparentSeries(data, jde) {
265
+ const [lon, lat, dist] = moonGeometric(data, jde);
266
+ return [mod(lon + nutation(data, jde)[0], TWO_PI), lat, dist];
267
+ }
268
+ // ---------------------------------------------------------------- chebyshev
269
+ function clenshaw(coeffs, x) {
270
+ let b0 = 0.0;
271
+ let b1 = 0.0;
272
+ for (let i = coeffs.length - 1; i >= 1; i--) {
273
+ [b0, b1] = [2.0 * x * b0 - b1 + coeffs[i], b0];
274
+ }
275
+ return x * b0 - b1 + coeffs[0];
276
+ }
277
+ function clenshawDeriv(coeffs, x, halfSpanDays) {
278
+ const n = coeffs.length;
279
+ const d = new Array(n).fill(0.0);
280
+ for (let k = n - 1; k >= 1; k--) {
281
+ d[k - 1] = (k + 1 < n ? d[k + 1] : 0.0) + 2.0 * k * coeffs[k];
282
+ }
283
+ d[0] *= 0.5;
284
+ return [clenshaw(coeffs, x), clenshaw(d.slice(0, Math.max(n - 1, 1)), x) / halfSpanDays];
285
+ }
286
+ export class ChebSeries {
287
+ jd0;
288
+ seg;
289
+ segments;
290
+ jd1;
291
+ scale;
292
+ constructor(data) {
293
+ this.jd0 = data.jd0;
294
+ this.seg = data.seg_days;
295
+ this.segments = data.segments;
296
+ this.jd1 = this.jd0 + this.seg * this.segments.length;
297
+ this.scale = data.scale ?? 1.0;
298
+ }
299
+ locate(jd) {
300
+ if (jd < this.jd0 || jd > this.jd1) {
301
+ throw new RangeError(`jd ${jd} outside fitted range ${this.jd0}-${this.jd1}`);
302
+ }
303
+ const i = Math.min(Math.floor((jd - this.jd0) / this.seg), this.segments.length - 1);
304
+ const x = (2.0 * (jd - (this.jd0 + i * this.seg))) / this.seg - 1.0;
305
+ return [i, x];
306
+ }
307
+ xyz(jd) {
308
+ const [i, x] = this.locate(jd);
309
+ const s = this.segments[i];
310
+ return [
311
+ clenshaw(s[0], x) * this.scale,
312
+ clenshaw(s[1], x) * this.scale,
313
+ clenshaw(s[2], x) * this.scale,
314
+ ];
315
+ }
316
+ xyzVel(jd) {
317
+ const [i, x] = this.locate(jd);
318
+ const s = this.segments[i];
319
+ const half = this.seg / 2.0;
320
+ const pos = [];
321
+ const vel = [];
322
+ for (const c of s) {
323
+ const [p, v] = clenshawDeriv(c, x, half);
324
+ pos.push(p * this.scale);
325
+ vel.push(v * this.scale);
326
+ }
327
+ return [pos, vel];
328
+ }
329
+ }
330
+ // ---------------------------------------------------------------- precise moon
331
+ export function moonApparentPrecise(data, cheb, jde) {
332
+ let [x, y, z] = cheb.xyz(jde);
333
+ const dist = Math.sqrt(x * x + y * y + z * z);
334
+ const tau = dist / C_KM_PER_DAY;
335
+ [x, y, z] = cheb.xyz(jde - tau);
336
+ let lon = mod(Math.atan2(y, x), TWO_PI);
337
+ let lat = Math.atan2(z, Math.sqrt(x * x + y * y));
338
+ [lon, lat] = precessEcliptic(lon, lat, J2000, jde);
339
+ lon = mod(lon + nutation(data, jde)[0], TWO_PI);
340
+ return [lon, lat, dist];
341
+ }
342
+ export function trueNodePrecise(data, cheb, jde) {
343
+ const [[x, y, z], [vx, vy, vz]] = cheb.xyzVel(jde);
344
+ const h = [
345
+ y * vz - z * vy, z * vx - x * vz, x * vy - y * vx,
346
+ ];
347
+ const [hx, hy] = eclJ2000ToEclDate(h, jde);
348
+ const node = mod(Math.atan2(hx, -hy), TWO_PI);
349
+ return mod(node + nutation(data, jde)[0], TWO_PI);
350
+ }
351
+ // ---------------------------------------------------------------- lunar node
352
+ export function meanNode(data, jde) {
353
+ const T = (jde - J2000) / 36525.0;
354
+ const om = (125.0445479 - 1934.1362891 * T + 0.0020754 * T * T
355
+ + T ** 3 / 467441 - T ** 4 / 60616000) * DEG;
356
+ return mod(om + nutation(data, jde)[0], TWO_PI);
357
+ }
358
+ /** Osculating node from the series moon (fallback outside Chebyshev range). */
359
+ export function trueNodeSeries(data, jde) {
360
+ const h = 0.01;
361
+ const xyz = (t) => {
362
+ const [lon, lat, dist] = moonGeometric(data, t);
363
+ return [
364
+ dist * Math.cos(lat) * Math.cos(lon),
365
+ dist * Math.cos(lat) * Math.sin(lon),
366
+ dist * Math.sin(lat),
367
+ ];
368
+ };
369
+ const [x0, y0, z0] = xyz(jde - h);
370
+ const [x1, y1, z1] = xyz(jde + h);
371
+ const [x, y, z] = xyz(jde);
372
+ const vx = (x1 - x0) / (2 * h);
373
+ const vy = (y1 - y0) / (2 * h);
374
+ const vz = (z1 - z0) / (2 * h);
375
+ const hx = y * vz - z * vy;
376
+ const hy = z * vx - x * vz;
377
+ const node = mod(Math.atan2(hx, -hy), TWO_PI);
378
+ return mod(node + nutation(data, jde)[0], TWO_PI);
379
+ }
380
+ // ---------------------------------------------------------------- pluto
381
+ export function plutoApparent(data, jde) {
382
+ const helioJ2000 = (tJde) => {
383
+ const T = (tJde - J2000) / 36525.0;
384
+ const J = (34.35 + 3034.9057 * T) * DEG;
385
+ const S = (50.08 + 1222.1138 * T) * DEG;
386
+ const P = (238.96 + 144.96 * T) * DEG;
387
+ let l = 0.0;
388
+ let b = 0.0;
389
+ let r = 0.0;
390
+ for (const [i, j, k, lA, lB, bA, bB, rA, rB] of data.pluto) {
391
+ const a = i * J + j * S + k * P;
392
+ const sa = Math.sin(a);
393
+ const ca = Math.cos(a);
394
+ l += lA * sa + lB * ca;
395
+ b += bA * sa + bB * ca;
396
+ r += rA * sa + rB * ca;
397
+ }
398
+ return [
399
+ (l + 238.958116 + 144.96 * T) * DEG,
400
+ (b - 3.908239) * DEG,
401
+ r + 40.7241346,
402
+ ];
403
+ };
404
+ const [L0d, B0d, R0d] = vsopHeliocentric(data.vsop.earth, jde);
405
+ const [Lj, Bj] = precessEcliptic(L0d, B0d, jde, J2000);
406
+ const ex = R0d * Math.cos(Bj) * Math.cos(Lj);
407
+ const ey = R0d * Math.cos(Bj) * Math.sin(Lj);
408
+ const ez = R0d * Math.sin(Bj);
409
+ const geo = (t) => {
410
+ const [l, b, r] = helioJ2000(t);
411
+ return [
412
+ r * Math.cos(b) * Math.cos(l) - ex,
413
+ r * Math.cos(b) * Math.sin(l) - ey,
414
+ r * Math.sin(b) - ez,
415
+ ];
416
+ };
417
+ let [x, y, z] = geo(jde);
418
+ let delta = Math.sqrt(x * x + y * y + z * z);
419
+ for (let i = 0; i < 2; i++) {
420
+ [x, y, z] = geo(jde - LIGHT_TIME_AU * delta);
421
+ delta = Math.sqrt(x * x + y * y + z * z);
422
+ }
423
+ let lon = mod(Math.atan2(y, x), TWO_PI);
424
+ let lat = Math.atan2(z, Math.sqrt(x * x + y * y));
425
+ const T = (jde - J2000) / 36525.0;
426
+ const sunLon = mod(L0d + Math.PI, TWO_PI);
427
+ const k = 20.4898 * ARCSEC;
428
+ const e = 0.016708634 - 0.000042037 * T;
429
+ const piPer = (102.93735 + 1.71946 * T) * DEG;
430
+ lon += (-k * Math.cos(sunLon - lon) + e * k * Math.cos(piPer - lon)) / Math.cos(lat);
431
+ [lon, lat] = precessEcliptic(lon, lat, J2000, jde);
432
+ lon = mod(lon + nutation(data, jde)[0], TWO_PI);
433
+ return [lon, lat, delta];
434
+ }
435
+ // ---------------------------------------------------------------- chiron
436
+ export function chironApparent(data, cheb, jde) {
437
+ const [L0, B0, R0] = vsopHeliocentric(data.vsop.earth, jde);
438
+ const [Lj, Bj] = precessEcliptic(L0, B0, jde, J2000);
439
+ const ex = R0 * Math.cos(Bj) * Math.cos(Lj);
440
+ const ey = R0 * Math.cos(Bj) * Math.sin(Lj);
441
+ const ez = R0 * Math.sin(Bj);
442
+ const geo = (t) => {
443
+ const [cx, cy, cz] = cheb.xyz(t);
444
+ return [cx - ex, cy - ey, cz - ez];
445
+ };
446
+ let [x, y, z] = geo(jde);
447
+ let delta = Math.sqrt(x * x + y * y + z * z);
448
+ for (let i = 0; i < 2; i++) {
449
+ [x, y, z] = geo(jde - LIGHT_TIME_AU * delta);
450
+ delta = Math.sqrt(x * x + y * y + z * z);
451
+ }
452
+ let lon = mod(Math.atan2(y, x), TWO_PI);
453
+ let lat = Math.atan2(z, Math.sqrt(x * x + y * y));
454
+ const T = (jde - J2000) / 36525.0;
455
+ const sunLon = mod(L0 + Math.PI, TWO_PI);
456
+ const k = 20.4898 * ARCSEC;
457
+ const e = 0.016708634 - 0.000042037 * T;
458
+ const piPer = (102.93735 + 1.71946 * T) * DEG;
459
+ lon += (-k * Math.cos(sunLon - lon) + e * k * Math.cos(piPer - lon)) / Math.cos(lat);
460
+ [lon, lat] = precessEcliptic(lon, lat, J2000, jde);
461
+ lon = mod(lon + nutation(data, jde)[0], TWO_PI);
462
+ return [lon, lat, delta];
463
+ }
@@ -0,0 +1,2 @@
1
+ import type { EngineData } from "./core.js";
2
+ export declare const embeddedData: EngineData;
@@ -0,0 +1,20 @@
1
+ /** Bundler-friendly embedded dataset: ~85 KB gzipped total. Imports JSON
2
+ * statically so web bundlers (Next.js, Vite) inline it -- charts fully
3
+ * client-side. The precise moon tier is intentionally NOT here (729 KB);
4
+ * fetch it lazily and pass via { ...embeddedData, moonCheb } if wanted. */
5
+ import mercury from "../data/vsop87d_mercury.embedded.json" with { type: "json" };
6
+ import venus from "../data/vsop87d_venus.embedded.json" with { type: "json" };
7
+ import earth from "../data/vsop87d_earth.embedded.json" with { type: "json" };
8
+ import mars from "../data/vsop87d_mars.embedded.json" with { type: "json" };
9
+ import jupiter from "../data/vsop87d_jupiter.embedded.json" with { type: "json" };
10
+ import saturn from "../data/vsop87d_saturn.embedded.json" with { type: "json" };
11
+ import uranus from "../data/vsop87d_uranus.embedded.json" with { type: "json" };
12
+ import neptune from "../data/vsop87d_neptune.embedded.json" with { type: "json" };
13
+ import nutation from "../data/nutation_iau1980.json" with { type: "json" };
14
+ import moonMeeus from "../data/moon_meeus47.json" with { type: "json" };
15
+ import pluto from "../data/pluto_meeus37.json" with { type: "json" };
16
+ import chiron from "../data/chiron_cheb.json" with { type: "json" };
17
+ export const embeddedData = {
18
+ vsop: { mercury, venus, earth, mars, jupiter, saturn, uranus, neptune },
19
+ nutation, moonMeeus, pluto, chiron,
20
+ };
@@ -0,0 +1,18 @@
1
+ /** astroengine houses -- sidereal time, angles, house systems. */
2
+ import { EngineData } from "./core.js";
3
+ /** Greenwich mean sidereal time, radians (IAU 1982 / Meeus 12.4). */
4
+ export declare function gmst(jdUt: number): number;
5
+ /** Greenwich apparent sidereal time. */
6
+ export declare function gast(data: EngineData, jdUt: number): number;
7
+ /** Ascendant, MC, ARMC, obliquity. East longitude positive. */
8
+ export declare function angles(data: EngineData, jdUt: number, latDeg: number, lonDeg: number): [number, number, number, number];
9
+ export declare function housesWholeSign(asc: number): number[];
10
+ export declare function housesEqual(asc: number): number[];
11
+ export declare function housesPorphyry(asc: number, mc: number): number[];
12
+ /**
13
+ * Placidus cusps via the classic iterative scheme. Semi-arc derivation:
14
+ * for ALL four intermediate cusps RA = ARMC + offset + f*AD with
15
+ * AD = asin(tan(phi) tan(dec)); offsets 30/60/120/150, f = 1/3,2/3,2/3,1/3.
16
+ * Undefined above the polar circles (as Placidus itself is).
17
+ */
18
+ export declare function housesPlacidus(armc: number, phi: number, eps: number): number[];
@@ -0,0 +1,96 @@
1
+ /** astroengine houses -- sidereal time, angles, house systems. */
2
+ import { DEG, J2000, mod, nutation, trueObliquity, jdTT, } from "./core.js";
3
+ const TWO_PI = 2 * Math.PI;
4
+ /** Greenwich mean sidereal time, radians (IAU 1982 / Meeus 12.4). */
5
+ export function gmst(jdUt) {
6
+ const T = (jdUt - J2000) / 36525.0;
7
+ const deg = 280.46061837 + 360.98564736629 * (jdUt - J2000)
8
+ + 0.000387933 * T * T - T ** 3 / 38710000.0;
9
+ return mod(deg, 360.0) * DEG;
10
+ }
11
+ /** Greenwich apparent sidereal time. */
12
+ export function gast(data, jdUt) {
13
+ const jde = jdTT(jdUt);
14
+ const [dpsi] = nutation(data, jde);
15
+ const eps = trueObliquity(data, jde);
16
+ return mod(gmst(jdUt) + dpsi * Math.cos(eps), TWO_PI);
17
+ }
18
+ /** Ascendant, MC, ARMC, obliquity. East longitude positive. */
19
+ export function angles(data, jdUt, latDeg, lonDeg) {
20
+ const jde = jdTT(jdUt);
21
+ const eps = trueObliquity(data, jde);
22
+ const armc = mod(gast(data, jdUt) + lonDeg * DEG, TWO_PI);
23
+ const phi = latDeg * DEG;
24
+ const mc = mod(Math.atan2(Math.sin(armc), Math.cos(armc) * Math.cos(eps)), TWO_PI);
25
+ const asc = mod(Math.atan2(Math.cos(armc), -(Math.sin(armc) * Math.cos(eps) + Math.tan(phi) * Math.sin(eps))), TWO_PI);
26
+ return [asc, mc, armc, eps];
27
+ }
28
+ export function housesWholeSign(asc) {
29
+ const first = Math.floor(asc / (30 * DEG)) * 30 * DEG;
30
+ return Array.from({ length: 12 }, (_, i) => mod(first + i * 30 * DEG, TWO_PI));
31
+ }
32
+ export function housesEqual(asc) {
33
+ return Array.from({ length: 12 }, (_, i) => mod(asc + i * 30 * DEG, TWO_PI));
34
+ }
35
+ export function housesPorphyry(asc, mc) {
36
+ const ic = mod(mc + Math.PI, TWO_PI);
37
+ const dsc = mod(asc + Math.PI, TWO_PI);
38
+ const span = (a, b) => mod(b - a, TWO_PI);
39
+ const cusps = new Array(12).fill(0);
40
+ cusps[0] = asc;
41
+ cusps[9] = mc;
42
+ let s = span(mc, asc) / 3.0;
43
+ cusps[10] = mod(mc + s, TWO_PI);
44
+ cusps[11] = mod(mc + 2 * s, TWO_PI);
45
+ s = span(asc, ic) / 3.0;
46
+ cusps[1] = mod(asc + s, TWO_PI);
47
+ cusps[2] = mod(asc + 2 * s, TWO_PI);
48
+ cusps[3] = ic;
49
+ cusps[6] = dsc;
50
+ cusps[4] = mod(cusps[10] + Math.PI, TWO_PI);
51
+ cusps[5] = mod(cusps[11] + Math.PI, TWO_PI);
52
+ cusps[7] = mod(cusps[1] + Math.PI, TWO_PI);
53
+ cusps[8] = mod(cusps[2] + Math.PI, TWO_PI);
54
+ return cusps;
55
+ }
56
+ /**
57
+ * Placidus cusps via the classic iterative scheme. Semi-arc derivation:
58
+ * for ALL four intermediate cusps RA = ARMC + offset + f*AD with
59
+ * AD = asin(tan(phi) tan(dec)); offsets 30/60/120/150, f = 1/3,2/3,2/3,1/3.
60
+ * Undefined above the polar circles (as Placidus itself is).
61
+ */
62
+ export function housesPlacidus(armc, phi, eps) {
63
+ const cusp = (offsetDeg, f) => {
64
+ let lam = mod(armc + offsetDeg * DEG, TWO_PI);
65
+ for (let i = 0; i < 50; i++) {
66
+ const dec = Math.asin(Math.sin(eps) * Math.sin(lam));
67
+ let x = Math.tan(phi) * Math.tan(dec);
68
+ x = Math.max(-1.0, Math.min(1.0, x));
69
+ const ad = Math.asin(x);
70
+ const raI = mod(armc + offsetDeg * DEG + f * ad, TWO_PI);
71
+ const lamNew = mod(Math.atan2(Math.sin(raI), Math.cos(raI) * Math.cos(eps)), TWO_PI);
72
+ if (Math.abs(mod(lamNew - lam + Math.PI, TWO_PI) - Math.PI) < 1e-10) {
73
+ lam = lamNew;
74
+ break;
75
+ }
76
+ lam = lamNew;
77
+ }
78
+ return lam;
79
+ };
80
+ const mc = mod(Math.atan2(Math.sin(armc), Math.cos(armc) * Math.cos(eps)), TWO_PI);
81
+ const asc = mod(Math.atan2(Math.cos(armc), -(Math.sin(armc) * Math.cos(eps) + Math.tan(phi) * Math.sin(eps))), TWO_PI);
82
+ const cusps = new Array(12).fill(0);
83
+ cusps[0] = asc;
84
+ cusps[9] = mc;
85
+ cusps[10] = cusp(30, 1.0 / 3.0);
86
+ cusps[11] = cusp(60, 2.0 / 3.0);
87
+ cusps[1] = cusp(120, 2.0 / 3.0);
88
+ cusps[2] = cusp(150, 1.0 / 3.0);
89
+ cusps[3] = mod(mc + Math.PI, TWO_PI);
90
+ cusps[6] = mod(asc + Math.PI, TWO_PI);
91
+ cusps[4] = mod(cusps[10] + Math.PI, TWO_PI);
92
+ cusps[5] = mod(cusps[11] + Math.PI, TWO_PI);
93
+ cusps[7] = mod(cusps[1] + Math.PI, TWO_PI);
94
+ cusps[8] = mod(cusps[2] + Math.PI, TWO_PI);
95
+ return cusps;
96
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./core.js";
2
+ export * from "./houses.js";
3
+ export * from "./chart.js";
@@ -0,0 +1,3 @@
1
+ export * from "./core.js";
2
+ export * from "./houses.js";
3
+ export * from "./chart.js";