gnss-js 1.66.0 → 2.1.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,6 +1,8 @@
1
1
  # gnss-js
2
2
 
3
- Comprehensive GNSS library for JavaScript and TypeScript. Zero dependencies. Works in Node.js, browsers, Deno, Bun, and Workers.
3
+ A comprehensive, dependency-free GNSS toolkit for JavaScript and TypeScript — from raw receiver bytes to a positioning solution, and everything between. Runs in Node.js, browsers, Deno, Bun, and Workers.
4
+
5
+ It decodes the formats a GNSS workflow actually meets in the wild (RINEX, RTCM 2/3, and the Septentrio/u-blox/NovAtel/Trimble/BINEX binary logs), reconstructs broadcast navigation down to the raw subframe, and solves positions from single-point all the way to PPP with integer ambiguity resolution — with consistent units and explicit JSDoc throughout.
4
6
 
5
7
  ## Install
6
8
 
@@ -8,169 +10,192 @@ Comprehensive GNSS library for JavaScript and TypeScript. Zero dependencies. Wor
8
10
  npm install gnss-js
9
11
  ```
10
12
 
11
- ## Modules
13
+ Everything is exposed through subpath exports (`gnss-js/rinex`, `gnss-js/positioning`, …), so bundlers only pull in what you import.
12
14
 
13
- Import only what you need via subpath exports:
15
+ ## What's inside
14
16
 
15
- ### Time scales
17
+ - **Formats in** — RINEX 2/3/4 obs + nav (plain, Hatanaka `.crx`, `.gz`), SP3, RINEX CLK, IONEX, SINEX bias; RTCM3 (MSM + legacy obs, ephemeris, SSR/IGS-SSR, station); RTCM 2.x; and the receiver binaries SBF, UBX, NovAtel OEM4, Trimble, BINEX.
18
+ - **Formats out** — RINEX 2/3/4 observation + navigation writers and Hatanaka (CRINEX) compression.
19
+ - **Navigation** — Keplerian/GLONASS ephemeris handling plus raw-frame decoding of GPS LNAV/CNAV, Galileo I/NAV + F/NAV, BeiDou D1/D2, GLONASS strings, and Galileo HAS (E6 C/NAV) corrections.
20
+ - **Orbits** — satellite position/velocity, Doppler, DOP, azimuth/elevation, pass prediction and almanac propagation.
21
+ - **Positioning** — SPP, static and kinematic float PPP, PPP-AR (LAMBDA + wide-/narrow-lane FCB estimation), post-processed RTK, DGNSS, and SBAS/DFMC augmentation.
22
+ - **Quality** — multipath, cycle slips, completeness and slant-TEC ionosphere analysis, with SINEX-DCB calibration.
23
+ - **Live** — NTRIP client and a self-detecting RTCM/receiver stream stats accumulator.
24
+ - **Foundations** — GNSS time scales, ECEF/geodetic coordinates, 14-parameter reference-frame transforms, signal/frequency definitions and physical constants.
16
25
 
17
- ```ts
18
- import {
19
- getGpsTime,
20
- getTimeOfWeek,
21
- getWeekNumber,
22
- getDateFromGpsData,
23
- } from 'gnss-js/time';
24
- import { getGalTime, getBdsTime, getUnixTime } from 'gnss-js/time';
25
- import { getJulianDate, getMJD } from 'gnss-js/time';
26
- import { getLeap, getGpsLeap, getUtcDate } from 'gnss-js/time';
27
-
28
- const date = new Date('2024-01-15T12:00:00Z');
29
- const gpsTime = getGpsTime(date); // milliseconds since GPS epoch
30
- const tow = getTimeOfWeek(date); // milliseconds into current GPS week
31
- const week = getWeekNumber(date); // GPS week number
32
- ```
26
+ ## Usage
33
27
 
34
- ### Coordinates
28
+ ### Time, coordinates & reference frames
35
29
 
36
30
  ```ts
31
+ import { getGpsTime, getTimeOfWeek, getWeekNumber } from 'gnss-js/time';
37
32
  import {
38
33
  geodeticToEcef,
39
34
  ecefToGeodetic,
40
35
  vincenty,
41
36
  deg2rad,
42
37
  } from 'gnss-js/coordinates';
38
+ import { transformFrame } from 'gnss-js/frames';
43
39
 
40
+ const gpsTime = getGpsTime(new Date()); // ms since GPS epoch
44
41
  const [x, y, z] = geodeticToEcef(deg2rad(48.8566), deg2rad(2.3522), 35);
45
- const { distance } = vincenty(
46
- deg2rad(51.5),
47
- deg2rad(-0.1),
48
- deg2rad(48.9),
49
- deg2rad(2.4)
50
- );
51
- // distance in meters, angles in radians
52
- ```
53
-
54
- ### Visibility / pass prediction
55
42
 
56
- ```ts
57
- import { computeVisibility } from 'gnss-js/orbit';
58
-
59
- const vis = computeVisibility(ephemerides, rxEcef, startMs, endMs, 300, 10);
60
- // vis.passes: rise/peak/set per satellite; vis.pdop / vis.visibleCount
61
- // per epoch; vis.elevation[prn]: elevation timeline for plotting.
62
- ```
63
-
64
- ### Single-point positioning
65
-
66
- ```ts
67
- import { solveSpp, ionoFree } from 'gnss-js/positioning';
68
-
69
- const sol = solveSpp(pseudoranges, ephemerides, epochMs);
70
- // sol.position (ECEF m), per-system clock biases, DOP, residuals,
71
- // rejected outliers. ~10 m single-frequency; use ionoFree(p1, p2, f1, f2)
72
- // pseudoranges for metre-level.
73
- ```
74
-
75
- ### Reference frame transformations
76
-
77
- ```ts
78
- import { transformFrame, dateToEpoch } from 'gnss-js/frames';
79
-
80
- // ETRS89 (ETRF2000) station coordinates → ITRF2020 at epoch 2010.5
43
+ // Time-dependent 14-parameter Helmert (IERS/EPSG): ITRF88–ITRF2020,
44
+ // ETRF2000/2014/2020, NAD83(2011). 'WGS84' aliases ITRF2020.
81
45
  const itrf = transformFrame(
82
- [3924687.7039, 301132.7618, 5001910.7712],
46
+ [3924687.7, 301132.8, 5001910.8],
83
47
  'ETRF2000',
84
48
  'ITRF2020',
85
49
  2010.5
86
50
  );
87
- // Time-dependent 14-parameter Helmert (IERS/EPSG parameters);
88
- // supports ITRF88–ITRF2020, ETRF2000/2014/2020, NAD83(2011).
89
- // 'WGS84' is treated as an ITRF2020 alias (standard GNSS practice).
90
51
  ```
91
52
 
92
- ### RINEX parsing
53
+ ### RINEX — read, write & products
93
54
 
94
55
  ```ts
95
- import { parseRinexStream, parseNavFile } from 'gnss-js/rinex';
96
-
97
- // Streaming observation parser (positional arguments)
98
- const result = await parseRinexStream(
99
- file, // File (plain, Hatanaka .crx, or .gz)
100
- (percent) => {}, // optional progress callback
56
+ import {
57
+ parseRinexStream,
58
+ parseNavFile,
59
+ parseSp3,
60
+ parseClk,
61
+ parseIonex,
62
+ writeRinexObsBlob,
63
+ writeRinexNav,
64
+ writeCrx,
65
+ } from 'gnss-js/rinex';
66
+
67
+ // Streaming observation parser — plain, Hatanaka .crx, or .gz
68
+ const { header } = await parseRinexStream(
69
+ file,
70
+ (pct) => {}, // optional progress
101
71
  undefined, // optional AbortSignal
102
72
  (time, prn, codes, values) => {
103
- // per-satellite observation callback
73
+ /* per-satellite obs callback */
104
74
  }
105
75
  );
106
- // result.header: RinexHeader
107
76
 
108
- // Navigation file parser
109
- const nav = parseNavFile(navFileText);
110
- // nav.ephemerides: KeplerEphemeris[] | GlonassEphemeris[]
77
+ const nav = parseNavFile(navText); // KeplerEphemeris[] | GlonassEphemeris[]
78
+ const sp3 = parseSp3(sp3Text); // precise orbits
79
+ const clk = parseClk(clkText); // precise clocks
80
+
81
+ // Writers: RINEX 2/3/4 obs + nav, and Hatanaka compression
82
+ const rnx = writeRinexObsBlob(header, epochs);
83
+ const crx = writeCrx(rnx); // → CRINEX
84
+ ```
85
+
86
+ ### Receiver binary formats
87
+
88
+ One decoder per receiver family; each returns measurement epochs, and a companion nav parser reconstructs broadcast ephemeris from the raw frames.
89
+
90
+ ```ts
91
+ import { parseSbfMeas, parseSbfNav, parseSbfHas } from 'gnss-js/sbf'; // Septentrio
92
+ import { parseUbxRawx, parseUbxRawNav } from 'gnss-js/ubx'; // u-blox
93
+ import { parseNovatelRange, parseNovatelNav } from 'gnss-js/novatel'; // NovAtel OEM4
94
+ import { parseTrimble, parseTrimbleNav } from 'gnss-js/trimble'; // Trimble
95
+ import { parseBinex } from 'gnss-js/binex'; // BINEX
96
+
97
+ const sbf = parseSbfMeas(bytes); // measurement epochs + counts
98
+ const nav = parseSbfNav(bytes); // GPS/GAL/BDS/GLO ephemeris from raw blocks
99
+ const has = parseSbfHas(bytes); // Galileo HAS (E6 C/NAV) SSR corrections
111
100
  ```
112
101
 
113
- ### RTCM3 decoding
102
+ ### RTCM (RTCM3, RTCM2, SSR)
114
103
 
115
104
  ```ts
116
- import { Rtcm3Decoder, decodeMsmFull, decodeEphemeris } from 'gnss-js/rtcm3';
105
+ import {
106
+ Rtcm3Decoder,
107
+ decodeObs,
108
+ decodeEphemeris,
109
+ decodeSsr,
110
+ decodeIgsSsr,
111
+ updateStreamStats,
112
+ createStreamStats,
113
+ } from 'gnss-js/rtcm3';
117
114
 
118
115
  const decoder = new Rtcm3Decoder();
119
- decoder.addData(chunk); // Uint8Array from NTRIP stream
116
+ decoder.addData(chunk); // Uint8Array from an NTRIP stream
120
117
  for (const frame of decoder) {
121
- const msm = decodeMsmFull(frame); // MSM4-7 observations
122
- const eph = decodeEphemeris(frame); // broadcast ephemeris
118
+ const obs = decodeObs(frame); // ObsEpoch | null — MSM4-7 or legacy 1001-1012
119
+ const eph = decodeEphemeris(frame); // broadcast ephemeris (all constellations)
120
+ const ssr = decodeSsr(frame); // orbit/clock/bias corrections (1057-1068)
121
+ const igs = decodeIgsSsr(frame); // IGS-SSR 4076 (multi-GNSS + phase bias + iono)
123
122
  }
124
123
  ```
125
124
 
126
- ### Orbit computation
127
-
128
125
  ```ts
129
- import { computeAllPositions, computeDop } from 'gnss-js/orbit';
126
+ import { Rtcm2Decoder, rtcm2Observation, rtcm2Station } from 'gnss-js/rtcm2';
130
127
 
131
- const positions = computeAllPositions(ephemerides, times, receiverPosition);
132
- // positions.prns: string[], positions.times: number[]
133
- // positions.positions[prn][epochIdx]: { lat, lon, az, el } | null
128
+ // Legacy RTCM 2.x — a wholly different wire format (30-bit words, 6-of-8)
129
+ const frames = new Rtcm2Decoder().decode(bytes);
134
130
  ```
135
131
 
136
- ### Signal analysis
132
+ ### Orbits, visibility & Doppler
137
133
 
138
134
  ```ts
139
- import { analyzeQuality } from 'gnss-js/analysis';
135
+ import {
136
+ computeAllPositions,
137
+ computeVisibility,
138
+ computeDop,
139
+ dopplerHz,
140
+ } from 'gnss-js/orbit';
140
141
 
141
- // One re-parse pass: multipath (per code band, Anubis-style), cycle
142
- // slips, completeness, and slant-TEC ionosphere series.
143
- const q = await analyzeQuality(file, header);
144
- // q.multipath, q.cycleSlips, q.completeness, q.iono
142
+ const positions = computeAllPositions(ephemerides, times, rxEcef);
143
+ const vis = computeVisibility(ephemerides, rxEcef, startMs, endMs, 300, 10);
144
+ // vis.passes: rise/peak/set; vis.pdop / vis.visibleCount per epoch;
145
+ // vis.elevation[prn]: elevation timeline for plotting.
146
+ ```
145
147
 
146
- // The accumulators (MultipathAccumulator, CycleSlipAccumulator,
147
- // CompletenessAccumulator, IonoAccumulator) are also exported for
148
- // wiring into a custom parseRinexStream pass.
148
+ ### Positioning — SPP → PPP → PPP-AR / RTK
149
+
150
+ ```ts
151
+ import {
152
+ solveSpp,
153
+ ionoFree, // single-point
154
+ solvePpp, // static/kinematic float PPP + optional AR
155
+ postProcessRtk, // rnx2rtkp-style relative positioning
156
+ solveDgnss, // code-differential
157
+ SbasProcessor, // SBAS / DFMC augmentation
158
+ } from 'gnss-js/positioning';
159
+
160
+ // Single-point: ~10 m single-frequency; ionoFree(p1,p2,f1,f2) for metre-level.
161
+ const spp = solveSpp(pseudoranges, ephemerides, epochMs);
162
+
163
+ // Precise point positioning against SP3/CLK products (dm→cm; AR to cm).
164
+ const ppp = solvePpp(pppEpochs, sp3, pppOptions);
165
+
166
+ // Post-processed RTK: base + rover obs, ephemeris, base ECEF → fixed track.
167
+ const rtk = postProcessRtk(baseObs, roverObs, ephemerides, baseEcef);
149
168
  ```
150
169
 
151
- ### Ionosphere and DCBs
170
+ ### Signal quality & ionosphere
152
171
 
153
172
  ```ts
154
- import { parseSinexBiasDcb, applyIonoDcb } from 'gnss-js/analysis';
155
-
156
- // q.iono: slant TEC per satellite from the geometry-free phase
157
- // levelled to the code — DCB-biased until calibrated:
158
- const satDcb = parseSinexBiasDcb(sinexBiasText, obsEpochMs); // ESA .BIA / CAS .BSX
159
- const { result, receiverDcbTecu } = applyIonoDcb(q.iono, satDcb);
160
- // result.series[n].points: calibrated STEC (TECU); receiver bias
161
- // estimated from the night-time floor per system/signal pair.
173
+ import {
174
+ analyzeQuality,
175
+ parseSinexBiasDcb,
176
+ applyIonoDcb,
177
+ } from 'gnss-js/analysis';
178
+
179
+ // One re-parse pass: multipath (per band, Anubis-style), cycle slips,
180
+ // completeness, and slant-TEC series.
181
+ const q = await analyzeQuality(file, header);
182
+
183
+ // Calibrate STEC with satellite DCBs (ESA .BIA / CAS .BSX); receiver bias
184
+ // from the night-time floor.
185
+ const { result, receiverDcbTecu } = applyIonoDcb(
186
+ q.iono,
187
+ parseSinexBiasDcb(biasText, epochMs)
188
+ );
162
189
  ```
163
190
 
164
- ### NTRIP client
191
+ The individual accumulators (`MultipathAccumulator`, `CycleSlipAccumulator`, `CompletenessAccumulator`, `IonoAccumulator`) are exported too, for a custom `parseRinexStream` pass.
192
+
193
+ ### NTRIP, NMEA & ANTEX
165
194
 
166
195
  ```ts
167
196
  import { fetchSourcetable, connectToMountpoint } from 'gnss-js/ntrip';
168
-
169
- const table = await fetchSourcetable('https://proxy.example.com', {
170
- host: 'caster.example.com',
171
- port: 2101,
172
- version: '2.0',
173
- });
197
+ import { parseNmeaFile, computeStats } from 'gnss-js/nmea';
198
+ import { parseAntex } from 'gnss-js/antex';
174
199
 
175
200
  const { reader, abort } = await connectToMountpoint(
176
201
  'https://proxy.example.com',
@@ -183,28 +208,16 @@ const { reader, abort } = await connectToMountpoint(
183
208
  version: '2.0',
184
209
  }
185
210
  );
186
- ```
187
-
188
- ### NMEA and ANTEX
189
-
190
- ```ts
191
- import { parseNmeaFile, computeStats } from 'gnss-js/nmea';
192
- import { parseAntex } from 'gnss-js/antex';
193
211
 
194
212
  const track = parseNmeaFile(nmeaText); // fixes, satellites, per-sentence records
195
- const stats = computeStats(track.fixes); // position/HDOP/speed statistics
196
213
  const antex = parseAntex(antexText); // antenna PCO/PCV calibrations
197
214
  ```
198
215
 
199
- ### Constants
216
+ ### Constants & signals
200
217
 
201
218
  ```ts
202
- import {
203
- C_LIGHT,
204
- FREQ,
205
- SYSTEM_NAMES,
206
- DUAL_FREQ_PAIRS,
207
- } from 'gnss-js/constants';
219
+ import { C_LIGHT, FREQ, SYSTEM_NAMES } from 'gnss-js/constants';
220
+ import { CONSTELLATIONS, computePsdDb } from 'gnss-js/signals';
208
221
 
209
222
  FREQ['G']['1']; // 1575.42e6 Hz (GPS L1)
210
223
  SYSTEM_NAMES['E']; // "Galileo"
@@ -212,7 +225,7 @@ SYSTEM_NAMES['E']; // "Galileo"
212
225
 
213
226
  ## Unit conventions
214
227
 
215
- All functions use consistent units:
228
+ All functions use consistent units, with explicit `@param`/`@returns` JSDoc:
216
229
 
217
230
  | Quantity | Unit |
218
231
  | ----------- | ---------------- |
@@ -221,12 +234,11 @@ All functions use consistent units:
221
234
  | Distances | **meters** |
222
235
  | Frequencies | **Hz** |
223
236
 
224
- Every exported function has JSDoc with `@param` and `@returns` annotations including explicit units.
225
-
226
237
  ## Roadmap
227
238
 
228
- - **Class-based API** — Ergonomic wrappers like `GnssTime.fromUtc(date).gps` and `Position.fromGeodetic(lat, lon, h).utm` on top of the existing functional core
229
- - **RINEX observation writers** (RINEX 2/3/4 with gzip compression)
239
+ - **Class-based API** — ergonomic wrappers (`GnssTime.fromUtc(date).gps`, `Position.fromGeodetic(...).utm`) over the functional core.
240
+ - **RTCM3 MSM1–3** and NavIC 1041 ephemeris decoding.
241
+ - **Real-time SSR/HAS application** — feeding decoded corrections into a live PPP solve.
230
242
 
231
243
  ## Development
232
244
 
@@ -238,9 +250,9 @@ pnpm run lint
238
250
  pnpm run build
239
251
  ```
240
252
 
241
- Without the fixtures, the nav-file test suites are skipped silently (`describe.skipIf`), so run the fetch script before trusting a green test run.
253
+ Without the fixtures, the data-backed suites skip silently (`describe.skipIf`), so run the fetch script before trusting a green run.
242
254
 
243
- **Releasing**: bump the version and push the tag — CI publishes to npm via Trusted Publishing (one-time setup: npm package settings → Trusted Publisher → this repo + publish.yml) and creates the GitHub Release:
255
+ **Releasing**: bump the version and push the tag — CI publishes to npm via Trusted Publishing and creates the GitHub Release:
244
256
 
245
257
  ```bash
246
258
  npm version patch # or minor/major; updates package.json + creates the tag
@@ -258,5 +270,5 @@ cd ../gnsscalc && pnpm link ../gnss-js # then `pnpm run build --watch` here
258
270
 
259
271
  This project is dual-licensed:
260
272
 
261
- - **AGPL-3.0** — Free for open-source projects. Any software that uses gnss-js must also be open-sourced under a compatible license.
262
- - **Commercial license** — For proprietary/closed-source use. Contact [work@miguel.es](mailto:work@miguel.es) for terms.
273
+ - **AGPL-3.0** — free for open-source projects. Any software that uses gnss-js must also be open-sourced under a compatible license.
274
+ - **Commercial license** — for proprietary/closed-source use. Contact [work@miguel.es](mailto:work@miguel.es) for terms.