caelus 0.5.0 → 0.6.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 +1 -1
- package/accuracy.json +9 -9
- package/dist/src/core.d.ts +5 -4
- package/dist/src/core.js +133 -43
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/query.d.ts +32 -0
- package/dist/src/query.js +161 -0
- package/dist/src/stars.d.ts +2 -3
- package/dist/src/stars.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ ephemeris files. 1:1 port of the Python reference, checked by golden fixtures.
|
|
|
11
11
|
(vs full DE431 files, 1850–2149), angles and Placidus cusps ≤ 3.2″ — all
|
|
12
12
|
invisible at the arcminute display precision chart software uses.
|
|
13
13
|
2. TypeScript port verified against Python golden fixtures: **3,218 checks,
|
|
14
|
-
0 failures, worst deviation
|
|
14
|
+
0 failures, worst deviation 0.41 nano-arcseconds.** The two implementations
|
|
15
15
|
are numerically identical.
|
|
16
16
|
|
|
17
17
|
Regenerate fixtures any time from the Python side; any future TS change must
|
package/accuracy.json
CHANGED
|
@@ -107,9 +107,9 @@
|
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
109
|
"name": "Sidereal longitudes",
|
|
110
|
-
"max": "
|
|
110
|
+
"max": "—",
|
|
111
111
|
"rms": "—",
|
|
112
|
-
"note": "ayanamsa model vs SE ≤0.
|
|
112
|
+
"note": "ayanamsa model vs SE ≤0.005″ (Vondrák 2011, same model both sides); sidereal longitudes inherit each body's tropical bound"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "RA / Dec",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
"name": "Topocentric Moon",
|
|
122
|
-
"max": "2.
|
|
122
|
+
"max": "2.5",
|
|
123
123
|
"rms": "—",
|
|
124
124
|
"note": "parallax model adds ≤0.1″ over the geocentric bound"
|
|
125
125
|
},
|
|
@@ -179,15 +179,15 @@
|
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"name": "Fixed stars (318-star catalog)",
|
|
182
|
-
"max": "0.
|
|
182
|
+
"max": "0.3",
|
|
183
183
|
"rms": "—",
|
|
184
|
-
"note": "HYG-derived catalog (ICRS J2000 + proper motions, full 3D space motion); vs swe_fixstar fed the same rows
|
|
184
|
+
"note": "HYG-derived catalog (ICRS J2000 + proper motions, full 3D space motion, Vondrák 2011 precession); vs swe_fixstar fed the same rows"
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
"name": "Star-anchored ayanamsas",
|
|
188
|
-
"max": "0.
|
|
188
|
+
"max": "0.5",
|
|
189
189
|
"rms": "—",
|
|
190
|
-
"note": "galcent_0sag and true_citra computed from the apparent star
|
|
190
|
+
"note": "galcent_0sag and true_citra computed from the apparent star; bound tracks the fixed-star chain (≤0.3″) plus the body's tropical accuracy"
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"name": "Gauquelin sectors",
|
|
@@ -257,11 +257,11 @@
|
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
259
|
"label": "Fixed stars",
|
|
260
|
-
"bound": "≤ 0.
|
|
260
|
+
"bound": "≤ 0.3″"
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
"label": "Sidereal (7 ayanamsas)",
|
|
264
|
-
"bound": "≤ 0.
|
|
264
|
+
"bound": "≤ 0.005″ added"
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"label": "Eclipses",
|
package/dist/src/core.d.ts
CHANGED
|
@@ -69,7 +69,8 @@ export declare function vsopHeliocentric(series: VsopSeries, jde: number): [numb
|
|
|
69
69
|
export declare function nutation(data: EngineData, jde: number): [number, number];
|
|
70
70
|
export declare function meanObliquity(jde: number): number;
|
|
71
71
|
export declare function trueObliquity(data: EngineData, jde: number): number;
|
|
72
|
-
/** Precession of ecliptic coordinates (
|
|
72
|
+
/** Precession of ecliptic coordinates between epochs (Vondrak 2011):
|
|
73
|
+
* ecliptic-of-from -> J2000 equatorial -> ecliptic-of-to. */
|
|
73
74
|
export declare function precessEcliptic(lon: number, lat: number, jdeFrom: number, jdeTo: number): [number, number];
|
|
74
75
|
/** Apparent geocentric ecliptic lon/lat (true equinox of date), distance. */
|
|
75
76
|
export declare function planetApparent(data: EngineData, name: string, jde: number): [number, number, number];
|
|
@@ -96,9 +97,9 @@ export declare function trueNodeSeries(data: EngineData, jde: number): number;
|
|
|
96
97
|
/** Ecliptic lon/lat -> right ascension, declination (all radians). */
|
|
97
98
|
export declare function equatorial(lon: number, lat: number, eps: number): [number, number];
|
|
98
99
|
/** Mean ayanamsa at J2000.0 (degrees) per mode. Standard epoch anchors
|
|
99
|
-
* (matched to Swiss Ephemeris 2.10 to 1e-9 deg); propagation uses
|
|
100
|
-
* ecliptic precession
|
|
101
|
-
*
|
|
100
|
+
* (matched to Swiss Ephemeris 2.10 to 1e-9 deg); propagation uses Vondrak
|
|
101
|
+
* 2011 ecliptic precession, the same model Swiss Ephemeris uses:
|
|
102
|
+
* agreement over 1900-2099 is <=0.005 arcsec. */
|
|
102
103
|
export declare const AYANAMSA_J2000: Record<string, number>;
|
|
103
104
|
/** Mean ayanamsa in degrees. Sidereal longitude = (tropical true-equinox
|
|
104
105
|
* longitude - nutation in longitude) - ayanamsa: the sidereal zodiac is
|
package/dist/src/core.js
CHANGED
|
@@ -140,48 +140,138 @@ function fk5Correction(L, B, jde) {
|
|
|
140
140
|
const dB = 0.03916 * ARCSEC * (Math.cos(Lp) - Math.sin(Lp));
|
|
141
141
|
return [L + dL, B + dB];
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
// ------------------------------------------------- Vondrak 2011 precession
|
|
144
|
+
// Long-term precession of the ecliptic and equator (Vondrak, Capitaine &
|
|
145
|
+
// Wallace 2011, A&A 534 A22; coefficient tables as carried by ERFA under
|
|
146
|
+
// BSD-3). Replaces the IAU 1976 angles.
|
|
147
|
+
const PQ_POL = [
|
|
148
|
+
[5851.607687, -0.1189, -0.00028913, 0.000000101],
|
|
149
|
+
[-1600.8863, 1.1689818, -0.0000002, -0.000000437],
|
|
150
|
+
];
|
|
151
|
+
const PQ_PER = [
|
|
152
|
+
[708.15, -5486.751211, -684.66156, 667.66673, -5523.863691],
|
|
153
|
+
[2309.0, -17.127623, 2446.28388, -2354.886252, -549.74745],
|
|
154
|
+
[1620.0, -617.517403, 399.671049, -428.152441, -310.998056],
|
|
155
|
+
[492.2, 413.44294, -356.652376, 376.202861, 421.535876],
|
|
156
|
+
[1183.0, 78.614193, -186.387003, 184.778874, -36.776172],
|
|
157
|
+
[622.0, -180.732815, -316.80007, 335.321713, -145.278396],
|
|
158
|
+
[882.0, -87.676083, 198.296701, -185.138669, -34.74445],
|
|
159
|
+
[547.0, 46.140315, 101.135679, -120.97283, 22.885731],
|
|
160
|
+
];
|
|
161
|
+
const XY_POL = [
|
|
162
|
+
[5453.282155, 0.4252841, -0.00037173, -0.000000152],
|
|
163
|
+
[-73750.93035, -0.7675452, -0.00018725, 0.000000231],
|
|
164
|
+
];
|
|
165
|
+
const XY_PER = [
|
|
166
|
+
[256.75, -819.940624, 75004.344875, 81491.287984, 1558.515853],
|
|
167
|
+
[708.15, -8444.676815, 624.033993, 787.163481, 7774.939698],
|
|
168
|
+
[274.2, 2600.009459, 1251.136893, 1251.296102, -2219.534038],
|
|
169
|
+
[241.45, 2755.17563, -1102.212834, -1257.950837, -2523.969396],
|
|
170
|
+
[2309.0, -167.659835, -2660.66498, -2966.79973, 247.850422],
|
|
171
|
+
[492.2, 871.855056, 699.291817, 639.744522, -846.485643],
|
|
172
|
+
[396.1, 44.769698, 153.16722, 131.600209, -1393.124055],
|
|
173
|
+
[288.9, -512.313065, -950.865637, -445.040117, 368.526116],
|
|
174
|
+
[231.1, -819.415595, 499.754645, 584.522874, 749.045012],
|
|
175
|
+
[1610.0, -538.071099, -145.18821, -89.756563, 444.704518],
|
|
176
|
+
[620.0, -189.793622, 558.116553, 524.42963, 235.934465],
|
|
177
|
+
[157.87, -402.922932, -23.923029, -13.549067, 374.049623],
|
|
178
|
+
[220.3, 179.516345, -165.405086, -210.157124, -171.33018],
|
|
179
|
+
[1200.0, -9.814756, 9.344131, -44.919798, -22.899655],
|
|
180
|
+
];
|
|
181
|
+
const EPS0_V = 84381.406 * ARCSEC; // J2000 obliquity of the Vondrak model
|
|
182
|
+
const EPS0_FRAME = 84381.448 * ARCSEC; // obliquity defining our ecliptic-J2000 data
|
|
183
|
+
function ltpPecl(jde) {
|
|
184
|
+
const t = (jde - J2000) / 36525.0;
|
|
185
|
+
let p = 0.0;
|
|
186
|
+
let q = 0.0;
|
|
187
|
+
const w = 2.0 * Math.PI * t;
|
|
188
|
+
for (const [per, c1, c2, s1, s2] of PQ_PER) {
|
|
189
|
+
const a = w / per;
|
|
190
|
+
const ca = Math.cos(a);
|
|
191
|
+
const sa = Math.sin(a);
|
|
192
|
+
p += ca * c1 + sa * s1;
|
|
193
|
+
q += ca * c2 + sa * s2;
|
|
194
|
+
}
|
|
195
|
+
let tn = 1.0;
|
|
196
|
+
for (let i = 0; i < 4; i++) {
|
|
197
|
+
p += PQ_POL[0][i] * tn;
|
|
198
|
+
q += PQ_POL[1][i] * tn;
|
|
199
|
+
tn *= t;
|
|
200
|
+
}
|
|
201
|
+
p *= ARCSEC;
|
|
202
|
+
q *= ARCSEC;
|
|
203
|
+
const z = Math.sqrt(Math.max(1.0 - p * p - q * q, 0.0));
|
|
204
|
+
const s = Math.sin(EPS0_V);
|
|
205
|
+
const c = Math.cos(EPS0_V);
|
|
206
|
+
return [p, -q * c - z * s, -q * s + z * c];
|
|
207
|
+
}
|
|
208
|
+
function ltpPequ(jde) {
|
|
209
|
+
const t = (jde - J2000) / 36525.0;
|
|
210
|
+
let x = 0.0;
|
|
211
|
+
let y = 0.0;
|
|
212
|
+
const w = 2.0 * Math.PI * t;
|
|
213
|
+
for (const [per, c1, c2, s1, s2] of XY_PER) {
|
|
214
|
+
const a = w / per;
|
|
215
|
+
const ca = Math.cos(a);
|
|
216
|
+
const sa = Math.sin(a);
|
|
217
|
+
x += ca * c1 + sa * s1;
|
|
218
|
+
y += ca * c2 + sa * s2;
|
|
219
|
+
}
|
|
220
|
+
let tn = 1.0;
|
|
221
|
+
for (let i = 0; i < 4; i++) {
|
|
222
|
+
x += XY_POL[0][i] * tn;
|
|
223
|
+
y += XY_POL[1][i] * tn;
|
|
224
|
+
tn *= t;
|
|
225
|
+
}
|
|
226
|
+
x *= ARCSEC;
|
|
227
|
+
y *= ARCSEC;
|
|
228
|
+
return [x, y, Math.sqrt(Math.max(1.0 - x * x - y * y, 0.0))];
|
|
229
|
+
}
|
|
230
|
+
/** Rows of the rotation J2000-equatorial -> mean ecliptic/equinox of date
|
|
231
|
+
* (ERFA eraLtecm): x = equinox, z = ecliptic pole, y = z cross x. */
|
|
232
|
+
function ltpEclMatrix(jde) {
|
|
233
|
+
const p = ltpPequ(jde);
|
|
234
|
+
const z = ltpPecl(jde);
|
|
235
|
+
const wx = [
|
|
236
|
+
p[1] * z[2] - p[2] * z[1], p[2] * z[0] - p[0] * z[2], p[0] * z[1] - p[1] * z[0],
|
|
237
|
+
];
|
|
238
|
+
const n = Math.sqrt(wx[0] ** 2 + wx[1] ** 2 + wx[2] ** 2);
|
|
239
|
+
const x = [wx[0] / n, wx[1] / n, wx[2] / n];
|
|
240
|
+
const y = [
|
|
241
|
+
z[1] * x[2] - z[2] * x[1], z[2] * x[0] - z[0] * x[2], z[0] * x[1] - z[1] * x[0],
|
|
242
|
+
];
|
|
243
|
+
return [x, y, z];
|
|
244
|
+
}
|
|
245
|
+
/** Precession of ecliptic coordinates between epochs (Vondrak 2011):
|
|
246
|
+
* ecliptic-of-from -> J2000 equatorial -> ecliptic-of-to. */
|
|
144
247
|
export function precessEcliptic(lon, lat, jdeFrom, jdeTo) {
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
248
|
+
const cb = Math.cos(lat);
|
|
249
|
+
const v = [cb * Math.cos(lon), cb * Math.sin(lon), Math.sin(lat)];
|
|
250
|
+
const [xf, yf, zf] = ltpEclMatrix(jdeFrom);
|
|
251
|
+
const e = [0, 1, 2].map((i) => xf[i] * v[0] + yf[i] * v[1] + zf[i] * v[2]);
|
|
252
|
+
const [xt, yt, zt] = ltpEclMatrix(jdeTo);
|
|
253
|
+
const u = [
|
|
254
|
+
xt[0] * e[0] + xt[1] * e[1] + xt[2] * e[2],
|
|
255
|
+
yt[0] * e[0] + yt[1] * e[1] + yt[2] * e[2],
|
|
256
|
+
zt[0] * e[0] + zt[1] * e[1] + zt[2] * e[2],
|
|
257
|
+
];
|
|
258
|
+
return [mod(Math.atan2(u[1], u[0]), TWO_PI),
|
|
259
|
+
Math.asin(Math.max(-1, Math.min(1, u[2])))];
|
|
260
|
+
}
|
|
261
|
+
/** Rotate a vector from the ecliptic-J2000 data frame (obliquity 84381.448
|
|
262
|
+
* arcsec, as used by Horizons/Meeus) to the mean ecliptic of date
|
|
263
|
+
* (Vondrak 2011). */
|
|
161
264
|
function eclJ2000ToEclDate(v, jde) {
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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];
|
|
265
|
+
const [x, y, z] = v;
|
|
266
|
+
const s = Math.sin(EPS0_FRAME);
|
|
267
|
+
const c = Math.cos(EPS0_FRAME);
|
|
268
|
+
const e = [x, y * c - z * s, y * s + z * c];
|
|
269
|
+
const [xt, yt, zt] = ltpEclMatrix(jde);
|
|
270
|
+
return [
|
|
271
|
+
xt[0] * e[0] + xt[1] * e[1] + xt[2] * e[2],
|
|
272
|
+
yt[0] * e[0] + yt[1] * e[1] + yt[2] * e[2],
|
|
273
|
+
zt[0] * e[0] + zt[1] * e[1] + zt[2] * e[2],
|
|
274
|
+
];
|
|
185
275
|
}
|
|
186
276
|
// ---------------------------------------------------------------- planets
|
|
187
277
|
function geoVector(data, name, jde) {
|
|
@@ -385,9 +475,9 @@ export function equatorial(lon, lat, eps) {
|
|
|
385
475
|
return [ra, dec];
|
|
386
476
|
}
|
|
387
477
|
/** Mean ayanamsa at J2000.0 (degrees) per mode. Standard epoch anchors
|
|
388
|
-
* (matched to Swiss Ephemeris 2.10 to 1e-9 deg); propagation uses
|
|
389
|
-
* ecliptic precession
|
|
390
|
-
*
|
|
478
|
+
* (matched to Swiss Ephemeris 2.10 to 1e-9 deg); propagation uses Vondrak
|
|
479
|
+
* 2011 ecliptic precession, the same model Swiss Ephemeris uses:
|
|
480
|
+
* agreement over 1900-2099 is <=0.005 arcsec. */
|
|
391
481
|
export const AYANAMSA_J2000 = {
|
|
392
482
|
lahiri: 23.857092325,
|
|
393
483
|
fagan_bradley: 24.740299966,
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
2
|
+
export declare const QUERY_ASPECTS: Record<string, number>;
|
|
3
|
+
export type Interval = [number, number];
|
|
4
|
+
/** Margin function (true where >= 0) carrying the bodies it depends on. */
|
|
5
|
+
export interface Predicate {
|
|
6
|
+
(engine: Engine, t: number): number;
|
|
7
|
+
bodies: Set<string>;
|
|
8
|
+
}
|
|
9
|
+
/** True while `body` is within `orb` deg of an exact `kind` aspect to
|
|
10
|
+
* `target` -- a fixed ecliptic longitude (deg) or another body name. */
|
|
11
|
+
export declare function aspect(body: BodyId, kind: string, target: number | BodyId, orb?: number, zodiac?: Zodiac): Predicate;
|
|
12
|
+
/** True while `body` is in `sign` (index 0=Aries..11=Pisces, or name). */
|
|
13
|
+
export declare function inSign(body: BodyId, sign: number | string, zodiac?: Zodiac): Predicate;
|
|
14
|
+
/** True while `body` is in apparent retrograde motion. */
|
|
15
|
+
export declare function retrograde(body: BodyId, zodiac?: Zodiac): Predicate;
|
|
16
|
+
/** True while `body` is direct or stationary. */
|
|
17
|
+
export declare function notRetrograde(body: BodyId, zodiac?: Zodiac): Predicate;
|
|
18
|
+
/** True where every predicate is true (interval intersection). */
|
|
19
|
+
export declare function allOf(...preds: Predicate[]): Predicate;
|
|
20
|
+
/** True where any predicate is true (interval union). */
|
|
21
|
+
export declare function anyOf(...preds: Predicate[]): Predicate;
|
|
22
|
+
/** True where `pred` is false (interval complement). */
|
|
23
|
+
export declare function notOf(pred: Predicate): Predicate;
|
|
24
|
+
export interface WhenOptions {
|
|
25
|
+
step?: number;
|
|
26
|
+
maxIntervals?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Time intervals (jdStartUt, jdEndUt) in [jdStart, jdEnd] where `predicate`
|
|
29
|
+
* is true. Endpoints touching the range bounds are clamped. The scan step
|
|
30
|
+
* defaults to 0.125 d when a fast body (Moon, nodes, Lilith) is involved
|
|
31
|
+
* and 1 d otherwise. */
|
|
32
|
+
export declare function when(engine: Engine, predicate: Predicate, jdStart: number, jdEnd: number, opts?: WhenOptions): Interval[];
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine query -- declarative time queries ("when is ...?").
|
|
3
|
+
*
|
|
4
|
+
* The engine answers "where is the body?"; this answers "when is the
|
|
5
|
+
* configuration true?" over a time range. A predicate is a continuous
|
|
6
|
+
* "margin" function, true exactly where margin >= 0 (e.g. aspect-within-orb
|
|
7
|
+
* -> orb minus angular distance from exact). A boolean combination is then
|
|
8
|
+
* itself a margin -- AND = min of the parts, OR = max, NOT = negation -- so
|
|
9
|
+
* any query reduces to one continuous function and `when()` returns the
|
|
10
|
+
* intervals where it is true using the same coarse-scan-then-bisect root
|
|
11
|
+
* finder as events.crossings.
|
|
12
|
+
*
|
|
13
|
+
* when(engine, allOf(aspect("saturn", "square", natalMoon),
|
|
14
|
+
* notRetrograde("mercury"),
|
|
15
|
+
* inSign("venus", "Taurus")), jdStart, jdEnd)
|
|
16
|
+
*
|
|
17
|
+
* Mirrors the Python reference (astroengine/query.py); the golden fixtures
|
|
18
|
+
* pin the two implementations together.
|
|
19
|
+
*/
|
|
20
|
+
import { mod } from "./core.js";
|
|
21
|
+
import { SIGNS } from "./chart.js";
|
|
22
|
+
export const QUERY_ASPECTS = {
|
|
23
|
+
conjunction: 0, semisextile: 30, sextile: 60, square: 90,
|
|
24
|
+
trine: 120, quincunx: 150, opposition: 180,
|
|
25
|
+
};
|
|
26
|
+
const FAST = new Set([
|
|
27
|
+
"moon", "mean_node", "true_node", "mean_lilith", "true_lilith",
|
|
28
|
+
]);
|
|
29
|
+
function wrap180(d) {
|
|
30
|
+
return mod(d + 180, 360) - 180;
|
|
31
|
+
}
|
|
32
|
+
function mk(fn, bodies) {
|
|
33
|
+
const p = fn;
|
|
34
|
+
p.bodies = bodies;
|
|
35
|
+
return p;
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------- predicates
|
|
38
|
+
/** True while `body` is within `orb` deg of an exact `kind` aspect to
|
|
39
|
+
* `target` -- a fixed ecliptic longitude (deg) or another body name. */
|
|
40
|
+
export function aspect(body, kind, target, orb = 1.0, zodiac = "tropical") {
|
|
41
|
+
const ang = QUERY_ASPECTS[kind];
|
|
42
|
+
if (ang === undefined)
|
|
43
|
+
throw new Error(`unknown aspect ${kind}`);
|
|
44
|
+
const isLon = typeof target === "number";
|
|
45
|
+
const bodies = new Set([body]);
|
|
46
|
+
if (!isLon)
|
|
47
|
+
bodies.add(target);
|
|
48
|
+
return mk((engine, t) => {
|
|
49
|
+
const lon = engine.longitude(body, t, { zodiac });
|
|
50
|
+
const tl = isLon
|
|
51
|
+
? target
|
|
52
|
+
: engine.longitude(target, t, { zodiac });
|
|
53
|
+
const sep = lon - tl;
|
|
54
|
+
return orb - Math.min(Math.abs(wrap180(sep - ang)), Math.abs(wrap180(sep + ang)));
|
|
55
|
+
}, bodies);
|
|
56
|
+
}
|
|
57
|
+
/** True while `body` is in `sign` (index 0=Aries..11=Pisces, or name). */
|
|
58
|
+
export function inSign(body, sign, zodiac = "tropical") {
|
|
59
|
+
const idx = typeof sign === "number" ? sign : SIGNS.indexOf(sign);
|
|
60
|
+
if (idx < 0)
|
|
61
|
+
throw new Error(`unknown sign ${sign}`);
|
|
62
|
+
const lo = idx * 30;
|
|
63
|
+
return mk((engine, t) => {
|
|
64
|
+
const d = mod(engine.longitude(body, t, { zodiac }) - lo, 360);
|
|
65
|
+
// signed distance to the nearest 30-deg band edge, positive inside
|
|
66
|
+
return d <= 30 ? Math.min(d, 30 - d) : -Math.min(d - 30, 360 - d);
|
|
67
|
+
}, new Set([body]));
|
|
68
|
+
}
|
|
69
|
+
/** True while `body` is in apparent retrograde motion. */
|
|
70
|
+
export function retrograde(body, zodiac = "tropical") {
|
|
71
|
+
const h = 0.25;
|
|
72
|
+
return mk((engine, t) => {
|
|
73
|
+
const l0 = engine.longitude(body, t - h, { zodiac });
|
|
74
|
+
const l1 = engine.longitude(body, t + h, { zodiac });
|
|
75
|
+
return -wrap180(l1 - l0) / (2 * h); // >= 0 when moving backwards
|
|
76
|
+
}, new Set([body]));
|
|
77
|
+
}
|
|
78
|
+
/** True while `body` is direct or stationary. */
|
|
79
|
+
export function notRetrograde(body, zodiac = "tropical") {
|
|
80
|
+
return notOf(retrograde(body, zodiac));
|
|
81
|
+
}
|
|
82
|
+
// --------------------------------------------------------------- combinators
|
|
83
|
+
function combine(op, preds) {
|
|
84
|
+
const bodies = new Set();
|
|
85
|
+
for (const p of preds)
|
|
86
|
+
for (const b of p.bodies)
|
|
87
|
+
bodies.add(b);
|
|
88
|
+
return mk((engine, t) => op(preds.map((p) => p(engine, t))), bodies);
|
|
89
|
+
}
|
|
90
|
+
/** True where every predicate is true (interval intersection). */
|
|
91
|
+
export function allOf(...preds) {
|
|
92
|
+
return combine((xs) => Math.min(...xs), preds);
|
|
93
|
+
}
|
|
94
|
+
/** True where any predicate is true (interval union). */
|
|
95
|
+
export function anyOf(...preds) {
|
|
96
|
+
return combine((xs) => Math.max(...xs), preds);
|
|
97
|
+
}
|
|
98
|
+
/** True where `pred` is false (interval complement). */
|
|
99
|
+
export function notOf(pred) {
|
|
100
|
+
return mk((engine, t) => -pred(engine, t), new Set(pred.bodies));
|
|
101
|
+
}
|
|
102
|
+
// --------------------------------------------------------------- solver
|
|
103
|
+
function bisect(f, a, b, tol = 1e-6) {
|
|
104
|
+
let fa = f(a);
|
|
105
|
+
for (let i = 0; i < 60; i++) {
|
|
106
|
+
const m = 0.5 * (a + b);
|
|
107
|
+
if (Math.abs(b - a) < tol)
|
|
108
|
+
return m;
|
|
109
|
+
const fm = f(m);
|
|
110
|
+
if ((fa < 0) !== (fm < 0)) {
|
|
111
|
+
b = m;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
a = m;
|
|
115
|
+
fa = fm;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return 0.5 * (a + b);
|
|
119
|
+
}
|
|
120
|
+
/** Time intervals (jdStartUt, jdEndUt) in [jdStart, jdEnd] where `predicate`
|
|
121
|
+
* is true. Endpoints touching the range bounds are clamped. The scan step
|
|
122
|
+
* defaults to 0.125 d when a fast body (Moon, nodes, Lilith) is involved
|
|
123
|
+
* and 1 d otherwise. */
|
|
124
|
+
export function when(engine, predicate, jdStart, jdEnd, opts = {}) {
|
|
125
|
+
let step = opts.step;
|
|
126
|
+
if (step === undefined) {
|
|
127
|
+
let fast = false;
|
|
128
|
+
for (const b of predicate.bodies)
|
|
129
|
+
if (FAST.has(b))
|
|
130
|
+
fast = true;
|
|
131
|
+
step = fast ? 0.125 : 1.0;
|
|
132
|
+
}
|
|
133
|
+
const maxIntervals = opts.maxIntervals ?? 500;
|
|
134
|
+
const f = (t) => predicate(engine, t);
|
|
135
|
+
const intervals = [];
|
|
136
|
+
let prev = f(jdStart);
|
|
137
|
+
let openStart = prev >= 0 ? jdStart : null;
|
|
138
|
+
let t = jdStart + step;
|
|
139
|
+
while (t <= jdEnd + 1e-9 && intervals.length < maxIntervals) {
|
|
140
|
+
if (t > jdEnd)
|
|
141
|
+
t = jdEnd;
|
|
142
|
+
const cur = f(t);
|
|
143
|
+
if ((prev < 0) !== (cur < 0)) {
|
|
144
|
+
const edge = bisect(f, t - step, t);
|
|
145
|
+
if (cur >= 0) {
|
|
146
|
+
openStart = edge;
|
|
147
|
+
}
|
|
148
|
+
else if (openStart !== null) {
|
|
149
|
+
intervals.push([openStart, edge]);
|
|
150
|
+
openStart = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
prev = cur;
|
|
154
|
+
if (t >= jdEnd)
|
|
155
|
+
break;
|
|
156
|
+
t += step;
|
|
157
|
+
}
|
|
158
|
+
if (openStart !== null)
|
|
159
|
+
intervals.push([openStart, jdEnd]);
|
|
160
|
+
return intervals;
|
|
161
|
+
}
|
package/dist/src/stars.d.ts
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* catalog (data/fixed_stars.json; ICRS J2000 with proper motions).
|
|
4
4
|
*
|
|
5
5
|
* Chain: full 3D space motion (proper motion + radial velocity at the
|
|
6
|
-
* parallax distance) -> ICRS equatorial -> ecliptic J2000 ->
|
|
6
|
+
* parallax distance) -> ICRS equatorial -> ecliptic J2000 -> Vondrak 2011
|
|
7
7
|
* precession to date -> annual aberration (classic elliptic form, as for
|
|
8
8
|
* Pluto/Chiron) -> nutation. Validated against swe_fixstar fed the same
|
|
9
|
-
* catalog rows: <=0.
|
|
10
|
-
* Vondrak precession difference, shared with the rest of the engine).
|
|
9
|
+
* catalog rows: <=0.3 arcsec over 1900-2099.
|
|
11
10
|
*/
|
|
12
11
|
import { EngineData } from "./core.js";
|
|
13
12
|
export interface StarEntry {
|
package/dist/src/stars.js
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* catalog (data/fixed_stars.json; ICRS J2000 with proper motions).
|
|
4
4
|
*
|
|
5
5
|
* Chain: full 3D space motion (proper motion + radial velocity at the
|
|
6
|
-
* parallax distance) -> ICRS equatorial -> ecliptic J2000 ->
|
|
6
|
+
* parallax distance) -> ICRS equatorial -> ecliptic J2000 -> Vondrak 2011
|
|
7
7
|
* precession to date -> annual aberration (classic elliptic form, as for
|
|
8
8
|
* Pluto/Chiron) -> nutation. Validated against swe_fixstar fed the same
|
|
9
|
-
* catalog rows: <=0.
|
|
10
|
-
* Vondrak precession difference, shared with the rest of the engine).
|
|
9
|
+
* catalog rows: <=0.3 arcsec over 1900-2099.
|
|
11
10
|
*/
|
|
12
11
|
import { DEG, ARCSEC, J2000, mod, nutation, precessEcliptic, vsopHeliocentric, } from "./core.js";
|
|
13
12
|
const TWO_PI = 2 * Math.PI;
|