@neilberkman/sidereon 0.8.1 → 0.9.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 +128 -72
- package/package.json +18 -2
- package/pkg/sidereon.d.ts +4858 -1734
- package/pkg/sidereon.js +7739 -903
- package/pkg/sidereon_bg.wasm +0 -0
- package/pkg/sidereon_bg.wasm.d.ts +1011 -582
- package/pkg-node/sidereon.d.ts +2916 -221
- package/pkg-node/sidereon.js +7844 -876
- package/pkg-node/sidereon_bg.wasm +0 -0
- package/pkg-node/sidereon_bg.wasm.d.ts +1011 -582
- package/types/sidereon-extra.d.ts +1417 -7
package/README.md
CHANGED
|
@@ -1,105 +1,161 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sidereon
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
GNSS and astrodynamics for JavaScript and TypeScript: propagate satellites,
|
|
4
|
+
predict passes, solve precise positions (SPP / RTK / PPP), and convert between
|
|
5
|
+
coordinate frames and time — the real engine, compiled to WebAssembly, running
|
|
6
|
+
in the browser or Node — checked against the references the field trusts
|
|
7
|
+
(Vallado, Skyfield, IGS, IERS).
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
This isn't a reimplementation in JavaScript. It's the same Rust engine that
|
|
10
|
+
backs the Python, C, and Elixir interfaces, compiled to WebAssembly — so a
|
|
11
|
+
browser tab gets bit-for-bit the same numbers as the native builds, with no
|
|
12
|
+
server round-trip and no native add-on to install.
|
|
11
13
|
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
The published package ships the prebuilt wasm. To build from source:
|
|
14
|
+
## Install
|
|
15
15
|
|
|
16
16
|
```sh
|
|
17
|
-
|
|
18
|
-
wasm-pack build --target nodejs --out-dir pkg-node --out-name sidereon
|
|
17
|
+
npm install @neilberkman/sidereon
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
It's a dual ESM/CJS package shipping prebuilt wasm — works in bundlers,
|
|
21
|
+
browsers, and Node. TypeScript declarations are bundled. There are two entry
|
|
22
|
+
points and they init differently:
|
|
23
|
+
|
|
24
|
+
- **Browser / bundler (ESM):** import the default `init`, `await` it once, then
|
|
25
|
+
call the API. `init()` fetches the `.wasm` for you.
|
|
26
|
+
- **Node (CommonJS):** `require(...)` loads and initializes the wasm at require
|
|
27
|
+
time — there is no init step.
|
|
28
|
+
|
|
29
|
+
## Quickstart: when does the ISS fly over you?
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`.wasm` for you; in Node, hand it the bytes.
|
|
31
|
+
No data files, no setup — give it a two-line element set and a ground station,
|
|
32
|
+
and ask when the satellite is above the horizon.
|
|
26
33
|
|
|
27
34
|
```js
|
|
28
|
-
import init, {
|
|
29
|
-
import { readFile } from "node:fs/promises";
|
|
35
|
+
import init, { Tle, GroundStation } from "@neilberkman/sidereon";
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
await init({ module_or_path: await readFile(new URL("./pkg/sidereon_bg.wasm", import.meta.url)) });
|
|
37
|
+
await init(); // browser/bundler: fetches the wasm. (Node CJS: see below — no init.)
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
// Real orbital elements (grab fresh ones from CelesTrak any time).
|
|
40
|
+
const iss = new Tle(
|
|
41
|
+
"1 25544U 98067A 26178.50947090 .00006280 00000+0 12016-3 0 9996",
|
|
42
|
+
"2 25544 51.6322 248.9966 0004278 238.4942 121.5629 15.49454046573359",
|
|
43
|
+
);
|
|
36
44
|
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
45
|
+
// A ground station: latitude, longitude in degrees (altitude in metres, optional).
|
|
46
|
+
const berkeley = new GroundStation(37.87, -122.27);
|
|
47
|
+
|
|
48
|
+
// Epochs are UTC unix microseconds and cross as BigInt.
|
|
49
|
+
const us = (ms) => BigInt(ms) * 1000n;
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
|
|
52
|
+
// Every pass above 10° over the next 24 hours.
|
|
53
|
+
const passes = iss.findPasses(berkeley, us(now), us(now + 24 * 3600 * 1000), 10.0);
|
|
54
|
+
|
|
55
|
+
const at = (uus) => new Date(Number(uus / 1000n)).toISOString().slice(11, 16);
|
|
56
|
+
for (const p of passes) {
|
|
57
|
+
console.log(`${at(p.aosUnixUs)} UTC · ${(p.durationS / 60).toFixed(1)} min · peak ${p.maxElevationDeg.toFixed(0)}°`);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
40
60
|
|
|
41
|
-
// Single-point positioning. Pass one options object (see the SppRequest type).
|
|
42
|
-
const solution = sp3.solveSpp({
|
|
43
|
-
observations: [{ satelliteId: "G01", pseudorangeM: 22853110.4 }, /* ... */],
|
|
44
|
-
tRxJ2000S: 649728000,
|
|
45
|
-
tRxSecondOfDayS: 86400,
|
|
46
|
-
dayOfYear: 176,
|
|
47
|
-
corrections: { ionosphere: false, troposphere: false },
|
|
48
|
-
});
|
|
49
|
-
console.log(solution.positionM, solution.rxClockS, solution.usedSats);
|
|
50
61
|
```
|
|
62
|
+
08:30 UTC · 6.8 min · peak 88°
|
|
63
|
+
10:09 UTC · 4.3 min · peak 16°
|
|
64
|
+
13:25 UTC · 3.2 min · peak 13°
|
|
65
|
+
15:01 UTC · 6.6 min · peak 56°
|
|
66
|
+
16:39 UTC · 3.5 min · peak 14°
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`Tle` also gives you `propagate(epochs)` (TEME position/velocity over a
|
|
70
|
+
`BigInt64Array` of unix-microsecond epochs) and `lookAngles(station, epochs)`
|
|
71
|
+
(azimuth / elevation / range over a time grid).
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
### Node (CommonJS)
|
|
74
|
+
|
|
75
|
+
`require` resolves to the Node build, which loads the wasm at require time — no
|
|
76
|
+
init call, everything is ready synchronously:
|
|
54
77
|
|
|
55
78
|
```js
|
|
56
|
-
const {
|
|
57
|
-
|
|
79
|
+
const { Tle, GroundStation } = require("@neilberkman/sidereon");
|
|
80
|
+
|
|
81
|
+
const iss = new Tle(line1, line2);
|
|
82
|
+
const passes = iss.findPasses(new GroundStation(37.87, -122.27), startUs, endUs, 10.0);
|
|
58
83
|
```
|
|
59
84
|
|
|
60
|
-
|
|
85
|
+
(Node ESM resolves the same default-`init` build as the browser; `await init()`
|
|
86
|
+
needs the wasm bytes there — `await init({ module_or_path: await readFile(new
|
|
87
|
+
URL("...sidereon_bg.wasm", import.meta.url)) })`. If you're not sure, `require`
|
|
88
|
+
is the simplest path in Node.)
|
|
89
|
+
|
|
90
|
+
## Precise positioning
|
|
91
|
+
|
|
92
|
+
The positioning engine is the other half of the library: load a precise
|
|
93
|
+
ephemeris, hand it pseudoranges, and get a least-squares fix back.
|
|
61
94
|
|
|
62
95
|
```js
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
96
|
+
import { loadSp3 } from "@neilberkman/sidereon"; // (after init)
|
|
97
|
+
import type { SppRequest } from "@neilberkman/sidereon/types";
|
|
98
|
+
|
|
99
|
+
const sp3 = loadSp3(new Uint8Array(sp3FileBytes));
|
|
100
|
+
|
|
101
|
+
const fix = sp3.solveSpp({
|
|
102
|
+
observations: [
|
|
103
|
+
{ satelliteId: "G01", pseudorangeM: 21_000_123.4 },
|
|
104
|
+
{ satelliteId: "G08", pseudorangeM: 22_517_889.1 },
|
|
105
|
+
// ...more satellites
|
|
106
|
+
],
|
|
107
|
+
tRxJ2000S: 649_728_000, // receive epoch, seconds since J2000
|
|
108
|
+
tRxSecondOfDayS: 86_400,
|
|
109
|
+
dayOfYear: 176,
|
|
110
|
+
corrections: { ionosphere: true, troposphere: true },
|
|
111
|
+
withGeodetic: true,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(fix.positionM); // Float64Array [x, y, z] ECEF metres
|
|
115
|
+
console.log(fix.geodetic); // Float64Array [latRad, lonRad, heightM]
|
|
116
|
+
console.log(fix.usedSats); // satellites that contributed
|
|
67
117
|
```
|
|
68
118
|
|
|
69
|
-
|
|
119
|
+
`solveRtkFloat` / `solveRtkFixed` and `solvePppFloat` / `solvePppFixed` follow
|
|
120
|
+
the same shape — an options object in, a result object with `Float64Array`
|
|
121
|
+
positions and scalar attributes out.
|
|
70
122
|
|
|
71
|
-
|
|
72
|
-
const tle = new Tle(line1, line2 /*, "improved" | "afspc" */);
|
|
73
|
-
const t0 = BigInt(Date.now()) * 1000n; // unix microseconds
|
|
74
|
-
const epochs = new BigInt64Array([t0, t0 + 600n * 1_000_000n]);
|
|
123
|
+
## What's in the box
|
|
75
124
|
|
|
76
|
-
|
|
125
|
+
The wasm surface is broad — the same domains the native engine exposes:
|
|
77
126
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
127
|
+
- **Orbits** — SGP4/TLE and OMM, numerical propagation, passes, look angles, visibility
|
|
128
|
+
- **Frames & time** — TEME ↔ GCRS ↔ ITRS, geodetic ↔ ECEF, `Instant` with GMST/GAST and the resolved TT/UT1/TDB scales
|
|
129
|
+
- **Bodies** — Sun/Moon positions, eclipse / shadow geometry, plus JPL SPK (DAF/.bsp) kernels
|
|
130
|
+
- **Positioning** — SPP, RTK (float/fixed), PPP (float/fixed), DGNSS, DOP, velocity, RAIM/FDE
|
|
131
|
+
- **GNSS data** — SP3 (load/interpolate/merge), RINEX (obs/nav/clock), CRINEX (Hatanaka), ANTEX, IONEX slant delay, broadcast ephemeris
|
|
132
|
+
- **Space situational awareness** — conjunction/TCA screening, collision probability, CDM, covariance transforms
|
|
133
|
+
- **RF / signal** — link budget (FSPL, EIRP, C/N0, antenna gain), C/A code, acquisition/correlation
|
|
134
|
+
|
|
135
|
+
The binding adds no modeling of its own: every result is exactly what the engine
|
|
136
|
+
computes. Failures surface as the JS exception you'd expect — `Error` for engine
|
|
137
|
+
rejections (parse failures, non-converging solves, SGP4 error codes), `TypeError`
|
|
138
|
+
for malformed input, `RangeError` for out-of-domain numbers. Full signatures
|
|
139
|
+
live in the bundled TypeScript declarations (`sidereon.d.ts`), with the
|
|
140
|
+
plain-object request shapes in `@neilberkman/sidereon/types`.
|
|
81
141
|
|
|
82
|
-
|
|
142
|
+
A few conventions worth knowing: positions and state arrays cross as
|
|
143
|
+
`Float64Array` (multi-epoch arrays are flat row-major, `3 * epochCount`); SGP4
|
|
144
|
+
epoch grids are `BigInt64Array` of unix microseconds; SP3 query epochs are plain
|
|
145
|
+
numbers in seconds since J2000.
|
|
83
146
|
|
|
84
|
-
|
|
85
|
-
throw a JS `Error` carrying the engine's own message. Bad input throws
|
|
86
|
-
`TypeError` (wrong shape or an unparseable satellite token) or `RangeError`
|
|
87
|
-
(non-finite or out-of-domain numbers). Nothing traps or panics across the
|
|
88
|
-
boundary.
|
|
147
|
+
## Other languages
|
|
89
148
|
|
|
90
|
-
|
|
149
|
+
sidereon is one validated engine with first-class interfaces in **Rust**,
|
|
150
|
+
**Python**, **C**, **Elixir**, and **WebAssembly** — same numbers everywhere.
|
|
151
|
+
See the live demo and docs at [sidereon.dev](https://sidereon.dev).
|
|
91
152
|
|
|
92
|
-
|
|
93
|
-
row-major: `[x0, y0, z0, x1, y1, z1, ...]`, length `3 * epochCount`.
|
|
94
|
-
- Epochs are plain numbers (seconds since J2000) except SGP4 epoch grids, which
|
|
95
|
-
are `BigInt64Array` of unix microseconds.
|
|
96
|
-
- SPP positions are ECEF metres; geodetic output is `[latRad, lonRad, heightM]`.
|
|
97
|
-
- SGP4 states are TEME, kilometres and km/s.
|
|
153
|
+
## How it's validated
|
|
98
154
|
|
|
99
|
-
|
|
155
|
+
The SGP4 propagator is a Rust port of David Vallado's reference implementation,
|
|
156
|
+
bit-exact to it. Frames and time are checked against Skyfield and IERS; the
|
|
157
|
+
positioning stack is checked against IGS products. The published package ships
|
|
158
|
+
the prebuilt wasm, so there's nothing to compile to use it.
|
|
100
159
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
broader astro event/conjunction surface. The rayon-parallel batch solves are
|
|
104
|
-
intentionally omitted (wasm32 is single-threaded; the serial paths produce the
|
|
105
|
-
same numbers).
|
|
160
|
+
*Building from source (for contributors): `npm run build` (runs `wasm-pack
|
|
161
|
+
build` for the `web` and `nodejs` targets). Tests: `npm test`.*
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neilberkman/sidereon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "WebAssembly / JavaScript interface over the sidereon GNSS + astrodynamics engine: SP3 loading and multi-product merge, SPP / RTK (float + fixed) / PPP (float + fixed) positioning, IONEX slant delay, SGP4 and numerical orbit propagation, RINEX observation/navigation/clock parsing, CRINEX (Hatanaka) decoding, observable-domain math (carrier combinations, cycle slips, Hatch smoothing, weighting, velocity, GPS C/A signal), and the astro domain (scale-tagged time, frame transforms, Sun/Moon ephemerides, eclipse / body angles, DOP, passes / visibility, conjunction + collision probability, CDM / OMM / ANTEX parsing, RF link budget)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/neilberkman/sidereon-wasm.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/neilberkman/sidereon-wasm#readme",
|
|
12
|
+
"bugs": "https://github.com/neilberkman/sidereon-wasm/issues",
|
|
7
13
|
"exports": {
|
|
8
14
|
".": {
|
|
9
15
|
"types": "./pkg/sidereon.d.ts",
|
|
@@ -28,6 +34,16 @@
|
|
|
28
34
|
],
|
|
29
35
|
"scripts": {
|
|
30
36
|
"build": "wasm-pack build --target web --out-dir pkg --out-name sidereon && wasm-pack build --target nodejs --out-dir pkg-node --out-name sidereon && node scripts/postbuild.mjs",
|
|
31
|
-
"test": "node --test"
|
|
37
|
+
"test": "node --test",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"lint": "eslint . && prettier --check \"test/**/*.mjs\" \"scripts/**/*.mjs\" \"*.json\" \"eslint.config.js\"",
|
|
40
|
+
"format": "prettier --write \"test/**/*.mjs\" \"scripts/**/*.mjs\" \"*.json\" \"eslint.config.js\""
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@eslint/js": "^10.0.1",
|
|
44
|
+
"eslint": "^10.6.0",
|
|
45
|
+
"globals": "^17.7.0",
|
|
46
|
+
"prettier": "^3.9.1",
|
|
47
|
+
"typescript": "^6.0.3"
|
|
32
48
|
}
|
|
33
49
|
}
|