@neilberkman/sidereon 0.8.1 → 0.9.1

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 CHANGED
@@ -1,105 +1,152 @@
1
1
  # @neilberkman/sidereon
2
2
 
3
- A WebAssembly / JavaScript interface over the [sidereon](https://github.com/neilberkman/sidereon)
4
- GNSS + astrodynamics engine. It loads SP3 precise ephemerides, runs single-point
5
- positioning (SPP), queries IONEX slant ionospheric delay, and propagates SGP4 /
6
- TLE orbits with ground-station look angles. The numbers come straight from
7
- `sidereon-core`; this package only marshals across the wasm boundary.
3
+ GNSS and astrodynamics in the browser and in Node: propagate satellites, predict
4
+ passes, solve precise positions (SPP / RTK / PPP), and convert between coordinate
5
+ frames and time scales. This is the JavaScript and TypeScript interface to the
6
+ sidereon engine.
8
7
 
9
- The binding calls the engine's serial paths only, so no thread pool is spawned
10
- under wasm32.
8
+ The engine is written in Rust and compiled to WebAssembly, so a browser tab or a
9
+ Node process gets the same numbers the native builds produce, with no server
10
+ round-trip and no native add-on to install. Results are reference-validated: the
11
+ SGP4 propagator is a bit-exact port of Vallado's reference implementation, frames
12
+ and time are checked against Skyfield and IERS, and the positioning stack is
13
+ checked against IGS products.
11
14
 
12
- ## Install / build
13
-
14
- The published package ships the prebuilt wasm. To build from source:
15
+ ## Install
15
16
 
16
17
  ```sh
17
- wasm-pack build --target web --out-dir pkg --out-name sidereon
18
- wasm-pack build --target nodejs --out-dir pkg-node --out-name sidereon
18
+ npm install @neilberkman/sidereon
19
19
  ```
20
20
 
21
- ## Usage (ESM, async init)
22
-
23
- The `web` build follows the standard wasm-pack pattern: import the default
24
- `init`, await it once, then call the API. In the browser `init()` fetches the
25
- `.wasm` for you; in Node, hand it the bytes.
21
+ The package is dual ESM/CJS and ships prebuilt wasm with bundled TypeScript
22
+ declarations. The two entry points initialize differently:
26
23
 
27
- ```js
28
- import init, { loadSp3, loadIonex, Tle, GroundStation } from "@neilberkman/sidereon";
29
- import { readFile } from "node:fs/promises";
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, so there is no init step and everything is ready synchronously.
30
28
 
31
- // Browser: await init(); Node: pass the wasm bytes.
32
- await init({ module_or_path: await readFile(new URL("./pkg/sidereon_bg.wasm", import.meta.url)) });
29
+ ## Example: propagate a TLE
33
30
 
34
- const sp3 = loadSp3(new Uint8Array(await readFile("COD0MGXFIN.SP3")));
35
- console.log(sp3.epochCount, sp3.satellites);
31
+ Parse a two-line element set, run SGP4, and take look angles (azimuth, elevation,
32
+ range) from a ground station. No data files, no setup.
36
33
 
37
- // Positions come back as Float64Array; epochs as plain numbers (seconds J2000).
38
- const epochs = sp3.epochsJ2000Seconds();
39
- const { positionM, clockS } = sp3.interpolate("G01", epochs.slice(0, 1));
34
+ ```js
35
+ import init, { Tle, GroundStation } from "@neilberkman/sidereon";
40
36
 
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
- ```
37
+ await init();
51
38
 
52
- For CommonJS / synchronous Node, `require("@neilberkman/sidereon")` resolves to
53
- the `nodejs` build, which loads the wasm at require time (no init step):
39
+ const tle = new Tle(
40
+ "1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9009",
41
+ "2 25544 51.6400 208.8657 0002644 250.3037 109.7782 15.49560812999990",
42
+ );
43
+ const station = new GroundStation(51.5, -0.1, 10.0);
54
44
 
55
- ```js
56
- const { loadSp3 } = require("@neilberkman/sidereon");
57
- const sp3 = loadSp3(require("fs").readFileSync("COD0MGXFIN.SP3"));
45
+ // Epochs are unix microseconds as BigInt64Array.
46
+ const t = BigInt(Date.UTC(2024, 0, 1, 12)) * 1000n;
47
+ const look = tle.lookAngles(station, new BigInt64Array([t]));
48
+ console.log(look.azimuthDeg[0], look.elevationDeg[0], look.rangeKm[0]);
58
49
  ```
59
50
 
60
- ## IONEX slant delay
51
+ `Tle` also gives you `propagate(epochs)` (TEME position/velocity over a
52
+ `BigInt64Array` of unix-microsecond epochs) and `findPasses(station, start, end,
53
+ minElevationDeg)` for visibility windows.
61
54
 
62
- ```js
63
- const ionex = loadIonex(new Uint8Array(await readFile("igsg.20i")));
64
- const epochs = ionex.mapEpochsJ2000S; // Float64Array, seconds since J2000
65
- // receiver lat/lon deg, satellite az/el deg, epoch, carrier Hz -> metres
66
- const delayM = ionex.slantDelay(37.4, -122.1, 135, 30, epochs[0], 1575.42e6);
67
- ```
55
+ ### Node (CommonJS)
68
56
 
69
- ## SGP4 propagation and look angles
57
+ `require` resolves to the Node build, which initializes the wasm at require time:
70
58
 
71
59
  ```js
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]);
75
-
76
- const { positionKm, velocityKmS } = tle.propagate(epochs); // TEME, flat [x,y,z,...]
60
+ const { Tle, GroundStation } = require("@neilberkman/sidereon");
77
61
 
78
- const station = new GroundStation(37.7749, -122.4194 /*, altitudeM */);
79
- const { azimuthDeg, elevationDeg, rangeKm } = tle.lookAngles(station, epochs);
62
+ const tle = new Tle(line1, line2);
63
+ const look = tle.lookAngles(new GroundStation(51.5, -0.1, 10.0), epochs);
80
64
  ```
81
65
 
82
- ## Errors
66
+ ## Example: precise positioning
83
67
 
84
- Engine failures (parse rejections, non-converging solves, SGP4 error codes)
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.
68
+ Load a precise SP3 ephemeris, hand it pseudoranges, and get a least-squares fix
69
+ back.
89
70
 
90
- ## Data conventions
71
+ ```js
72
+ import init, { loadSp3 } from "@neilberkman/sidereon";
91
73
 
92
- - Positions and vectors cross as `Float64Array`. Multi-epoch arrays are flat,
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.
74
+ await init();
98
75
 
99
- ## Scope
76
+ // SP3-c precise orbits, as bytes (read from a file, fetch, or string).
77
+ const sp3 = loadSp3(new TextEncoder().encode(sp3Text));
78
+
79
+ // GPS L1 pseudoranges (m) for the satellites in view at the epoch.
80
+ const solution = sp3.solveSpp({
81
+ observations: [
82
+ { satelliteId: "G08", pseudorangeM: 23825519.8 },
83
+ { satelliteId: "G10", pseudorangeM: 22717690.1 },
84
+ { satelliteId: "G16", pseudorangeM: 20478653.4 },
85
+ { satelliteId: "G18", pseudorangeM: 21768335.2 },
86
+ { satelliteId: "G20", pseudorangeM: 21248327.7 },
87
+ { satelliteId: "G21", pseudorangeM: 20808709.8 },
88
+ ],
89
+ tRxJ2000S: 646272000.0,
90
+ tRxSecondOfDayS: 43200.0,
91
+ dayOfYear: 176.5,
92
+ initialGuess: [4.5e6, 0.5e6, 4.5e6, 0],
93
+ corrections: { ionosphere: false, troposphere: false },
94
+ withGeodetic: true,
95
+ });
96
+
97
+ console.log(solution.positionM); // ECEF m ~ [4484128, 550582, 4487561]
98
+ console.log(solution.rxClockS); // receiver clock bias, seconds
99
+ ```
100
100
 
101
- Exposed: SP3 load/query, SPP solve, IONEX slant delay, SGP4 propagate /
102
- look angles. Not yet exposed: RTK, PPP, RINEX OBS/NAV/clock, velocity, and the
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).
101
+ `solveRtkFloat` / `solveRtkFixed` and `solvePppFloat` / `solvePppFixed` follow
102
+ the same shape: an options object in, a result object with `Float64Array`
103
+ positions and scalar attributes out.
104
+
105
+ ## Capabilities
106
+
107
+ The wasm surface mirrors the full breadth of the engine:
108
+
109
+ - **Orbit propagation:** SGP4 from TLE and OMM, numerical propagation, batch
110
+ constellation propagation, pass prediction, look angles, coverage.
111
+ - **GNSS positioning:** SPP, RTK (float/fixed), PPP (float/fixed), DGNSS, RAIM/FDE,
112
+ DOP, velocity.
113
+ - **Ephemeris and time:** broadcast ephemeris and SP3 (load/interpolate/merge),
114
+ JPL SPK (DAF/.bsp) kernels, scale-aware time (`Instant` with GMST/GAST and
115
+ resolved TT/UT1/TDB), Earth orientation parameters.
116
+ - **Geometry and events:** reference frames (TEME, GCRS, ITRS, geodetic, ECEF),
117
+ look angles, eclipse and shadow geometry, conjunction screening with collision
118
+ probability, initial orbit determination, orbital elements.
119
+ - **Atmosphere:** Klobuchar and NeQuick-G ionosphere, IONEX slant delay,
120
+ troposphere models.
121
+ - **RF link budget:** free-space path loss, EIRP, C/N0, antenna gain.
122
+ - **Format parsing and serialization:** TLE/OMM, CCSDS, RINEX, CRINEX (Hatanaka),
123
+ SP3, IONEX, ANTEX, RTCM.
124
+
125
+ The binding adds no modeling of its own: every result is exactly what the engine
126
+ computes. Failures surface as the JS exception you would expect (`Error` for
127
+ engine rejections such as parse failures, non-converging solves, and SGP4 error
128
+ codes; `TypeError` for malformed input; `RangeError` for out-of-domain numbers).
129
+ Full signatures live in the bundled TypeScript declarations (`sidereon.d.ts`),
130
+ with the plain-object request shapes in `@neilberkman/sidereon/types`.
131
+
132
+ A few conventions worth knowing: positions and state arrays cross as
133
+ `Float64Array` (multi-epoch arrays are flat row-major, `3 * epochCount`); SGP4
134
+ epoch grids are `BigInt64Array` of unix microseconds; SP3 query epochs are plain
135
+ numbers in seconds since J2000.
136
+
137
+ ## Live demo
138
+
139
+ The interactive demo at [sidereon.dev](https://sidereon.dev) runs on this exact
140
+ package: every computation happens client-side in your browser via this wasm
141
+ build.
142
+
143
+ ## Links
144
+
145
+ - **Engine and core repo:** https://github.com/neilberkman/sidereon
146
+ - **Live demo:** https://sidereon.dev
147
+ - **Sibling interfaces:** sidereon-python (PyPI), sidereon-c, sidereon-ex (Hex).
148
+ One validated engine, the same numbers in every language.
149
+
150
+ ## License
151
+
152
+ MIT
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@neilberkman/sidereon",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
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
  }