gnss-js 1.36.0 → 1.38.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/index.cjs CHANGED
@@ -142,6 +142,7 @@ __export(src_exports, {
142
142
  decodeBinexEph: () => decodeBinexEph,
143
143
  decodeEphemeris: () => decodeEphemeris,
144
144
  decodeMsmFull: () => decodeMsmFull,
145
+ decodeSbfNavigation: () => decodeSbfNavigation,
145
146
  deg2dms: () => deg2dms,
146
147
  deg2rad: () => deg2rad,
147
148
  detrendIonoArcs: () => detrendIonoArcs,
@@ -260,10 +261,12 @@ __export(src_exports, {
260
261
  parseSbfCnav: () => parseSbfCnav,
261
262
  parseSbfGalNav: () => parseSbfGalNav,
262
263
  parseSbfGloNav: () => parseSbfGloNav,
264
+ parseSbfGpsNav: () => parseSbfGpsNav,
263
265
  parseSbfHas: () => parseSbfHas,
264
266
  parseSbfIonoUtc: () => parseSbfIonoUtc,
265
267
  parseSbfMeas: () => parseSbfMeas,
266
268
  parseSbfNav: () => parseSbfNav,
269
+ parseSbfReceiverSetup: () => parseSbfReceiverSetup,
267
270
  parseSinexBiasDcb: () => parseSinexBiasDcb,
268
271
  parseSourcetable: () => parseSourcetable,
269
272
  parseSp3: () => parseSp3,
@@ -2699,6 +2702,38 @@ function setBitU(buff, pos, len, data) {
2699
2702
  }
2700
2703
  }
2701
2704
  }
2705
+ var GpsLnavAssembler = class _GpsLnavAssembler {
2706
+ buffers = /* @__PURE__ */ new Map();
2707
+ last = /* @__PURE__ */ new Map();
2708
+ /** Subframe ID (1–5) from the HOW word of a parity-stripped subframe. */
2709
+ static subframeId(subframe) {
2710
+ return getBitU(subframe, 43, 3);
2711
+ }
2712
+ /**
2713
+ * Push one parity-stripped 30-byte LNAV subframe for `prn`, resolving
2714
+ * the 10-bit week against `refWeek`. See {@link LnavPush} for outcomes.
2715
+ */
2716
+ push(prn, subframe, refWeek) {
2717
+ const id = _GpsLnavAssembler.subframeId(subframe);
2718
+ if (id < 1 || id > 3) return { kind: "skipped" };
2719
+ let sf = this.buffers.get(prn);
2720
+ if (!sf) {
2721
+ sf = { buf: new Uint8Array(90), have: 0 };
2722
+ this.buffers.set(prn, sf);
2723
+ }
2724
+ sf.buf.set(subframe, (id - 1) * 30);
2725
+ sf.have |= 1 << id - 1;
2726
+ if (id !== 3 || sf.have !== 7) return { kind: "incomplete" };
2727
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
2728
+ if (!eph) return { kind: "decodeFailed" };
2729
+ const prev = this.last.get(prn);
2730
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
2731
+ return { kind: "duplicate" };
2732
+ }
2733
+ this.last.set(prn, eph);
2734
+ return { kind: "eph", eph };
2735
+ }
2736
+ };
2702
2737
  function decodeGpsLnavFrame(subframes, opts = {}) {
2703
2738
  if (subframes.length < 90) return null;
2704
2739
  const b = subframes;
@@ -5918,38 +5953,19 @@ function parseUbxNav(data, opts = {}) {
5918
5953
  }
5919
5954
  }
5920
5955
  if (refWeek === void 0) return { ephemerides, badParity };
5921
- const subframes = /* @__PURE__ */ new Map();
5922
- const last = /* @__PURE__ */ new Map();
5956
+ const assembler = new GpsLnavAssembler();
5923
5957
  for (const f of ubxFrames(data)) {
5924
5958
  if (f.msgClass !== 2 || f.msgId !== 19) continue;
5925
5959
  const lnav = readLnavSubframe(view, f);
5926
5960
  if (!lnav) continue;
5927
- const { prn, gnssId, svId, buff, id } = lnav;
5961
+ const { prn, buff, id } = lnav;
5928
5962
  if (id < 1 || id > 5) {
5929
5963
  badParity++;
5930
5964
  continue;
5931
5965
  }
5932
- if (id > 3) continue;
5933
- const key = gnssId * 256 + svId;
5934
- let sf = subframes.get(key);
5935
- if (!sf) {
5936
- sf = { buf: new Uint8Array(90), have: 0 };
5937
- subframes.set(key, sf);
5938
- }
5939
- sf.buf.set(buff, (id - 1) * 30);
5940
- sf.have |= 1 << id - 1;
5941
- if (id !== 3 || sf.have !== 7) continue;
5942
- const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
5943
- if (!eph) {
5944
- badParity++;
5945
- continue;
5946
- }
5947
- const prev = last.get(prn);
5948
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
5949
- continue;
5950
- }
5951
- last.set(prn, eph);
5952
- ephemerides.push(eph);
5966
+ const r = assembler.push(prn, buff, refWeek);
5967
+ if (r.kind === "eph") ephemerides.push(r.eph);
5968
+ else if (r.kind === "decodeFailed") badParity++;
5953
5969
  }
5954
5970
  return { ephemerides, badParity };
5955
5971
  }
@@ -6740,9 +6756,9 @@ var GloStringAssembler = class {
6740
6756
  * Returns the newly completed ephemeris, or null. The decoded
6741
6757
  * string-4 slot number must match `prn`, or nothing is emitted.
6742
6758
  */
6743
- push(prn, str, time, freqNum = 0) {
6744
- if (str.length < GLO_STRING_BYTES) return null;
6745
- const m = getBitU(str, 1, 4);
6759
+ push(prn, str2, time, freqNum = 0) {
6760
+ if (str2.length < GLO_STRING_BYTES) return null;
6761
+ const m = getBitU(str2, 1, 4);
6746
6762
  if (m < 1) return null;
6747
6763
  const sec = Math.floor(time.getTime() / 1e3);
6748
6764
  let sat = this.sats.get(prn);
@@ -6754,7 +6770,7 @@ var GloStringAssembler = class {
6754
6770
  sat.batchSec = sec;
6755
6771
  }
6756
6772
  if (m > 4) return null;
6757
- sat.buf.set(str.subarray(0, GLO_STRING_BYTES), (m - 1) * 10);
6773
+ sat.buf.set(str2.subarray(0, GLO_STRING_BYTES), (m - 1) * 10);
6758
6774
  if (m !== 4) return null;
6759
6775
  const eph = decodeGloStrings(sat.buf, time, { freqNum });
6760
6776
  if (!eph || eph.prn !== prn) return null;
@@ -6845,20 +6861,20 @@ function parseUbxRawNav(data, opts = {}) {
6845
6861
  if (p.length < 8 + 16) continue;
6846
6862
  counts.glo++;
6847
6863
  if (svId < 1 || svId > 32) continue;
6848
- const str = new Uint8Array(16);
6864
+ const str2 = new Uint8Array(16);
6849
6865
  for (let k = 0; k < 4; k++) {
6850
6866
  const w = view.getUint32(base + 4 * k, true);
6851
- str[4 * k] = w >>> 24;
6852
- str[4 * k + 1] = w >>> 16 & 255;
6853
- str[4 * k + 2] = w >>> 8 & 255;
6854
- str[4 * k + 3] = w & 255;
6867
+ str2[4 * k] = w >>> 24;
6868
+ str2[4 * k + 1] = w >>> 16 & 255;
6869
+ str2[4 * k + 2] = w >>> 8 & 255;
6870
+ str2[4 * k + 3] = w & 255;
6855
6871
  }
6856
- if (!testGloString(str)) {
6872
+ if (!testGloString(str2)) {
6857
6873
  badParity++;
6858
6874
  continue;
6859
6875
  }
6860
6876
  if (!refDate) continue;
6861
- const eph = glo.push(`R${two2(svId)}`, str, refDate, p[3] - 7);
6877
+ const eph = glo.push(`R${two2(svId)}`, str2, refDate, p[3] - 7);
6862
6878
  if (eph) ephemerides.push(eph);
6863
6879
  }
6864
6880
  }
@@ -7360,38 +7376,132 @@ function parseSbfIonoUtc(data) {
7360
7376
  return { ionoCorrections, leapSeconds };
7361
7377
  }
7362
7378
 
7379
+ // src/sbf/setup.ts
7380
+ var F8_DNU = -2e10;
7381
+ var F4_DNU3 = -2e10;
7382
+ function str(data, off, n) {
7383
+ let end = off;
7384
+ const stop = off + n;
7385
+ while (end < stop && data[end] !== 0) end++;
7386
+ let s = "";
7387
+ for (let i = off; i < end; i++) s += String.fromCharCode(data[i]);
7388
+ return s.trim();
7389
+ }
7390
+ function parseSbfReceiverSetup(data) {
7391
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7392
+ let out = null;
7393
+ scanSbfFrames(data, view, (id, b, len) => {
7394
+ if (id !== 5902 || len < 256) return;
7395
+ const setup = {
7396
+ markerName: str(data, b + 16, 60),
7397
+ markerNumber: str(data, b + 76, 20),
7398
+ observer: str(data, b + 96, 20),
7399
+ agency: str(data, b + 116, 40),
7400
+ rxSerialNumber: str(data, b + 156, 20),
7401
+ rxName: str(data, b + 176, 20),
7402
+ rxVersion: str(data, b + 196, 20),
7403
+ antSerialNumber: str(data, b + 216, 20),
7404
+ antType: str(data, b + 236, 20),
7405
+ deltaH: len >= 268 ? view.getFloat32(b + 256, true) : 0,
7406
+ deltaE: len >= 268 ? view.getFloat32(b + 260, true) : 0,
7407
+ deltaN: len >= 268 ? view.getFloat32(b + 264, true) : 0,
7408
+ markerType: len >= 288 ? str(data, b + 268, 20) : null,
7409
+ gnssFwVersion: len >= 328 ? str(data, b + 288, 40) : null,
7410
+ productName: len >= 368 ? str(data, b + 328, 40) : null,
7411
+ latitude: null,
7412
+ longitude: null,
7413
+ height: null,
7414
+ position: null,
7415
+ stationCode: len >= 398 ? str(data, b + 388, 10) : null
7416
+ };
7417
+ if (len >= 388) {
7418
+ const lat = view.getFloat64(b + 368, true);
7419
+ const lon = view.getFloat64(b + 376, true);
7420
+ const h = view.getFloat32(b + 384, true);
7421
+ if (lat !== F8_DNU && lon !== F8_DNU && h !== F4_DNU3) {
7422
+ setup.latitude = lat;
7423
+ setup.longitude = lon;
7424
+ setup.height = h;
7425
+ setup.position = geodeticToEcef(lat, lon, h);
7426
+ }
7427
+ }
7428
+ out = setup;
7429
+ });
7430
+ return out;
7431
+ }
7432
+
7433
+ // src/sbf/rawnav-gps.ts
7434
+ var WNC_DNU = 65535;
7435
+ function extractSbfLnavSubframe(view, b) {
7436
+ const sf = new Uint8Array(30);
7437
+ for (let k = 0; k < 10; k++) {
7438
+ const w = view.getUint32(b + 20 + 4 * k, true);
7439
+ setBitU(sf, 24 * k, 24, w >>> 6 & 16777215);
7440
+ }
7441
+ return sf;
7442
+ }
7443
+ function feedGpsLnavBlock(view, b, assembler) {
7444
+ const prn = svidToPrn(view.getUint8(b + 14));
7445
+ if (!prn || prn[0] !== "G" && prn[0] !== "J") return {};
7446
+ if (view.getUint8(b + 15) !== 1) return {};
7447
+ const wnc = view.getUint16(b + 12, true);
7448
+ if (wnc === WNC_DNU) return {};
7449
+ const r = assembler.push(prn, extractSbfLnavSubframe(view, b), wnc);
7450
+ if (r.kind === "eph") return { eph: r.eph };
7451
+ if (r.kind === "decodeFailed") return { badParity: true };
7452
+ return {};
7453
+ }
7454
+ function parseSbfGpsNav(data) {
7455
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7456
+ const ephemerides = [];
7457
+ const assembler = new GpsLnavAssembler();
7458
+ let badParity = 0;
7459
+ let messages = 0;
7460
+ scanSbfFrames(data, view, (id, b, len) => {
7461
+ if (id !== 4017 && id !== 4066 || len < 60) return;
7462
+ messages++;
7463
+ const r = feedGpsLnavBlock(view, b, assembler);
7464
+ if (r.eph) ephemerides.push(r.eph);
7465
+ else if (r.badParity) badParity++;
7466
+ });
7467
+ return { ephemerides, badParity, messages };
7468
+ }
7469
+
7363
7470
  // src/sbf/rawnav.ts
7364
- var SIGNAL_OF_BLOCK = {
7471
+ var CNAV_SIGNAL_OF_BLOCK = {
7365
7472
  4018: "L2C",
7366
7473
  4019: "L5"
7367
7474
  };
7475
+ function newCnavAssemblers() {
7476
+ return { L2C: new CnavAssembler(), L5: new CnavAssembler() };
7477
+ }
7478
+ function feedCnavBlock(view, b, id, asm) {
7479
+ const signal = CNAV_SIGNAL_OF_BLOCK[id];
7480
+ if (!signal) return {};
7481
+ const msg = new Uint8Array(40);
7482
+ for (let k = 0; k < 10; k++) {
7483
+ const w = view.getUint32(b + 20 + 4 * k, true);
7484
+ msg[4 * k] = w >>> 24;
7485
+ msg[4 * k + 1] = w >>> 16 & 255;
7486
+ msg[4 * k + 2] = w >>> 8 & 255;
7487
+ msg[4 * k + 3] = w & 255;
7488
+ }
7489
+ if (!cnavCrcOk(msg)) return { badCrc: true };
7490
+ const eph = asm[signal].push(msg);
7491
+ return eph ? { eph: { ...eph, signal } } : {};
7492
+ }
7368
7493
  function parseSbfCnav(data) {
7369
7494
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7370
7495
  const ephemerides = [];
7371
- const assemblers = {
7372
- L2C: new CnavAssembler(),
7373
- L5: new CnavAssembler()
7374
- };
7496
+ const asm = newCnavAssemblers();
7375
7497
  let badCrc = 0;
7376
7498
  let messages = 0;
7377
7499
  scanSbfFrames(data, view, (id, b, len) => {
7378
- const signal = SIGNAL_OF_BLOCK[id];
7379
- if (!signal || len < 60) return;
7500
+ if (!CNAV_SIGNAL_OF_BLOCK[id] || len < 60) return;
7380
7501
  messages++;
7381
- const msg = new Uint8Array(40);
7382
- for (let k = 0; k < 10; k++) {
7383
- const w = view.getUint32(b + 20 + 4 * k, true);
7384
- msg[4 * k] = w >>> 24;
7385
- msg[4 * k + 1] = w >>> 16 & 255;
7386
- msg[4 * k + 2] = w >>> 8 & 255;
7387
- msg[4 * k + 3] = w & 255;
7388
- }
7389
- if (!cnavCrcOk(msg)) {
7390
- badCrc++;
7391
- return;
7392
- }
7393
- const eph = assemblers[signal].push(msg);
7394
- if (eph) ephemerides.push({ ...eph, signal });
7502
+ const r = feedCnavBlock(view, b, id, asm);
7503
+ if (r.eph) ephemerides.push(r.eph);
7504
+ else if (r.badCrc) badCrc++;
7395
7505
  });
7396
7506
  return { ephemerides, badCrc, messages };
7397
7507
  }
@@ -7399,50 +7509,51 @@ function parseSbfCnav(data) {
7399
7509
  // src/sbf/rawnav-gal.ts
7400
7510
  var INAV_SOURCES = [17, 21, 22];
7401
7511
  var FNAV_SOURCES = [20, 22];
7512
+ function newGalAssemblers() {
7513
+ return { inav: new GalInavAssembler(), fnav: new GalFnavAssembler() };
7514
+ }
7515
+ function feedGalBlock(data, view, b, id, asm) {
7516
+ const prnStr = svidToPrn(data[b + 14]);
7517
+ if (!prnStr || prnStr[0] !== "E") return {};
7518
+ const prn = parseInt(prnStr.slice(1), 10);
7519
+ const source = data[b + 17] & 31;
7520
+ const page = new Uint8Array(32);
7521
+ for (let k = 0; k < 8; k++) {
7522
+ const w = view.getUint32(b + 20 + 4 * k, true);
7523
+ page[4 * k] = w >>> 24;
7524
+ page[4 * k + 1] = w >>> 16 & 255;
7525
+ page[4 * k + 2] = w >>> 8 & 255;
7526
+ page[4 * k + 3] = w & 255;
7527
+ }
7528
+ let eph;
7529
+ let src;
7530
+ if (id === 4023) {
7531
+ src = "inav";
7532
+ if (!INAV_SOURCES.includes(source)) return {};
7533
+ if (getBitU22(page, 0) !== 0 || getBitU22(page, 114) !== 1) return {};
7534
+ if (!galInavPageCrcOk(page)) return { badCrc: true };
7535
+ eph = asm.inav.push(prn, page);
7536
+ } else {
7537
+ src = "fnav";
7538
+ if (!FNAV_SOURCES.includes(source)) return {};
7539
+ if (isFnavDummy(page)) return {};
7540
+ if (!galFnavPageCrcOk(page)) return { badCrc: true };
7541
+ eph = asm.fnav.push(prn, page);
7542
+ }
7543
+ return eph ? { eph: { ...eph, source: src } } : {};
7544
+ }
7402
7545
  function parseSbfGalNav(data) {
7403
7546
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7404
7547
  const ephemerides = [];
7405
- const inav = new GalInavAssembler();
7406
- const fnav = new GalFnavAssembler();
7548
+ const asm = newGalAssemblers();
7407
7549
  let badCrc = 0;
7408
7550
  let messages = 0;
7409
7551
  scanSbfFrames(data, view, (id, b, len) => {
7410
7552
  if (id !== 4022 && id !== 4023 || len < 52) return;
7411
7553
  messages++;
7412
- const prnStr = svidToPrn(data[b + 14]);
7413
- if (!prnStr || prnStr[0] !== "E") return;
7414
- const prn = parseInt(prnStr.slice(1), 10);
7415
- const source = data[b + 17] & 31;
7416
- const page = new Uint8Array(32);
7417
- for (let k = 0; k < 8; k++) {
7418
- const w = view.getUint32(b + 20 + 4 * k, true);
7419
- page[4 * k] = w >>> 24;
7420
- page[4 * k + 1] = w >>> 16 & 255;
7421
- page[4 * k + 2] = w >>> 8 & 255;
7422
- page[4 * k + 3] = w & 255;
7423
- }
7424
- let eph;
7425
- let src;
7426
- if (id === 4023) {
7427
- src = "inav";
7428
- if (!INAV_SOURCES.includes(source)) return;
7429
- if (getBitU22(page, 0) !== 0 || getBitU22(page, 114) !== 1) return;
7430
- if (!galInavPageCrcOk(page)) {
7431
- badCrc++;
7432
- return;
7433
- }
7434
- eph = inav.push(prn, page);
7435
- } else {
7436
- src = "fnav";
7437
- if (!FNAV_SOURCES.includes(source)) return;
7438
- if (isFnavDummy(page)) return;
7439
- if (!galFnavPageCrcOk(page)) {
7440
- badCrc++;
7441
- return;
7442
- }
7443
- eph = fnav.push(prn, page);
7444
- }
7445
- if (eph) ephemerides.push({ ...eph, source: src });
7554
+ const r = feedGalBlock(data, view, b, id, asm);
7555
+ if (r.eph) ephemerides.push(r.eph);
7556
+ else if (r.badCrc) badCrc++;
7446
7557
  });
7447
7558
  return { ephemerides, badCrc, messages };
7448
7559
  }
@@ -7457,7 +7568,26 @@ function isFnavDummy(page) {
7457
7568
  var GPS_EPOCH_MS10 = Date.UTC(1980, 0, 6);
7458
7569
  var MS_PER_WEEK5 = 7 * 86400 * 1e3;
7459
7570
  var TOW_DNU = 4294967295;
7460
- var WNC_DNU = 65535;
7571
+ var WNC_DNU2 = 65535;
7572
+ function feedBdsBlock(view, b, assembler) {
7573
+ const prn = svidToPrn(view.getUint8(b + 14));
7574
+ if (!prn || prn[0] !== "C") return {};
7575
+ const sf = new Uint8Array(38);
7576
+ for (let k = 0; k < 10; k++) {
7577
+ const w = view.getUint32(b + 20 + 4 * k, true);
7578
+ sf[4 * k] = w >>> 24;
7579
+ if (k < 9) {
7580
+ sf[4 * k + 1] = w >>> 16 & 255;
7581
+ sf[4 * k + 2] = w >>> 8 & 255;
7582
+ sf[4 * k + 3] = w & 255;
7583
+ } else {
7584
+ sf[37] = w >>> 16 & 240;
7585
+ }
7586
+ }
7587
+ if (!bdsSubframeParityOk(sf)) return { badCrc: true };
7588
+ const eph = assembler.push(prn, sf);
7589
+ return eph ? { eph } : {};
7590
+ }
7461
7591
  function parseSbfBdsNav(data) {
7462
7592
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7463
7593
  const ephemerides = [];
@@ -7467,29 +7597,41 @@ function parseSbfBdsNav(data) {
7467
7597
  scanSbfFrames(data, view, (id, b, len) => {
7468
7598
  if (id !== 4047 || len < 60) return;
7469
7599
  messages++;
7470
- const prn = svidToPrn(view.getUint8(b + 14));
7471
- if (!prn || prn[0] !== "C") return;
7472
- const sf = new Uint8Array(38);
7473
- for (let k = 0; k < 10; k++) {
7474
- const w = view.getUint32(b + 20 + 4 * k, true);
7475
- sf[4 * k] = w >>> 24;
7476
- if (k < 9) {
7477
- sf[4 * k + 1] = w >>> 16 & 255;
7478
- sf[4 * k + 2] = w >>> 8 & 255;
7479
- sf[4 * k + 3] = w & 255;
7480
- } else {
7481
- sf[37] = w >>> 16 & 240;
7482
- }
7483
- }
7484
- if (!bdsSubframeParityOk(sf)) {
7485
- badCrc++;
7486
- return;
7487
- }
7488
- const eph = assembler.push(prn, sf);
7489
- if (eph) ephemerides.push(eph);
7600
+ const r = feedBdsBlock(view, b, assembler);
7601
+ if (r.eph) ephemerides.push(r.eph);
7602
+ else if (r.badCrc) badCrc++;
7490
7603
  });
7491
7604
  return { ephemerides, badCrc, messages };
7492
7605
  }
7606
+ function feedGloBlock(view, b, assembler) {
7607
+ const prn = svidToPrn(view.getUint8(b + 14));
7608
+ if (!prn || prn[0] !== "R") return {};
7609
+ const towMs = view.getUint32(b + 8, true);
7610
+ const wnc = view.getUint16(b + 12, true);
7611
+ if (towMs === TOW_DNU || wnc === WNC_DNU2) return {};
7612
+ const str2 = new Uint8Array(11);
7613
+ for (let k = 0; k < 3; k++) {
7614
+ const w = view.getUint32(b + 20 + 4 * k, true);
7615
+ str2[4 * k] = w >>> 24;
7616
+ if (k < 2) {
7617
+ str2[4 * k + 1] = w >>> 16 & 255;
7618
+ str2[4 * k + 2] = w >>> 8 & 255;
7619
+ str2[4 * k + 3] = w & 255;
7620
+ } else {
7621
+ str2[9] = w >>> 16 & 255;
7622
+ str2[10] = w >>> 8 & 248;
7623
+ }
7624
+ }
7625
+ if (!testGloString(str2)) return { badCrc: true };
7626
+ const eph = assembler.push(
7627
+ prn,
7628
+ str2,
7629
+ new Date(GPS_EPOCH_MS10 + wnc * MS_PER_WEEK5 + towMs),
7630
+ view.getUint8(b + 18) - 8
7631
+ // FreqNr, offset by 8
7632
+ );
7633
+ return eph ? { eph } : {};
7634
+ }
7493
7635
  function parseSbfGloNav(data) {
7494
7636
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7495
7637
  const ephemerides = [];
@@ -7499,38 +7641,161 @@ function parseSbfGloNav(data) {
7499
7641
  scanSbfFrames(data, view, (id, b, len) => {
7500
7642
  if (id !== 4026 || len < 32) return;
7501
7643
  messages++;
7502
- const prn = svidToPrn(view.getUint8(b + 14));
7503
- if (!prn || prn[0] !== "R") return;
7504
- const towMs = view.getUint32(b + 8, true);
7505
- const wnc = view.getUint16(b + 12, true);
7506
- if (towMs === TOW_DNU || wnc === WNC_DNU) return;
7507
- const str = new Uint8Array(11);
7508
- for (let k = 0; k < 3; k++) {
7509
- const w = view.getUint32(b + 20 + 4 * k, true);
7510
- str[4 * k] = w >>> 24;
7511
- if (k < 2) {
7512
- str[4 * k + 1] = w >>> 16 & 255;
7513
- str[4 * k + 2] = w >>> 8 & 255;
7514
- str[4 * k + 3] = w & 255;
7515
- } else {
7516
- str[9] = w >>> 16 & 255;
7517
- str[10] = w >>> 8 & 248;
7518
- }
7644
+ const r = feedGloBlock(view, b, assembler);
7645
+ if (r.eph) ephemerides.push(r.eph);
7646
+ else if (r.badCrc) badCrc++;
7647
+ });
7648
+ return { ephemerides, badCrc, messages };
7649
+ }
7650
+
7651
+ // src/sbf/navigation.ts
7652
+ var F4_DNU4 = -2e10;
7653
+ function ephKey(e) {
7654
+ const source = "source" in e ? e.source : "";
7655
+ return `${e.prn}|${e.tocDate.getTime()}|${source}`;
7656
+ }
7657
+ function decodeSbfNavigation(data) {
7658
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7659
+ const ephemerides = [];
7660
+ const seen = /* @__PURE__ */ new Set();
7661
+ const cnav = [];
7662
+ const ionoCorrections = {};
7663
+ let leapSeconds = null;
7664
+ const counts = {
7665
+ decodedNav: 0,
7666
+ gpsLnavRaw: 0,
7667
+ cnavRaw: 0,
7668
+ galRaw: 0,
7669
+ gloRaw: 0,
7670
+ bdsRaw: 0,
7671
+ badFrames: 0
7672
+ };
7673
+ const cnavAsm = newCnavAssemblers();
7674
+ const galAsm = newGalAssemblers();
7675
+ const gloAsm = new GloStringAssembler();
7676
+ const bdsAsm = new BdsAssembler();
7677
+ const lnavAsm = new GpsLnavAssembler();
7678
+ const addEph = (e) => {
7679
+ if (!e) return;
7680
+ const key = ephKey(e);
7681
+ if (seen.has(key)) return;
7682
+ seen.add(key);
7683
+ ephemerides.push(e);
7684
+ };
7685
+ const f4s = (b, n) => {
7686
+ const out = [];
7687
+ for (let k = 0; k < n; k++) {
7688
+ const v = view.getFloat32(b + 4 * k, true);
7689
+ if (v === F4_DNU4) return null;
7690
+ out.push(v);
7519
7691
  }
7520
- if (!testGloString(str)) {
7521
- badCrc++;
7522
- return;
7692
+ return out;
7693
+ };
7694
+ scanSbfFrames(data, view, (id, b, len) => {
7695
+ switch (id) {
7696
+ /* ---- decoded ephemeris blocks ---- */
7697
+ case 5891:
7698
+ if (len >= 140) {
7699
+ counts.decodedNav++;
7700
+ addEph(decodeGpsQzsNav(view, b, "G"));
7701
+ }
7702
+ return;
7703
+ case 4095:
7704
+ if (len >= 140) {
7705
+ counts.decodedNav++;
7706
+ addEph(decodeGpsQzsNav(view, b, "J"));
7707
+ }
7708
+ return;
7709
+ case 4002:
7710
+ if (len >= 149) {
7711
+ counts.decodedNav++;
7712
+ addEph(decodeGalNav(view, b));
7713
+ }
7714
+ return;
7715
+ case 4081:
7716
+ if (len >= 140) {
7717
+ counts.decodedNav++;
7718
+ addEph(decodeBdsNav(view, b));
7719
+ }
7720
+ return;
7721
+ case 4004:
7722
+ if (len >= 96) {
7723
+ counts.decodedNav++;
7724
+ addEph(decodeGloNav(view, b));
7725
+ }
7726
+ return;
7727
+ /* ---- raw navigation-frame blocks ---- */
7728
+ case 4017:
7729
+ // GPSRawCA
7730
+ case 4066: {
7731
+ if (len < 60) return;
7732
+ counts.gpsLnavRaw++;
7733
+ const r = feedGpsLnavBlock(view, b, lnavAsm);
7734
+ if (r.eph) addEph(r.eph);
7735
+ else if (r.badParity) counts.badFrames++;
7736
+ return;
7737
+ }
7738
+ case 4018:
7739
+ // GPSRawL2C
7740
+ case 4019: {
7741
+ if (len < 60) return;
7742
+ counts.cnavRaw++;
7743
+ const r = feedCnavBlock(view, b, id, cnavAsm);
7744
+ if (r.eph) cnav.push(r.eph);
7745
+ else if (r.badCrc) counts.badFrames++;
7746
+ return;
7747
+ }
7748
+ case 4022:
7749
+ // GALRawFNAV
7750
+ case 4023: {
7751
+ if (len < 52) return;
7752
+ counts.galRaw++;
7753
+ const r = feedGalBlock(data, view, b, id, galAsm);
7754
+ if (r.eph) addEph(r.eph);
7755
+ else if (r.badCrc) counts.badFrames++;
7756
+ return;
7757
+ }
7758
+ case 4026: {
7759
+ if (len < 32) return;
7760
+ counts.gloRaw++;
7761
+ const r = feedGloBlock(view, b, gloAsm);
7762
+ if (r.eph) addEph(r.eph);
7763
+ else if (r.badCrc) counts.badFrames++;
7764
+ return;
7765
+ }
7766
+ case 4047: {
7767
+ if (len < 60) return;
7768
+ counts.bdsRaw++;
7769
+ const r = feedBdsBlock(view, b, bdsAsm);
7770
+ if (r.eph) addEph(r.eph);
7771
+ else if (r.badCrc) counts.badFrames++;
7772
+ return;
7773
+ }
7774
+ /* ---- iono / UTC ---- */
7775
+ case 5893:
7776
+ // GPSIon
7777
+ case 4120: {
7778
+ if (len < 48) return;
7779
+ const alpha = f4s(b + 16, 4);
7780
+ const beta = f4s(b + 32, 4);
7781
+ if (!alpha || !beta) return;
7782
+ const sys = id === 5893 ? "GPS" : "BDS";
7783
+ ionoCorrections[`${sys}A`] = alpha;
7784
+ ionoCorrections[`${sys}B`] = beta;
7785
+ return;
7786
+ }
7787
+ case 4030: {
7788
+ if (len < 28) return;
7789
+ const ai = f4s(b + 16, 3);
7790
+ if (ai) ionoCorrections["GAL"] = ai;
7791
+ return;
7792
+ }
7793
+ case 5894:
7794
+ if (len >= 37) leapSeconds = view.getInt8(b + 33);
7795
+ return;
7523
7796
  }
7524
- const eph = assembler.push(
7525
- prn,
7526
- str,
7527
- new Date(GPS_EPOCH_MS10 + wnc * MS_PER_WEEK5 + towMs),
7528
- view.getUint8(b + 18) - 8
7529
- // FreqNr, offset by 8
7530
- );
7531
- if (eph) ephemerides.push(eph);
7532
7797
  });
7533
- return { ephemerides, badCrc, messages };
7798
+ return { ephemerides, cnav, ionoCorrections, leapSeconds, counts };
7534
7799
  }
7535
7800
 
7536
7801
  // src/navbits/rs255.ts
@@ -16398,6 +16663,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16398
16663
  decodeBinexEph,
16399
16664
  decodeEphemeris,
16400
16665
  decodeMsmFull,
16666
+ decodeSbfNavigation,
16401
16667
  deg2dms,
16402
16668
  deg2rad,
16403
16669
  detrendIonoArcs,
@@ -16516,10 +16782,12 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16516
16782
  parseSbfCnav,
16517
16783
  parseSbfGalNav,
16518
16784
  parseSbfGloNav,
16785
+ parseSbfGpsNav,
16519
16786
  parseSbfHas,
16520
16787
  parseSbfIonoUtc,
16521
16788
  parseSbfMeas,
16522
16789
  parseSbfNav,
16790
+ parseSbfReceiverSetup,
16523
16791
  parseSinexBiasDcb,
16524
16792
  parseSourcetable,
16525
16793
  parseSp3,