@neilberkman/sidereon 0.8.0 → 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 CHANGED
@@ -1,105 +1,161 @@
1
- # @neilberkman/sidereon
1
+ # 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 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
- The binding calls the engine's serial paths only, so no thread pool is spawned
10
- under wasm32.
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 / build
13
-
14
- The published package ships the prebuilt wasm. To build from source:
14
+ ## Install
15
15
 
16
16
  ```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
17
+ npm install @neilberkman/sidereon
19
18
  ```
20
19
 
21
- ## Usage (ESM, async init)
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
- 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.
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, { loadSp3, loadIonex, Tle, GroundStation } from "@neilberkman/sidereon";
29
- import { readFile } from "node:fs/promises";
35
+ import init, { Tle, GroundStation } from "@neilberkman/sidereon";
30
36
 
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)) });
37
+ await init(); // browser/bundler: fetches the wasm. (Node CJS: see below — no init.)
33
38
 
34
- const sp3 = loadSp3(new Uint8Array(await readFile("COD0MGXFIN.SP3")));
35
- console.log(sp3.epochCount, sp3.satellites);
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
- // 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));
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
- For CommonJS / synchronous Node, `require("@neilberkman/sidereon")` resolves to
53
- the `nodejs` build, which loads the wasm at require time (no init step):
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 { loadSp3 } = require("@neilberkman/sidereon");
57
- const sp3 = loadSp3(require("fs").readFileSync("COD0MGXFIN.SP3"));
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
- ## IONEX slant delay
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
- 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);
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
- ## SGP4 propagation and look angles
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
- ```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]);
123
+ ## What's in the box
75
124
 
76
- const { positionKm, velocityKmS } = tle.propagate(epochs); // TEME, flat [x,y,z,...]
125
+ The wasm surface is broad the same domains the native engine exposes:
77
126
 
78
- const station = new GroundStation(37.7749, -122.4194 /*, altitudeM */);
79
- const { azimuthDeg, elevationDeg, rangeKm } = tle.lookAngles(station, epochs);
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
- ## Errors
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
- 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.
147
+ ## Other languages
89
148
 
90
- ## Data conventions
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
- - 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.
153
+ ## How it's validated
98
154
 
99
- ## Scope
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
- 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).
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.8.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",
@@ -18,6 +24,7 @@
18
24
  "pkg/sidereon.d.ts",
19
25
  "pkg/sidereon_bg.wasm",
20
26
  "pkg/sidereon_bg.wasm.d.ts",
27
+ "pkg-node/package.json",
21
28
  "pkg-node/sidereon.js",
22
29
  "pkg-node/sidereon.d.ts",
23
30
  "pkg-node/sidereon_bg.wasm",
@@ -26,7 +33,17 @@
26
33
  "README.md"
27
34
  ],
28
35
  "scripts": {
29
- "build": "wasm-pack build --target web --out-dir pkg --out-name sidereon && wasm-pack build --target nodejs --out-dir pkg-node --out-name sidereon",
30
- "test": "node --test"
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",
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"
31
48
  }
32
49
  }