@saber-usa/node-common 1.7.18-alpha.1 → 1.7.19
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/package.json +2 -2
- package/src/wasmProp/index.js +10 -10
- package/src/wasmProp/primitives.js +295 -295
- package/src/wasmProp/runtime.js +147 -147
- package/src/wasmProp/wasmAstro.js +251 -251
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber-usa/node-common",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.19",
|
|
4
4
|
"description": "Common node functions for Saber",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"mathjs": "^15.2.0",
|
|
30
30
|
"pious-squid": "^2.3.0",
|
|
31
31
|
"plotly": "^1.0.6",
|
|
32
|
-
"satellite.js": "^7.0.
|
|
32
|
+
"satellite.js": "^7.0.1",
|
|
33
33
|
"solar-calculator": "^0.3.0",
|
|
34
34
|
"three": "^0.184",
|
|
35
35
|
"winston": "3.19.0"
|
package/src/wasmProp/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Barrel for all WASM-backed bulk propagation exports. Kept behind a
|
|
2
|
-
// single entry point so the rest of the codebase (and external consumers
|
|
3
|
-
// via `@saber-usa/node-common`) can discover the bulk API without having
|
|
4
|
-
// to reach into per-feature files.
|
|
5
|
-
//
|
|
6
|
-
// See `docs/BULK_PROPAGATION.md` for the design rationale, lifecycle, and
|
|
7
|
-
// the measured performance characteristics.
|
|
8
|
-
export * from "./runtime.js";
|
|
9
|
-
export * from "./primitives.js";
|
|
10
|
-
export * from "./wasmAstro.js";
|
|
1
|
+
// Barrel for all WASM-backed bulk propagation exports. Kept behind a
|
|
2
|
+
// single entry point so the rest of the codebase (and external consumers
|
|
3
|
+
// via `@saber-usa/node-common`) can discover the bulk API without having
|
|
4
|
+
// to reach into per-feature files.
|
|
5
|
+
//
|
|
6
|
+
// See `docs/BULK_PROPAGATION.md` for the design rationale, lifecycle, and
|
|
7
|
+
// the measured performance characteristics.
|
|
8
|
+
export * from "./runtime.js";
|
|
9
|
+
export * from "./primitives.js";
|
|
10
|
+
export * from "./wasmAstro.js";
|
|
@@ -1,295 +1,295 @@
|
|
|
1
|
-
import {
|
|
2
|
-
twoline2satrec,
|
|
3
|
-
EciBaseCalculator,
|
|
4
|
-
GmstCalculator,
|
|
5
|
-
EcfPositionCalculator,
|
|
6
|
-
GeodeticPositionCalculator,
|
|
7
|
-
LookAnglesCalculator,
|
|
8
|
-
} from "satellite.js";
|
|
9
|
-
import {getBulkRuntime, getOrCreateBulkPropagator} from "./runtime.js";
|
|
10
|
-
import {RAD2DEG} from "../constants.js";
|
|
11
|
-
|
|
12
|
-
// ----------------------------------------------------------------------------
|
|
13
|
-
// WASM-backed bulk primitives
|
|
14
|
-
//
|
|
15
|
-
// Sibling functions to `prop` / `propGeodetic` / the manual LookAngles chain
|
|
16
|
-
// that live in `../astro.js`. The originals stay synchronous; these are
|
|
17
|
-
// async because the per-thread `BulkPropagator` runtime is async-bootstrapped
|
|
18
|
-
// (see `docs/BULK_PROPAGATION.md`).
|
|
19
|
-
//
|
|
20
|
-
// Time-grid semantics are matched exactly to the JS-path siblings to keep
|
|
21
|
-
// parity tests trivial:
|
|
22
|
-
// - `propBulk` mirrors `prop` -> half-open [start, end)
|
|
23
|
-
// - `propGeodeticBulk` mirrors `propGeodetic` -> closed [start, end]
|
|
24
|
-
//
|
|
25
|
-
// Return shapes mirror the JS-path siblings element-wise so callers can swap
|
|
26
|
-
// transparently. `meanElements` is intentionally absent from `propBulk`
|
|
27
|
-
// records: the WASM `EciBaseCalculator` exposes only position/velocity/error,
|
|
28
|
-
// it does not surface SGP4 mean elements. Callers that need them must either
|
|
29
|
-
// stay on `prop` or recompute osculating elements from p/v.
|
|
30
|
-
// ----------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Builds the closed list of evaluation timestamps for a half-open or closed
|
|
34
|
-
* interval, matching the loop semantics of `prop` / `propGeodetic`.
|
|
35
|
-
*
|
|
36
|
-
* @param {number} start
|
|
37
|
-
* @param {number} end
|
|
38
|
-
* @param {number} stepMs
|
|
39
|
-
* @param {boolean} inclusive when true, end is included (propGeodetic).
|
|
40
|
-
* @return {number[]}
|
|
41
|
-
*/
|
|
42
|
-
const buildTimeGrid = (start, end, stepMs, inclusive) => {
|
|
43
|
-
const grid = [];
|
|
44
|
-
if (inclusive) {
|
|
45
|
-
for (let t = start; t <= end; t = t + stepMs) {grid.push(t);}
|
|
46
|
-
} else {
|
|
47
|
-
for (let t = start; t < end; t = t + stepMs) {grid.push(t);}
|
|
48
|
-
}
|
|
49
|
-
return grid;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Bulk-converts an array of `{Line1, Line2}` elsets into satrecs, returning
|
|
54
|
-
* `null` if any single elset fails to convert. Mirrors the silent-skip
|
|
55
|
-
* behavior of `prop` (which returns `[]` on the first invalid pv).
|
|
56
|
-
*
|
|
57
|
-
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
58
|
-
* @return {Array|null}
|
|
59
|
-
*/
|
|
60
|
-
const elsetsToSatrecs = (elsets) => {
|
|
61
|
-
const satrecs = elsets.map((e) => twoline2satrec(e.Line1, e.Line2));
|
|
62
|
-
return satrecs;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* WASM-backed sibling of `prop`. Computes ECI position and velocity for
|
|
67
|
-
* `elsets.length` satellites at every step in the half-open interval
|
|
68
|
-
* `[start, end)` with step `stepMs`.
|
|
69
|
-
*
|
|
70
|
-
* Returns `Array<Array<{p, v, t}>>` indexed `[satIdx][dateIdx]`. Each inner
|
|
71
|
-
* array matches the shape returned by `prop` minus the `meanElements` field
|
|
72
|
-
* (see module-level note). If the WASM SGP4 produces a non-zero error code
|
|
73
|
-
* for any (sat, date) pair, that satellite's entire ephemeris is returned as
|
|
74
|
-
* `[]` — same all-or-nothing semantic as `prop` on `propTo` failure.
|
|
75
|
-
*
|
|
76
|
-
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
77
|
-
* @param {number} start UNIX milliseconds
|
|
78
|
-
* @param {number} end UNIX milliseconds (exclusive)
|
|
79
|
-
* @param {number} stepMs default 1000
|
|
80
|
-
* @return {Promise<Array<Array<{p: object, v: object, t: number}>>>}
|
|
81
|
-
*/
|
|
82
|
-
const propBulk = async (elsets, start, end, stepMs = 1000) => {
|
|
83
|
-
const grid = buildTimeGrid(start, end, stepMs, false);
|
|
84
|
-
if (elsets.length === 0 || grid.length === 0) {
|
|
85
|
-
return elsets.map(() => []);
|
|
86
|
-
}
|
|
87
|
-
const satrecs = elsetsToSatrecs(elsets);
|
|
88
|
-
const dates = grid.map((t) => new Date(t));
|
|
89
|
-
|
|
90
|
-
const runtime = await getBulkRuntime();
|
|
91
|
-
const propagator = getOrCreateBulkPropagator({
|
|
92
|
-
runtime,
|
|
93
|
-
key: "eci",
|
|
94
|
-
makeCalculators: () => [new EciBaseCalculator()],
|
|
95
|
-
satRecsCount: satrecs.length,
|
|
96
|
-
datesCount: dates.length,
|
|
97
|
-
});
|
|
98
|
-
propagator.setSatRecs(satrecs);
|
|
99
|
-
propagator.setDates(dates);
|
|
100
|
-
propagator.run();
|
|
101
|
-
|
|
102
|
-
const {eci} = propagator.getRawOutput();
|
|
103
|
-
// raw layout: [sat0 date0 (x,y,z), sat0 date1 (x,y,z), ..., sat1 date0, ...]
|
|
104
|
-
// error layout: [sat0 date0, sat0 date1, ..., sat1 date0, ...]
|
|
105
|
-
const out = new Array(satrecs.length);
|
|
106
|
-
const m = dates.length;
|
|
107
|
-
for (let s = 0; s < satrecs.length; s++) {
|
|
108
|
-
const ephem = new Array(m);
|
|
109
|
-
let satFailed = false;
|
|
110
|
-
for (let d = 0; d < m; d++) {
|
|
111
|
-
const errIdx = s * m + d;
|
|
112
|
-
if (eci.error[errIdx] !== 0) {
|
|
113
|
-
satFailed = true;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
const vec = (s * m + d) * 3;
|
|
117
|
-
ephem[d] = {
|
|
118
|
-
p: {
|
|
119
|
-
x: eci.position[vec],
|
|
120
|
-
y: eci.position[vec + 1],
|
|
121
|
-
z: eci.position[vec + 2],
|
|
122
|
-
},
|
|
123
|
-
v: {
|
|
124
|
-
x: eci.velocity[vec],
|
|
125
|
-
y: eci.velocity[vec + 1],
|
|
126
|
-
z: eci.velocity[vec + 2],
|
|
127
|
-
},
|
|
128
|
-
t: grid[d],
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
out[s] = satFailed ? [] : ephem;
|
|
132
|
-
}
|
|
133
|
-
return out;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* WASM-backed sibling of `propGeodetic`. Computes geodetic
|
|
138
|
-
* (lat, lon, t) for every satellite at every step in the closed interval
|
|
139
|
-
* `[start, end]`.
|
|
140
|
-
*
|
|
141
|
-
* Lat/Lon are returned in **degrees** to match `propGeodetic`. Returns
|
|
142
|
-
* `Array<Array<{lat, lon, t}>>` indexed `[satIdx][dateIdx]`. On any SGP4
|
|
143
|
-
* error for a satellite the whole ephemeris for that satellite is `[]`,
|
|
144
|
-
* matching `propGeodetic`'s short-circuit behavior.
|
|
145
|
-
*
|
|
146
|
-
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
147
|
-
* @param {number} start UNIX milliseconds
|
|
148
|
-
* @param {number} end UNIX milliseconds (inclusive)
|
|
149
|
-
* @param {number} stepMs default 60000
|
|
150
|
-
* @return {Promise<Array<Array<{lat: number, lon: number, t: number}>>>}
|
|
151
|
-
*/
|
|
152
|
-
const propGeodeticBulk = async (elsets, start, end, stepMs = 60000) => {
|
|
153
|
-
const grid = buildTimeGrid(start, end, stepMs, true);
|
|
154
|
-
if (elsets.length === 0 || grid.length === 0) {
|
|
155
|
-
return elsets.map(() => []);
|
|
156
|
-
}
|
|
157
|
-
const satrecs = elsetsToSatrecs(elsets);
|
|
158
|
-
const dates = grid.map((t) => new Date(t));
|
|
159
|
-
|
|
160
|
-
const runtime = await getBulkRuntime();
|
|
161
|
-
const propagator = getOrCreateBulkPropagator({
|
|
162
|
-
runtime,
|
|
163
|
-
key: "eci+gmst+geo",
|
|
164
|
-
makeCalculators: () => [
|
|
165
|
-
new EciBaseCalculator(),
|
|
166
|
-
new GmstCalculator(),
|
|
167
|
-
new GeodeticPositionCalculator(),
|
|
168
|
-
],
|
|
169
|
-
satRecsCount: satrecs.length,
|
|
170
|
-
datesCount: dates.length,
|
|
171
|
-
});
|
|
172
|
-
propagator.setSatRecs(satrecs);
|
|
173
|
-
propagator.setDates(dates);
|
|
174
|
-
propagator.run();
|
|
175
|
-
|
|
176
|
-
const {eci, geodeticPosition} = propagator.getRawOutput();
|
|
177
|
-
// KNOWN satellite.js@7.0.0 BUG / docs error: the
|
|
178
|
-
// `GeodeticPositionCalculator` raw layout is documented as
|
|
179
|
-
// `[lat, lon, height, ...]`, and `getFormattedOutput()` exposes fields
|
|
180
|
-
// `{latitude: raw[0], longitude: raw[1], height: raw[2]}` — but the
|
|
181
|
-
// physical values are SWAPPED. Empirically, against the canonical JS
|
|
182
|
-
// `eciToGeodetic` path:
|
|
183
|
-
// raw[3*k + 0] -> physical LONGITUDE (in radians)
|
|
184
|
-
// raw[3*k + 1] -> physical LATITUDE (in radians)
|
|
185
|
-
// raw[3*k + 2] -> height (km)
|
|
186
|
-
// We map raw -> {lat, lon} accordingly so `propGeodeticBulk` is exactly
|
|
187
|
-
// value-equivalent to `propGeodetic`. If a future satellite.js release
|
|
188
|
-
// fixes this upstream, swap the indices back here.
|
|
189
|
-
const out = new Array(satrecs.length);
|
|
190
|
-
const m = dates.length;
|
|
191
|
-
for (let s = 0; s < satrecs.length; s++) {
|
|
192
|
-
const positions = new Array(m);
|
|
193
|
-
let satFailed = false;
|
|
194
|
-
for (let d = 0; d < m; d++) {
|
|
195
|
-
const errIdx = s * m + d;
|
|
196
|
-
if (eci.error[errIdx] !== 0) {
|
|
197
|
-
satFailed = true;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
const vec = (s * m + d) * 3;
|
|
201
|
-
positions[d] = {
|
|
202
|
-
lat: geodeticPosition[vec + 1] * RAD2DEG,
|
|
203
|
-
lon: geodeticPosition[vec] * RAD2DEG,
|
|
204
|
-
t: grid[d],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
out[s] = satFailed ? [] : positions;
|
|
208
|
-
}
|
|
209
|
-
return out;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* WASM-backed bulk look-angles primitive. Computes (azimuth, elevation,
|
|
214
|
-
* rangeSat) for every satellite at every supplied date relative to a single
|
|
215
|
-
* observer (Geodetic location, **radians + km** as required by satellite.js
|
|
216
|
-
* v7 — `{latitude, longitude, height}`).
|
|
217
|
-
*
|
|
218
|
-
* Unlike `propBulk` and `propGeodeticBulk` this primitive takes an explicit
|
|
219
|
-
* `dates` array (rather than start/end/step) because look-angles consumers
|
|
220
|
-
* typically already hold the date list (e.g. observation timestamps in
|
|
221
|
-
* `GetResiduals`) and synthesizing a regular grid would re-impose JS-side
|
|
222
|
-
* step math the WASM pipeline is meant to avoid.
|
|
223
|
-
*
|
|
224
|
-
* Returns `Array<Array<{azimuth, elevation, rangeSat, t}>>` indexed
|
|
225
|
-
* `[satIdx][dateIdx]`, with angles in **radians** and `rangeSat` in km. On
|
|
226
|
-
* any SGP4 error for a satellite that satellite's entire output is `[]`.
|
|
227
|
-
*
|
|
228
|
-
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
229
|
-
* @param {Date[]|number[]} dates
|
|
230
|
-
* @param {{latitude: number, longitude: number, height: number}} observerGd
|
|
231
|
-
* Observer position in radians (lat/lon) and km (height).
|
|
232
|
-
* @return {Promise<Array<Array<{azimuth: number, elevation: number, rangeSat: number, t: number}>>>}
|
|
233
|
-
*/
|
|
234
|
-
const propLookAnglesBulk = async (elsets, dates, observerGd) => {
|
|
235
|
-
if (elsets.length === 0 || dates.length === 0) {
|
|
236
|
-
return elsets.map(() => []);
|
|
237
|
-
}
|
|
238
|
-
const satrecs = elsetsToSatrecs(elsets);
|
|
239
|
-
const dateObjs = dates.map((d) => (d instanceof Date ? d : new Date(d)));
|
|
240
|
-
const ts = dateObjs.map((d) => d.getTime());
|
|
241
|
-
|
|
242
|
-
const runtime = await getBulkRuntime();
|
|
243
|
-
const propagator = getOrCreateBulkPropagator({
|
|
244
|
-
runtime,
|
|
245
|
-
key: "eci+gmst+ecf+lookAngles",
|
|
246
|
-
makeCalculators: () => [
|
|
247
|
-
new EciBaseCalculator(),
|
|
248
|
-
new GmstCalculator(),
|
|
249
|
-
new EcfPositionCalculator(),
|
|
250
|
-
new LookAnglesCalculator(),
|
|
251
|
-
],
|
|
252
|
-
satRecsCount: satrecs.length,
|
|
253
|
-
datesCount: dateObjs.length,
|
|
254
|
-
});
|
|
255
|
-
propagator.setSatRecs(satrecs);
|
|
256
|
-
propagator.setDates(dateObjs);
|
|
257
|
-
propagator.run({lookAngles: {observer: observerGd}});
|
|
258
|
-
|
|
259
|
-
const {eci, lookAngles} = propagator.getRawOutput();
|
|
260
|
-
// lookAngles layout: [sat0 date0 (az, el, range), sat0 date1, ...]
|
|
261
|
-
const out = new Array(satrecs.length);
|
|
262
|
-
const m = dateObjs.length;
|
|
263
|
-
for (let s = 0; s < satrecs.length; s++) {
|
|
264
|
-
const samples = new Array(m);
|
|
265
|
-
let satFailed = false;
|
|
266
|
-
for (let d = 0; d < m; d++) {
|
|
267
|
-
const errIdx = s * m + d;
|
|
268
|
-
if (eci.error[errIdx] !== 0) {
|
|
269
|
-
satFailed = true;
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
const vec = (s * m + d) * 3;
|
|
273
|
-
samples[d] = {
|
|
274
|
-
azimuth: lookAngles[vec],
|
|
275
|
-
elevation: lookAngles[vec + 1],
|
|
276
|
-
rangeSat: lookAngles[vec + 2],
|
|
277
|
-
t: ts[d],
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
out[s] = satFailed ? [] : samples;
|
|
281
|
-
}
|
|
282
|
-
return out;
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
export {
|
|
286
|
-
// `buildTimeGrid` is intentionally NOT exported: it is consumed only by
|
|
287
|
-
// `propBulk` / `propGeodeticBulk` inside this module. Keeping it private
|
|
288
|
-
// avoids polluting the public `@saber-usa/node-common` surface with a
|
|
289
|
-
// helper that exists purely to bridge the JS-path loop semantics to the
|
|
290
|
-
// WASM `setDates(Date[])` API.
|
|
291
|
-
elsetsToSatrecs, // used by `./wasmAstro.js#getGeoShadowZonesBulk`
|
|
292
|
-
propBulk,
|
|
293
|
-
propGeodeticBulk,
|
|
294
|
-
propLookAnglesBulk,
|
|
295
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
twoline2satrec,
|
|
3
|
+
EciBaseCalculator,
|
|
4
|
+
GmstCalculator,
|
|
5
|
+
EcfPositionCalculator,
|
|
6
|
+
GeodeticPositionCalculator,
|
|
7
|
+
LookAnglesCalculator,
|
|
8
|
+
} from "satellite.js";
|
|
9
|
+
import {getBulkRuntime, getOrCreateBulkPropagator} from "./runtime.js";
|
|
10
|
+
import {RAD2DEG} from "../constants.js";
|
|
11
|
+
|
|
12
|
+
// ----------------------------------------------------------------------------
|
|
13
|
+
// WASM-backed bulk primitives
|
|
14
|
+
//
|
|
15
|
+
// Sibling functions to `prop` / `propGeodetic` / the manual LookAngles chain
|
|
16
|
+
// that live in `../astro.js`. The originals stay synchronous; these are
|
|
17
|
+
// async because the per-thread `BulkPropagator` runtime is async-bootstrapped
|
|
18
|
+
// (see `docs/BULK_PROPAGATION.md`).
|
|
19
|
+
//
|
|
20
|
+
// Time-grid semantics are matched exactly to the JS-path siblings to keep
|
|
21
|
+
// parity tests trivial:
|
|
22
|
+
// - `propBulk` mirrors `prop` -> half-open [start, end)
|
|
23
|
+
// - `propGeodeticBulk` mirrors `propGeodetic` -> closed [start, end]
|
|
24
|
+
//
|
|
25
|
+
// Return shapes mirror the JS-path siblings element-wise so callers can swap
|
|
26
|
+
// transparently. `meanElements` is intentionally absent from `propBulk`
|
|
27
|
+
// records: the WASM `EciBaseCalculator` exposes only position/velocity/error,
|
|
28
|
+
// it does not surface SGP4 mean elements. Callers that need them must either
|
|
29
|
+
// stay on `prop` or recompute osculating elements from p/v.
|
|
30
|
+
// ----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Builds the closed list of evaluation timestamps for a half-open or closed
|
|
34
|
+
* interval, matching the loop semantics of `prop` / `propGeodetic`.
|
|
35
|
+
*
|
|
36
|
+
* @param {number} start
|
|
37
|
+
* @param {number} end
|
|
38
|
+
* @param {number} stepMs
|
|
39
|
+
* @param {boolean} inclusive when true, end is included (propGeodetic).
|
|
40
|
+
* @return {number[]}
|
|
41
|
+
*/
|
|
42
|
+
const buildTimeGrid = (start, end, stepMs, inclusive) => {
|
|
43
|
+
const grid = [];
|
|
44
|
+
if (inclusive) {
|
|
45
|
+
for (let t = start; t <= end; t = t + stepMs) {grid.push(t);}
|
|
46
|
+
} else {
|
|
47
|
+
for (let t = start; t < end; t = t + stepMs) {grid.push(t);}
|
|
48
|
+
}
|
|
49
|
+
return grid;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Bulk-converts an array of `{Line1, Line2}` elsets into satrecs, returning
|
|
54
|
+
* `null` if any single elset fails to convert. Mirrors the silent-skip
|
|
55
|
+
* behavior of `prop` (which returns `[]` on the first invalid pv).
|
|
56
|
+
*
|
|
57
|
+
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
58
|
+
* @return {Array|null}
|
|
59
|
+
*/
|
|
60
|
+
const elsetsToSatrecs = (elsets) => {
|
|
61
|
+
const satrecs = elsets.map((e) => twoline2satrec(e.Line1, e.Line2));
|
|
62
|
+
return satrecs;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* WASM-backed sibling of `prop`. Computes ECI position and velocity for
|
|
67
|
+
* `elsets.length` satellites at every step in the half-open interval
|
|
68
|
+
* `[start, end)` with step `stepMs`.
|
|
69
|
+
*
|
|
70
|
+
* Returns `Array<Array<{p, v, t}>>` indexed `[satIdx][dateIdx]`. Each inner
|
|
71
|
+
* array matches the shape returned by `prop` minus the `meanElements` field
|
|
72
|
+
* (see module-level note). If the WASM SGP4 produces a non-zero error code
|
|
73
|
+
* for any (sat, date) pair, that satellite's entire ephemeris is returned as
|
|
74
|
+
* `[]` — same all-or-nothing semantic as `prop` on `propTo` failure.
|
|
75
|
+
*
|
|
76
|
+
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
77
|
+
* @param {number} start UNIX milliseconds
|
|
78
|
+
* @param {number} end UNIX milliseconds (exclusive)
|
|
79
|
+
* @param {number} stepMs default 1000
|
|
80
|
+
* @return {Promise<Array<Array<{p: object, v: object, t: number}>>>}
|
|
81
|
+
*/
|
|
82
|
+
const propBulk = async (elsets, start, end, stepMs = 1000) => {
|
|
83
|
+
const grid = buildTimeGrid(start, end, stepMs, false);
|
|
84
|
+
if (elsets.length === 0 || grid.length === 0) {
|
|
85
|
+
return elsets.map(() => []);
|
|
86
|
+
}
|
|
87
|
+
const satrecs = elsetsToSatrecs(elsets);
|
|
88
|
+
const dates = grid.map((t) => new Date(t));
|
|
89
|
+
|
|
90
|
+
const runtime = await getBulkRuntime();
|
|
91
|
+
const propagator = getOrCreateBulkPropagator({
|
|
92
|
+
runtime,
|
|
93
|
+
key: "eci",
|
|
94
|
+
makeCalculators: () => [new EciBaseCalculator()],
|
|
95
|
+
satRecsCount: satrecs.length,
|
|
96
|
+
datesCount: dates.length,
|
|
97
|
+
});
|
|
98
|
+
propagator.setSatRecs(satrecs);
|
|
99
|
+
propagator.setDates(dates);
|
|
100
|
+
propagator.run();
|
|
101
|
+
|
|
102
|
+
const {eci} = propagator.getRawOutput();
|
|
103
|
+
// raw layout: [sat0 date0 (x,y,z), sat0 date1 (x,y,z), ..., sat1 date0, ...]
|
|
104
|
+
// error layout: [sat0 date0, sat0 date1, ..., sat1 date0, ...]
|
|
105
|
+
const out = new Array(satrecs.length);
|
|
106
|
+
const m = dates.length;
|
|
107
|
+
for (let s = 0; s < satrecs.length; s++) {
|
|
108
|
+
const ephem = new Array(m);
|
|
109
|
+
let satFailed = false;
|
|
110
|
+
for (let d = 0; d < m; d++) {
|
|
111
|
+
const errIdx = s * m + d;
|
|
112
|
+
if (eci.error[errIdx] !== 0) {
|
|
113
|
+
satFailed = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
const vec = (s * m + d) * 3;
|
|
117
|
+
ephem[d] = {
|
|
118
|
+
p: {
|
|
119
|
+
x: eci.position[vec],
|
|
120
|
+
y: eci.position[vec + 1],
|
|
121
|
+
z: eci.position[vec + 2],
|
|
122
|
+
},
|
|
123
|
+
v: {
|
|
124
|
+
x: eci.velocity[vec],
|
|
125
|
+
y: eci.velocity[vec + 1],
|
|
126
|
+
z: eci.velocity[vec + 2],
|
|
127
|
+
},
|
|
128
|
+
t: grid[d],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
out[s] = satFailed ? [] : ephem;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* WASM-backed sibling of `propGeodetic`. Computes geodetic
|
|
138
|
+
* (lat, lon, t) for every satellite at every step in the closed interval
|
|
139
|
+
* `[start, end]`.
|
|
140
|
+
*
|
|
141
|
+
* Lat/Lon are returned in **degrees** to match `propGeodetic`. Returns
|
|
142
|
+
* `Array<Array<{lat, lon, t}>>` indexed `[satIdx][dateIdx]`. On any SGP4
|
|
143
|
+
* error for a satellite the whole ephemeris for that satellite is `[]`,
|
|
144
|
+
* matching `propGeodetic`'s short-circuit behavior.
|
|
145
|
+
*
|
|
146
|
+
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
147
|
+
* @param {number} start UNIX milliseconds
|
|
148
|
+
* @param {number} end UNIX milliseconds (inclusive)
|
|
149
|
+
* @param {number} stepMs default 60000
|
|
150
|
+
* @return {Promise<Array<Array<{lat: number, lon: number, t: number}>>>}
|
|
151
|
+
*/
|
|
152
|
+
const propGeodeticBulk = async (elsets, start, end, stepMs = 60000) => {
|
|
153
|
+
const grid = buildTimeGrid(start, end, stepMs, true);
|
|
154
|
+
if (elsets.length === 0 || grid.length === 0) {
|
|
155
|
+
return elsets.map(() => []);
|
|
156
|
+
}
|
|
157
|
+
const satrecs = elsetsToSatrecs(elsets);
|
|
158
|
+
const dates = grid.map((t) => new Date(t));
|
|
159
|
+
|
|
160
|
+
const runtime = await getBulkRuntime();
|
|
161
|
+
const propagator = getOrCreateBulkPropagator({
|
|
162
|
+
runtime,
|
|
163
|
+
key: "eci+gmst+geo",
|
|
164
|
+
makeCalculators: () => [
|
|
165
|
+
new EciBaseCalculator(),
|
|
166
|
+
new GmstCalculator(),
|
|
167
|
+
new GeodeticPositionCalculator(),
|
|
168
|
+
],
|
|
169
|
+
satRecsCount: satrecs.length,
|
|
170
|
+
datesCount: dates.length,
|
|
171
|
+
});
|
|
172
|
+
propagator.setSatRecs(satrecs);
|
|
173
|
+
propagator.setDates(dates);
|
|
174
|
+
propagator.run();
|
|
175
|
+
|
|
176
|
+
const {eci, geodeticPosition} = propagator.getRawOutput();
|
|
177
|
+
// KNOWN satellite.js@7.0.0 BUG / docs error: the
|
|
178
|
+
// `GeodeticPositionCalculator` raw layout is documented as
|
|
179
|
+
// `[lat, lon, height, ...]`, and `getFormattedOutput()` exposes fields
|
|
180
|
+
// `{latitude: raw[0], longitude: raw[1], height: raw[2]}` — but the
|
|
181
|
+
// physical values are SWAPPED. Empirically, against the canonical JS
|
|
182
|
+
// `eciToGeodetic` path:
|
|
183
|
+
// raw[3*k + 0] -> physical LONGITUDE (in radians)
|
|
184
|
+
// raw[3*k + 1] -> physical LATITUDE (in radians)
|
|
185
|
+
// raw[3*k + 2] -> height (km)
|
|
186
|
+
// We map raw -> {lat, lon} accordingly so `propGeodeticBulk` is exactly
|
|
187
|
+
// value-equivalent to `propGeodetic`. If a future satellite.js release
|
|
188
|
+
// fixes this upstream, swap the indices back here.
|
|
189
|
+
const out = new Array(satrecs.length);
|
|
190
|
+
const m = dates.length;
|
|
191
|
+
for (let s = 0; s < satrecs.length; s++) {
|
|
192
|
+
const positions = new Array(m);
|
|
193
|
+
let satFailed = false;
|
|
194
|
+
for (let d = 0; d < m; d++) {
|
|
195
|
+
const errIdx = s * m + d;
|
|
196
|
+
if (eci.error[errIdx] !== 0) {
|
|
197
|
+
satFailed = true;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
const vec = (s * m + d) * 3;
|
|
201
|
+
positions[d] = {
|
|
202
|
+
lat: geodeticPosition[vec + 1] * RAD2DEG,
|
|
203
|
+
lon: geodeticPosition[vec] * RAD2DEG,
|
|
204
|
+
t: grid[d],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
out[s] = satFailed ? [] : positions;
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* WASM-backed bulk look-angles primitive. Computes (azimuth, elevation,
|
|
214
|
+
* rangeSat) for every satellite at every supplied date relative to a single
|
|
215
|
+
* observer (Geodetic location, **radians + km** as required by satellite.js
|
|
216
|
+
* v7 — `{latitude, longitude, height}`).
|
|
217
|
+
*
|
|
218
|
+
* Unlike `propBulk` and `propGeodeticBulk` this primitive takes an explicit
|
|
219
|
+
* `dates` array (rather than start/end/step) because look-angles consumers
|
|
220
|
+
* typically already hold the date list (e.g. observation timestamps in
|
|
221
|
+
* `GetResiduals`) and synthesizing a regular grid would re-impose JS-side
|
|
222
|
+
* step math the WASM pipeline is meant to avoid.
|
|
223
|
+
*
|
|
224
|
+
* Returns `Array<Array<{azimuth, elevation, rangeSat, t}>>` indexed
|
|
225
|
+
* `[satIdx][dateIdx]`, with angles in **radians** and `rangeSat` in km. On
|
|
226
|
+
* any SGP4 error for a satellite that satellite's entire output is `[]`.
|
|
227
|
+
*
|
|
228
|
+
* @param {Array<{Line1: string, Line2: string}>} elsets
|
|
229
|
+
* @param {Date[]|number[]} dates
|
|
230
|
+
* @param {{latitude: number, longitude: number, height: number}} observerGd
|
|
231
|
+
* Observer position in radians (lat/lon) and km (height).
|
|
232
|
+
* @return {Promise<Array<Array<{azimuth: number, elevation: number, rangeSat: number, t: number}>>>}
|
|
233
|
+
*/
|
|
234
|
+
const propLookAnglesBulk = async (elsets, dates, observerGd) => {
|
|
235
|
+
if (elsets.length === 0 || dates.length === 0) {
|
|
236
|
+
return elsets.map(() => []);
|
|
237
|
+
}
|
|
238
|
+
const satrecs = elsetsToSatrecs(elsets);
|
|
239
|
+
const dateObjs = dates.map((d) => (d instanceof Date ? d : new Date(d)));
|
|
240
|
+
const ts = dateObjs.map((d) => d.getTime());
|
|
241
|
+
|
|
242
|
+
const runtime = await getBulkRuntime();
|
|
243
|
+
const propagator = getOrCreateBulkPropagator({
|
|
244
|
+
runtime,
|
|
245
|
+
key: "eci+gmst+ecf+lookAngles",
|
|
246
|
+
makeCalculators: () => [
|
|
247
|
+
new EciBaseCalculator(),
|
|
248
|
+
new GmstCalculator(),
|
|
249
|
+
new EcfPositionCalculator(),
|
|
250
|
+
new LookAnglesCalculator(),
|
|
251
|
+
],
|
|
252
|
+
satRecsCount: satrecs.length,
|
|
253
|
+
datesCount: dateObjs.length,
|
|
254
|
+
});
|
|
255
|
+
propagator.setSatRecs(satrecs);
|
|
256
|
+
propagator.setDates(dateObjs);
|
|
257
|
+
propagator.run({lookAngles: {observer: observerGd}});
|
|
258
|
+
|
|
259
|
+
const {eci, lookAngles} = propagator.getRawOutput();
|
|
260
|
+
// lookAngles layout: [sat0 date0 (az, el, range), sat0 date1, ...]
|
|
261
|
+
const out = new Array(satrecs.length);
|
|
262
|
+
const m = dateObjs.length;
|
|
263
|
+
for (let s = 0; s < satrecs.length; s++) {
|
|
264
|
+
const samples = new Array(m);
|
|
265
|
+
let satFailed = false;
|
|
266
|
+
for (let d = 0; d < m; d++) {
|
|
267
|
+
const errIdx = s * m + d;
|
|
268
|
+
if (eci.error[errIdx] !== 0) {
|
|
269
|
+
satFailed = true;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
const vec = (s * m + d) * 3;
|
|
273
|
+
samples[d] = {
|
|
274
|
+
azimuth: lookAngles[vec],
|
|
275
|
+
elevation: lookAngles[vec + 1],
|
|
276
|
+
rangeSat: lookAngles[vec + 2],
|
|
277
|
+
t: ts[d],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
out[s] = satFailed ? [] : samples;
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export {
|
|
286
|
+
// `buildTimeGrid` is intentionally NOT exported: it is consumed only by
|
|
287
|
+
// `propBulk` / `propGeodeticBulk` inside this module. Keeping it private
|
|
288
|
+
// avoids polluting the public `@saber-usa/node-common` surface with a
|
|
289
|
+
// helper that exists purely to bridge the JS-path loop semantics to the
|
|
290
|
+
// WASM `setDates(Date[])` API.
|
|
291
|
+
elsetsToSatrecs, // used by `./wasmAstro.js#getGeoShadowZonesBulk`
|
|
292
|
+
propBulk,
|
|
293
|
+
propGeodeticBulk,
|
|
294
|
+
propLookAnglesBulk,
|
|
295
|
+
};
|