gnss-js 1.15.0 → 1.16.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/dist/sbf.d.ts CHANGED
@@ -1,5 +1,22 @@
1
1
  import { E as Ephemeris } from './nav-BAI1a9vK.js';
2
2
 
3
+ /**
4
+ * Septentrio SBF framing shared by the measurement and navigation
5
+ * decoders: 0x24 0x40 sync, CRC16-CCITT (poly 0x1021, init 0) as U2,
6
+ * block ID U2 (number in bits 0..12, revision in 13..15), total length
7
+ * U2 (multiple of 4, includes the 8-byte header). The CRC covers block
8
+ * ID through payload end.
9
+ */
10
+ declare function crc16(data: Uint8Array, start: number, end: number): number;
11
+ /**
12
+ * Scan a byte stream for valid SBF frames and invoke `onBlock` for each
13
+ * one, passing the block number (revision bits stripped), the frame
14
+ * start offset and the total frame length. Frames with bad CRC are
15
+ * counted and a resync continues at the next byte; the count of bad
16
+ * frames is returned.
17
+ */
18
+ declare function scanSbfFrames(data: Uint8Array, view: DataView, onBlock: (id: number, offset: number, len: number) => void): number;
19
+
3
20
  /**
4
21
  * Septentrio SBF decoded navigation and almanac blocks.
5
22
  *
@@ -197,4 +214,4 @@ interface SbfParseResult {
197
214
  */
198
215
  declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
199
216
 
200
- export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav };
217
+ export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
package/dist/sbf.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import {
2
+ crc16,
2
3
  parseSbfAlmanac,
3
4
  parseSbfMeas,
4
- parseSbfNav
5
- } from "./chunk-BEQSEJMU.js";
5
+ parseSbfNav,
6
+ scanSbfFrames
7
+ } from "./chunk-YPOVPXNK.js";
6
8
  import "./chunk-HVXYFUCB.js";
7
9
  import "./chunk-LEEU5OIO.js";
8
10
  export {
9
11
  parseSbfAlmanac,
10
12
  parseSbfMeas,
11
- parseSbfNav
13
+ parseSbfNav,
14
+ crc16 as sbfCrc16,
15
+ scanSbfFrames
12
16
  };
package/dist/ubx.cjs CHANGED
@@ -20,10 +20,261 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/ubx/index.ts
21
21
  var ubx_exports = {};
22
22
  __export(ubx_exports, {
23
- parseUbxRawx: () => parseUbxRawx
23
+ parseUbxNav: () => parseUbxNav,
24
+ parseUbxRawx: () => parseUbxRawx,
25
+ ubxFrames: () => ubxFrames
24
26
  });
25
27
  module.exports = __toCommonJS(ubx_exports);
28
+
29
+ // src/ubx/frame.ts
30
+ function* ubxFrames(data, stats = { badChecksums: 0 }) {
31
+ let i = 0;
32
+ while (i + 8 <= data.length) {
33
+ if (data[i] !== 181 || data[i + 1] !== 98) {
34
+ i++;
35
+ continue;
36
+ }
37
+ const len = data[i + 4] | data[i + 5] << 8;
38
+ const end = i + 6 + len + 2;
39
+ if (end > data.length) break;
40
+ let ckA = 0;
41
+ let ckB = 0;
42
+ for (let j = i + 2; j < i + 6 + len; j++) {
43
+ ckA = ckA + data[j] & 255;
44
+ ckB = ckB + ckA & 255;
45
+ }
46
+ if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
47
+ stats.badChecksums++;
48
+ i++;
49
+ continue;
50
+ }
51
+ yield {
52
+ msgClass: data[i + 2],
53
+ msgId: data[i + 3],
54
+ payload: data.subarray(i + 6, i + 6 + len),
55
+ payloadStart: i + 6
56
+ };
57
+ i = end;
58
+ }
59
+ }
60
+
61
+ // src/navbits/index.ts
62
+ var GPS_PI = 3.1415926535898;
26
63
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
64
+ var SEC_PER_WEEK = 7 * 86400;
65
+ function getBitU(buff, pos, len) {
66
+ let bits = 0;
67
+ for (let i = pos; i < pos + len; i++) {
68
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
69
+ }
70
+ return bits;
71
+ }
72
+ function getBitS(buff, pos, len) {
73
+ const bits = getBitU(buff, pos, len);
74
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
75
+ return bits - 2 ** len;
76
+ }
77
+ function setBitU(buff, pos, len, data) {
78
+ let mask = 2 ** (len - 1);
79
+ for (let i = pos; i < pos + len; i++, mask /= 2) {
80
+ if (data >= mask) {
81
+ buff[i >> 3] |= 1 << 7 - (i & 7);
82
+ data -= mask;
83
+ } else {
84
+ buff[i >> 3] &= ~(1 << 7 - (i & 7));
85
+ }
86
+ }
87
+ }
88
+ function decodeGpsLnavFrame(subframes, opts = {}) {
89
+ if (subframes.length < 90) return null;
90
+ const b = subframes;
91
+ let i = 24;
92
+ const tow1 = getBitU(b, i, 17) * 6;
93
+ i += 17 + 2;
94
+ const id1 = getBitU(b, i, 3);
95
+ i += 3 + 2;
96
+ const week10 = getBitU(b, i, 10);
97
+ i += 10;
98
+ i += 2;
99
+ i += 4;
100
+ const svHealth = getBitU(b, i, 6);
101
+ i += 6;
102
+ const iodc0 = getBitU(b, i, 2);
103
+ i += 2;
104
+ i += 1 + 87;
105
+ const tgdRaw = getBitS(b, i, 8);
106
+ i += 8;
107
+ const iodc1 = getBitU(b, i, 8);
108
+ i += 8;
109
+ const tocSec = getBitU(b, i, 16) * 16;
110
+ i += 16;
111
+ const af2 = getBitS(b, i, 8) * 2 ** -55;
112
+ i += 8;
113
+ const af1 = getBitS(b, i, 16) * 2 ** -43;
114
+ i += 16;
115
+ const af0 = getBitS(b, i, 22) * 2 ** -31;
116
+ i = 240 + 24;
117
+ i += 17 + 2;
118
+ const id2 = getBitU(b, i, 3);
119
+ i += 3 + 2;
120
+ const iode = getBitU(b, i, 8);
121
+ i += 8;
122
+ const crs = getBitS(b, i, 16) * 2 ** -5;
123
+ i += 16;
124
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
125
+ i += 16;
126
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
127
+ i += 32;
128
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
129
+ i += 16;
130
+ const e = getBitU(b, i, 32) * 2 ** -33;
131
+ i += 32;
132
+ const cus = getBitS(b, i, 16) * 2 ** -29;
133
+ i += 16;
134
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
135
+ i += 32;
136
+ const toes = getBitU(b, i, 16) * 16;
137
+ i = 480 + 24;
138
+ i += 17 + 2;
139
+ const id3 = getBitU(b, i, 3);
140
+ i += 3 + 2;
141
+ const cic = getBitS(b, i, 16) * 2 ** -29;
142
+ i += 16;
143
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
144
+ i += 32;
145
+ const cis = getBitS(b, i, 16) * 2 ** -29;
146
+ i += 16;
147
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
148
+ i += 32;
149
+ const crc = getBitS(b, i, 16) * 2 ** -5;
150
+ i += 16;
151
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
152
+ i += 32;
153
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
154
+ i += 24;
155
+ const iode3 = getBitU(b, i, 8);
156
+ i += 8;
157
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
158
+ const iodc = iodc0 * 256 + iodc1;
159
+ if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
160
+ if (iode3 !== iode || iode !== (iodc & 255)) return null;
161
+ const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
162
+ let week = week10 + 1024 * Math.round((ref - week10) / 1024);
163
+ if (toes < tow1 - 302400) week++;
164
+ else if (toes > tow1 + 302400) week--;
165
+ const prn = opts.prn ?? "G00";
166
+ const tocDate = new Date(
167
+ GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
168
+ );
169
+ return {
170
+ system: prn[0] === "J" ? "J" : "G",
171
+ prn,
172
+ tocDate,
173
+ // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
174
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
175
+ af0,
176
+ af1,
177
+ af2,
178
+ iode,
179
+ crs,
180
+ deltaN,
181
+ m0,
182
+ cuc,
183
+ e,
184
+ cus,
185
+ sqrtA,
186
+ toe: toes,
187
+ cic,
188
+ omega0,
189
+ cis,
190
+ i0,
191
+ crc,
192
+ omega,
193
+ omegaDot,
194
+ idot,
195
+ week,
196
+ svHealth,
197
+ tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
198
+ // IS-GPS-200: -128 reserved
199
+ };
200
+ }
201
+
202
+ // src/ubx/nav.ts
203
+ var PREAMB_CNAV = 139;
204
+ function parseUbxNav(data, opts = {}) {
205
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
206
+ const ephemerides = [];
207
+ let badParity = 0;
208
+ let refWeek = opts.refWeek;
209
+ if (refWeek === void 0) {
210
+ for (const f of ubxFrames(data)) {
211
+ if (f.msgClass === 2 && f.msgId === 21 && f.payload.length >= 16) {
212
+ const week = view.getUint16(f.payloadStart + 8, true);
213
+ if (week > 0) {
214
+ refWeek = week;
215
+ break;
216
+ }
217
+ }
218
+ }
219
+ }
220
+ if (refWeek === void 0) return { ephemerides, badParity };
221
+ const subframes = /* @__PURE__ */ new Map();
222
+ const last = /* @__PURE__ */ new Map();
223
+ for (const f of ubxFrames(data)) {
224
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
225
+ const p = f.payload;
226
+ if (p.length < 8) continue;
227
+ const gnssId = p[0];
228
+ const svId = p[1];
229
+ let prn;
230
+ if (gnssId === 0 && svId >= 1 && svId <= 32) {
231
+ prn = `G${String(svId).padStart(2, "0")}`;
232
+ } else if (gnssId === 5 && svId >= 1 && svId <= 10) {
233
+ if (p.length === 44) continue;
234
+ prn = `J${String(svId).padStart(2, "0")}`;
235
+ } else {
236
+ continue;
237
+ }
238
+ if (p.length < 8 + 40) continue;
239
+ const base = f.payloadStart + 8;
240
+ if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) continue;
241
+ const buff = new Uint8Array(30);
242
+ for (let k = 0; k < 10; k++) {
243
+ const dwrd = view.getUint32(base + 4 * k, true);
244
+ setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
245
+ }
246
+ const id = getBitU(buff, 43, 3);
247
+ if (id < 1 || id > 5) {
248
+ badParity++;
249
+ continue;
250
+ }
251
+ if (id > 3) continue;
252
+ const key = gnssId * 256 + svId;
253
+ let sf = subframes.get(key);
254
+ if (!sf) {
255
+ sf = { buf: new Uint8Array(90), have: 0 };
256
+ subframes.set(key, sf);
257
+ }
258
+ sf.buf.set(buff, (id - 1) * 30);
259
+ sf.have |= 1 << id - 1;
260
+ if (id !== 3 || sf.have !== 7) continue;
261
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
262
+ if (!eph) {
263
+ badParity++;
264
+ continue;
265
+ }
266
+ const prev = last.get(prn);
267
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
268
+ continue;
269
+ }
270
+ last.set(prn, eph);
271
+ ephemerides.push(eph);
272
+ }
273
+ return { ephemerides, badParity };
274
+ }
275
+
276
+ // src/ubx/index.ts
277
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
27
278
  var MS_PER_WEEK = 7 * 864e5;
28
279
  var SIGNALS = {
29
280
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -63,33 +314,14 @@ function parseUbxRawx(data) {
63
314
  const epochs = [];
64
315
  const messageCounts = {};
65
316
  const obsCodes = {};
66
- let badChecksums = 0;
67
- let i = 0;
68
- while (i + 8 <= data.length) {
69
- if (data[i] !== 181 || data[i + 1] !== 98) {
70
- i++;
71
- continue;
72
- }
73
- const cls = data[i + 2];
74
- const id = data[i + 3];
75
- const len = data[i + 4] | data[i + 5] << 8;
76
- const end = i + 6 + len + 2;
77
- if (end > data.length) break;
78
- let ckA = 0;
79
- let ckB = 0;
80
- for (let j = i + 2; j < i + 6 + len; j++) {
81
- ckA = ckA + data[j] & 255;
82
- ckB = ckB + ckA & 255;
83
- }
84
- if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
85
- badChecksums++;
86
- i++;
87
- continue;
88
- }
317
+ const stats = { badChecksums: 0 };
318
+ for (const frame of ubxFrames(data, stats)) {
319
+ const { msgClass: cls, msgId: id } = frame;
320
+ const len = frame.payload.length;
89
321
  const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
90
322
  messageCounts[key] = (messageCounts[key] ?? 0) + 1;
91
323
  if (cls === 2 && id === 21 && len >= 16) {
92
- const p = i + 6;
324
+ const p = frame.payloadStart;
93
325
  const rcvTow = view.getFloat64(p, true);
94
326
  const week = view.getUint16(p + 8, true);
95
327
  const leapS = view.getInt8(p + 10);
@@ -125,16 +357,17 @@ function parseUbxRawx(data) {
125
357
  if (!codes.includes(sig[1])) codes.push(sig[1]);
126
358
  }
127
359
  epochs.push({
128
- timeMs: GPS_EPOCH_MS + week * MS_PER_WEEK + Math.round(rcvTow * 1e3),
360
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + Math.round(rcvTow * 1e3),
129
361
  leapS: leapValid ? leapS : null,
130
362
  meas
131
363
  });
132
364
  }
133
- i = end;
134
365
  }
135
- return { epochs, messageCounts, obsCodes, badChecksums };
366
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
136
367
  }
137
368
  // Annotate the CommonJS export names for ESM import in node:
138
369
  0 && (module.exports = {
139
- parseUbxRawx
370
+ parseUbxNav,
371
+ parseUbxRawx,
372
+ ubxFrames
140
373
  });
package/dist/ubx.d.cts CHANGED
@@ -1,3 +1,82 @@
1
+ import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
2
+
3
+ /**
4
+ * u-blox UBX framing shared by the measurement and navigation decoders:
5
+ * 0xB5 0x62 sync, message class and ID, little-endian payload length,
6
+ * payload, and an 8-bit Fletcher checksum over class..payload.
7
+ */
8
+ /** One checksum-valid UBX frame located in a byte stream. */
9
+ interface UbxFrame {
10
+ /** Message class, e.g. 0x02 for RXM. */
11
+ msgClass: number;
12
+ /** Message ID within the class, e.g. 0x15 for RXM-RAWX. */
13
+ msgId: number;
14
+ /** Payload bytes (a view into the input buffer, not a copy). */
15
+ payload: Uint8Array;
16
+ /** Byte offset of the payload within the input buffer. */
17
+ payloadStart: number;
18
+ }
19
+ /**
20
+ * Iterate every checksum-valid UBX frame in `data`. Bad checksums are
21
+ * counted in `stats.badChecksums` and a resync continues at the next
22
+ * byte; a frame candidate that overruns the buffer ends the scan
23
+ * (truncated capture).
24
+ */
25
+ declare function ubxFrames(data: Uint8Array, stats?: {
26
+ badChecksums: number;
27
+ }): Generator<UbxFrame>;
28
+
29
+ /**
30
+ * u-blox UBX navigation-message decoding: GPS/QZSS LNAV broadcast
31
+ * ephemerides from RXM-SFRBX (class 0x02, id 0x13).
32
+ *
33
+ * Ported from RTKLIB demo5 (rtklibexplorer fork, src/rcv/ublox.c:
34
+ * decode_rxmsfrbx / decode_nav / decode_eph, Copyright (c) 2007-2020
35
+ * T. Takasu, BSD-2-Clause); the subframe fields themselves are decoded
36
+ * by `decodeGpsLnavFrame` (the decode_frame port in src/navbits).
37
+ *
38
+ * On D30* polarity: the receiver has already checked parity and
39
+ * resolved the polarity inversion of the data bits before writing the
40
+ * 30-bit words into the 30 LSBs of each `dwrd`, so — exactly like
41
+ * RTKLIB's decode_nav (`U4(p)>>6`, "24 x 10 bits w/o parity") — the
42
+ * conversion to parity-stripped 24-bit words is a plain 6-bit shift
43
+ * with NO D30* re-inversion. (Verified against the TLM preamble 0x8B
44
+ * at bits 29..22 of word 1 in every GPS/QZSS LNAV message of the
45
+ * reference F9P capture.)
46
+ *
47
+ * Other constellations in RXM-SFRBX (Galileo I/NAV, BDS D1/D2, GLONASS
48
+ * strings, SBAS) and GPS/QZSS CNAV are out of scope and skipped
49
+ * silently, as are LNAV subframes 4/5 (iono/UTC/almanac pages).
50
+ */
51
+
52
+ interface UbxNavOptions {
53
+ /**
54
+ * Full GPS week used to resolve the 10-bit broadcast week. When
55
+ * omitted, the week is harvested from the first RXM-RAWX message in
56
+ * the same stream (u-blox raw logs carry both); a stream with
57
+ * neither yields no ephemerides — the system clock is never used.
58
+ */
59
+ refWeek?: number;
60
+ }
61
+ interface UbxNavResult {
62
+ /** Broadcast ephemerides in stream order, duplicates suppressed. */
63
+ ephemerides: Ephemeris[];
64
+ /**
65
+ * Subframes rejected for inconsistency: an out-of-range subframe ID
66
+ * in the HOW word, or a complete subframe-1/2/3 set whose
67
+ * IODC/IODE cross-check failed (mixed issues of data).
68
+ */
69
+ badParity: number;
70
+ }
71
+ /**
72
+ * Decode every GPS/QZSS LNAV ephemeris broadcast in a UBX byte stream
73
+ * (RXM-SFRBX). Subframes 1–3 are assembled per satellite; a frame is
74
+ * decoded when subframe 3 arrives with 1 and 2 buffered (RTKLIB
75
+ * decode_nav), and repeated broadcasts of an unchanged ephemeris are
76
+ * suppressed by issue of data and clock epoch like `parseNovatelNav`.
77
+ */
78
+ declare function parseUbxNav(data: Uint8Array, opts?: UbxNavOptions): UbxNavResult;
79
+
1
80
  /**
2
81
  * u-blox UBX raw-measurement decoding (RXM-RAWX) — the path from a
3
82
  * receiver log to RINEX-grade observables.
@@ -12,6 +91,7 @@
12
91
  * RINEX attributes chosen to match RTKLIB's u-blox conversion (the de
13
92
  * facto reference: GPS L2 CL/CM as 2X, Galileo E1 as 1X, E5b as 7X).
14
93
  */
94
+
15
95
  interface UbxMeasurement {
16
96
  /** RINEX PRN, e.g. "G04", "R11", "S23". */
17
97
  prn: string;
@@ -53,4 +133,4 @@ interface UbxParseResult {
53
133
  */
54
134
  declare function parseUbxRawx(data: Uint8Array): UbxParseResult;
55
135
 
56
- export { type UbxMeasurement, type UbxParseResult, type UbxRawxEpoch, parseUbxRawx };
136
+ export { type UbxFrame, type UbxMeasurement, type UbxNavOptions, type UbxNavResult, type UbxParseResult, type UbxRawxEpoch, parseUbxNav, parseUbxRawx, ubxFrames };
package/dist/ubx.d.ts CHANGED
@@ -1,3 +1,82 @@
1
+ import { E as Ephemeris } from './nav-BAI1a9vK.js';
2
+
3
+ /**
4
+ * u-blox UBX framing shared by the measurement and navigation decoders:
5
+ * 0xB5 0x62 sync, message class and ID, little-endian payload length,
6
+ * payload, and an 8-bit Fletcher checksum over class..payload.
7
+ */
8
+ /** One checksum-valid UBX frame located in a byte stream. */
9
+ interface UbxFrame {
10
+ /** Message class, e.g. 0x02 for RXM. */
11
+ msgClass: number;
12
+ /** Message ID within the class, e.g. 0x15 for RXM-RAWX. */
13
+ msgId: number;
14
+ /** Payload bytes (a view into the input buffer, not a copy). */
15
+ payload: Uint8Array;
16
+ /** Byte offset of the payload within the input buffer. */
17
+ payloadStart: number;
18
+ }
19
+ /**
20
+ * Iterate every checksum-valid UBX frame in `data`. Bad checksums are
21
+ * counted in `stats.badChecksums` and a resync continues at the next
22
+ * byte; a frame candidate that overruns the buffer ends the scan
23
+ * (truncated capture).
24
+ */
25
+ declare function ubxFrames(data: Uint8Array, stats?: {
26
+ badChecksums: number;
27
+ }): Generator<UbxFrame>;
28
+
29
+ /**
30
+ * u-blox UBX navigation-message decoding: GPS/QZSS LNAV broadcast
31
+ * ephemerides from RXM-SFRBX (class 0x02, id 0x13).
32
+ *
33
+ * Ported from RTKLIB demo5 (rtklibexplorer fork, src/rcv/ublox.c:
34
+ * decode_rxmsfrbx / decode_nav / decode_eph, Copyright (c) 2007-2020
35
+ * T. Takasu, BSD-2-Clause); the subframe fields themselves are decoded
36
+ * by `decodeGpsLnavFrame` (the decode_frame port in src/navbits).
37
+ *
38
+ * On D30* polarity: the receiver has already checked parity and
39
+ * resolved the polarity inversion of the data bits before writing the
40
+ * 30-bit words into the 30 LSBs of each `dwrd`, so — exactly like
41
+ * RTKLIB's decode_nav (`U4(p)>>6`, "24 x 10 bits w/o parity") — the
42
+ * conversion to parity-stripped 24-bit words is a plain 6-bit shift
43
+ * with NO D30* re-inversion. (Verified against the TLM preamble 0x8B
44
+ * at bits 29..22 of word 1 in every GPS/QZSS LNAV message of the
45
+ * reference F9P capture.)
46
+ *
47
+ * Other constellations in RXM-SFRBX (Galileo I/NAV, BDS D1/D2, GLONASS
48
+ * strings, SBAS) and GPS/QZSS CNAV are out of scope and skipped
49
+ * silently, as are LNAV subframes 4/5 (iono/UTC/almanac pages).
50
+ */
51
+
52
+ interface UbxNavOptions {
53
+ /**
54
+ * Full GPS week used to resolve the 10-bit broadcast week. When
55
+ * omitted, the week is harvested from the first RXM-RAWX message in
56
+ * the same stream (u-blox raw logs carry both); a stream with
57
+ * neither yields no ephemerides — the system clock is never used.
58
+ */
59
+ refWeek?: number;
60
+ }
61
+ interface UbxNavResult {
62
+ /** Broadcast ephemerides in stream order, duplicates suppressed. */
63
+ ephemerides: Ephemeris[];
64
+ /**
65
+ * Subframes rejected for inconsistency: an out-of-range subframe ID
66
+ * in the HOW word, or a complete subframe-1/2/3 set whose
67
+ * IODC/IODE cross-check failed (mixed issues of data).
68
+ */
69
+ badParity: number;
70
+ }
71
+ /**
72
+ * Decode every GPS/QZSS LNAV ephemeris broadcast in a UBX byte stream
73
+ * (RXM-SFRBX). Subframes 1–3 are assembled per satellite; a frame is
74
+ * decoded when subframe 3 arrives with 1 and 2 buffered (RTKLIB
75
+ * decode_nav), and repeated broadcasts of an unchanged ephemeris are
76
+ * suppressed by issue of data and clock epoch like `parseNovatelNav`.
77
+ */
78
+ declare function parseUbxNav(data: Uint8Array, opts?: UbxNavOptions): UbxNavResult;
79
+
1
80
  /**
2
81
  * u-blox UBX raw-measurement decoding (RXM-RAWX) — the path from a
3
82
  * receiver log to RINEX-grade observables.
@@ -12,6 +91,7 @@
12
91
  * RINEX attributes chosen to match RTKLIB's u-blox conversion (the de
13
92
  * facto reference: GPS L2 CL/CM as 2X, Galileo E1 as 1X, E5b as 7X).
14
93
  */
94
+
15
95
  interface UbxMeasurement {
16
96
  /** RINEX PRN, e.g. "G04", "R11", "S23". */
17
97
  prn: string;
@@ -53,4 +133,4 @@ interface UbxParseResult {
53
133
  */
54
134
  declare function parseUbxRawx(data: Uint8Array): UbxParseResult;
55
135
 
56
- export { type UbxMeasurement, type UbxParseResult, type UbxRawxEpoch, parseUbxRawx };
136
+ export { type UbxFrame, type UbxMeasurement, type UbxNavOptions, type UbxNavResult, type UbxParseResult, type UbxRawxEpoch, parseUbxNav, parseUbxRawx, ubxFrames };
package/dist/ubx.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import {
2
- parseUbxRawx
3
- } from "./chunk-5IXK25MX.js";
2
+ parseUbxNav,
3
+ parseUbxRawx,
4
+ ubxFrames
5
+ } from "./chunk-6CX6EXRK.js";
6
+ import "./chunk-4LVD4DYL.js";
4
7
  export {
5
- parseUbxRawx
8
+ parseUbxNav,
9
+ parseUbxRawx,
10
+ ubxFrames
6
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
5
5
  "type": "module",
6
6
  "sideEffects": false,