gnss-js 1.35.0 → 1.37.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,
@@ -156,6 +157,7 @@ __export(src_exports, {
156
157
  extractWidelaneArcs: () => extractWidelaneArcs,
157
158
  fetchSourcetable: () => fetchSourcetable,
158
159
  findSatBias: () => findSatBias,
160
+ fixPppPosition: () => fixPppPosition,
159
161
  fmtD: () => fmtD,
160
162
  fmtF: () => fmtF,
161
163
  formatUTCTime: () => formatUTCTime,
@@ -259,6 +261,7 @@ __export(src_exports, {
259
261
  parseSbfCnav: () => parseSbfCnav,
260
262
  parseSbfGalNav: () => parseSbfGalNav,
261
263
  parseSbfGloNav: () => parseSbfGloNav,
264
+ parseSbfGpsNav: () => parseSbfGpsNav,
262
265
  parseSbfHas: () => parseSbfHas,
263
266
  parseSbfIonoUtc: () => parseSbfIonoUtc,
264
267
  parseSbfMeas: () => parseSbfMeas,
@@ -2698,6 +2701,38 @@ function setBitU(buff, pos, len, data) {
2698
2701
  }
2699
2702
  }
2700
2703
  }
2704
+ var GpsLnavAssembler = class _GpsLnavAssembler {
2705
+ buffers = /* @__PURE__ */ new Map();
2706
+ last = /* @__PURE__ */ new Map();
2707
+ /** Subframe ID (1–5) from the HOW word of a parity-stripped subframe. */
2708
+ static subframeId(subframe) {
2709
+ return getBitU(subframe, 43, 3);
2710
+ }
2711
+ /**
2712
+ * Push one parity-stripped 30-byte LNAV subframe for `prn`, resolving
2713
+ * the 10-bit week against `refWeek`. See {@link LnavPush} for outcomes.
2714
+ */
2715
+ push(prn, subframe, refWeek) {
2716
+ const id = _GpsLnavAssembler.subframeId(subframe);
2717
+ if (id < 1 || id > 3) return { kind: "skipped" };
2718
+ let sf = this.buffers.get(prn);
2719
+ if (!sf) {
2720
+ sf = { buf: new Uint8Array(90), have: 0 };
2721
+ this.buffers.set(prn, sf);
2722
+ }
2723
+ sf.buf.set(subframe, (id - 1) * 30);
2724
+ sf.have |= 1 << id - 1;
2725
+ if (id !== 3 || sf.have !== 7) return { kind: "incomplete" };
2726
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
2727
+ if (!eph) return { kind: "decodeFailed" };
2728
+ const prev = this.last.get(prn);
2729
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
2730
+ return { kind: "duplicate" };
2731
+ }
2732
+ this.last.set(prn, eph);
2733
+ return { kind: "eph", eph };
2734
+ }
2735
+ };
2701
2736
  function decodeGpsLnavFrame(subframes, opts = {}) {
2702
2737
  if (subframes.length < 90) return null;
2703
2738
  const b = subframes;
@@ -5917,38 +5952,19 @@ function parseUbxNav(data, opts = {}) {
5917
5952
  }
5918
5953
  }
5919
5954
  if (refWeek === void 0) return { ephemerides, badParity };
5920
- const subframes = /* @__PURE__ */ new Map();
5921
- const last = /* @__PURE__ */ new Map();
5955
+ const assembler = new GpsLnavAssembler();
5922
5956
  for (const f of ubxFrames(data)) {
5923
5957
  if (f.msgClass !== 2 || f.msgId !== 19) continue;
5924
5958
  const lnav = readLnavSubframe(view, f);
5925
5959
  if (!lnav) continue;
5926
- const { prn, gnssId, svId, buff, id } = lnav;
5960
+ const { prn, buff, id } = lnav;
5927
5961
  if (id < 1 || id > 5) {
5928
5962
  badParity++;
5929
5963
  continue;
5930
5964
  }
5931
- if (id > 3) continue;
5932
- const key = gnssId * 256 + svId;
5933
- let sf = subframes.get(key);
5934
- if (!sf) {
5935
- sf = { buf: new Uint8Array(90), have: 0 };
5936
- subframes.set(key, sf);
5937
- }
5938
- sf.buf.set(buff, (id - 1) * 30);
5939
- sf.have |= 1 << id - 1;
5940
- if (id !== 3 || sf.have !== 7) continue;
5941
- const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
5942
- if (!eph) {
5943
- badParity++;
5944
- continue;
5945
- }
5946
- const prev = last.get(prn);
5947
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
5948
- continue;
5949
- }
5950
- last.set(prn, eph);
5951
- ephemerides.push(eph);
5965
+ const r = assembler.push(prn, buff, refWeek);
5966
+ if (r.kind === "eph") ephemerides.push(r.eph);
5967
+ else if (r.kind === "decodeFailed") badParity++;
5952
5968
  }
5953
5969
  return { ephemerides, badParity };
5954
5970
  }
@@ -7359,38 +7375,78 @@ function parseSbfIonoUtc(data) {
7359
7375
  return { ionoCorrections, leapSeconds };
7360
7376
  }
7361
7377
 
7378
+ // src/sbf/rawnav-gps.ts
7379
+ var WNC_DNU = 65535;
7380
+ function extractSbfLnavSubframe(view, b) {
7381
+ const sf = new Uint8Array(30);
7382
+ for (let k = 0; k < 10; k++) {
7383
+ const w = view.getUint32(b + 20 + 4 * k, true);
7384
+ setBitU(sf, 24 * k, 24, w >>> 6 & 16777215);
7385
+ }
7386
+ return sf;
7387
+ }
7388
+ function feedGpsLnavBlock(view, b, assembler) {
7389
+ const prn = svidToPrn(view.getUint8(b + 14));
7390
+ if (!prn || prn[0] !== "G" && prn[0] !== "J") return {};
7391
+ if (view.getUint8(b + 15) !== 1) return {};
7392
+ const wnc = view.getUint16(b + 12, true);
7393
+ if (wnc === WNC_DNU) return {};
7394
+ const r = assembler.push(prn, extractSbfLnavSubframe(view, b), wnc);
7395
+ if (r.kind === "eph") return { eph: r.eph };
7396
+ if (r.kind === "decodeFailed") return { badParity: true };
7397
+ return {};
7398
+ }
7399
+ function parseSbfGpsNav(data) {
7400
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7401
+ const ephemerides = [];
7402
+ const assembler = new GpsLnavAssembler();
7403
+ let badParity = 0;
7404
+ let messages = 0;
7405
+ scanSbfFrames(data, view, (id, b, len) => {
7406
+ if (id !== 4017 && id !== 4066 || len < 60) return;
7407
+ messages++;
7408
+ const r = feedGpsLnavBlock(view, b, assembler);
7409
+ if (r.eph) ephemerides.push(r.eph);
7410
+ else if (r.badParity) badParity++;
7411
+ });
7412
+ return { ephemerides, badParity, messages };
7413
+ }
7414
+
7362
7415
  // src/sbf/rawnav.ts
7363
- var SIGNAL_OF_BLOCK = {
7416
+ var CNAV_SIGNAL_OF_BLOCK = {
7364
7417
  4018: "L2C",
7365
7418
  4019: "L5"
7366
7419
  };
7420
+ function newCnavAssemblers() {
7421
+ return { L2C: new CnavAssembler(), L5: new CnavAssembler() };
7422
+ }
7423
+ function feedCnavBlock(view, b, id, asm) {
7424
+ const signal = CNAV_SIGNAL_OF_BLOCK[id];
7425
+ if (!signal) return {};
7426
+ const msg = new Uint8Array(40);
7427
+ for (let k = 0; k < 10; k++) {
7428
+ const w = view.getUint32(b + 20 + 4 * k, true);
7429
+ msg[4 * k] = w >>> 24;
7430
+ msg[4 * k + 1] = w >>> 16 & 255;
7431
+ msg[4 * k + 2] = w >>> 8 & 255;
7432
+ msg[4 * k + 3] = w & 255;
7433
+ }
7434
+ if (!cnavCrcOk(msg)) return { badCrc: true };
7435
+ const eph = asm[signal].push(msg);
7436
+ return eph ? { eph: { ...eph, signal } } : {};
7437
+ }
7367
7438
  function parseSbfCnav(data) {
7368
7439
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7369
7440
  const ephemerides = [];
7370
- const assemblers = {
7371
- L2C: new CnavAssembler(),
7372
- L5: new CnavAssembler()
7373
- };
7441
+ const asm = newCnavAssemblers();
7374
7442
  let badCrc = 0;
7375
7443
  let messages = 0;
7376
7444
  scanSbfFrames(data, view, (id, b, len) => {
7377
- const signal = SIGNAL_OF_BLOCK[id];
7378
- if (!signal || len < 60) return;
7445
+ if (!CNAV_SIGNAL_OF_BLOCK[id] || len < 60) return;
7379
7446
  messages++;
7380
- const msg = new Uint8Array(40);
7381
- for (let k = 0; k < 10; k++) {
7382
- const w = view.getUint32(b + 20 + 4 * k, true);
7383
- msg[4 * k] = w >>> 24;
7384
- msg[4 * k + 1] = w >>> 16 & 255;
7385
- msg[4 * k + 2] = w >>> 8 & 255;
7386
- msg[4 * k + 3] = w & 255;
7387
- }
7388
- if (!cnavCrcOk(msg)) {
7389
- badCrc++;
7390
- return;
7391
- }
7392
- const eph = assemblers[signal].push(msg);
7393
- if (eph) ephemerides.push({ ...eph, signal });
7447
+ const r = feedCnavBlock(view, b, id, asm);
7448
+ if (r.eph) ephemerides.push(r.eph);
7449
+ else if (r.badCrc) badCrc++;
7394
7450
  });
7395
7451
  return { ephemerides, badCrc, messages };
7396
7452
  }
@@ -7398,50 +7454,51 @@ function parseSbfCnav(data) {
7398
7454
  // src/sbf/rawnav-gal.ts
7399
7455
  var INAV_SOURCES = [17, 21, 22];
7400
7456
  var FNAV_SOURCES = [20, 22];
7457
+ function newGalAssemblers() {
7458
+ return { inav: new GalInavAssembler(), fnav: new GalFnavAssembler() };
7459
+ }
7460
+ function feedGalBlock(data, view, b, id, asm) {
7461
+ const prnStr = svidToPrn(data[b + 14]);
7462
+ if (!prnStr || prnStr[0] !== "E") return {};
7463
+ const prn = parseInt(prnStr.slice(1), 10);
7464
+ const source = data[b + 17] & 31;
7465
+ const page = new Uint8Array(32);
7466
+ for (let k = 0; k < 8; k++) {
7467
+ const w = view.getUint32(b + 20 + 4 * k, true);
7468
+ page[4 * k] = w >>> 24;
7469
+ page[4 * k + 1] = w >>> 16 & 255;
7470
+ page[4 * k + 2] = w >>> 8 & 255;
7471
+ page[4 * k + 3] = w & 255;
7472
+ }
7473
+ let eph;
7474
+ let src;
7475
+ if (id === 4023) {
7476
+ src = "inav";
7477
+ if (!INAV_SOURCES.includes(source)) return {};
7478
+ if (getBitU22(page, 0) !== 0 || getBitU22(page, 114) !== 1) return {};
7479
+ if (!galInavPageCrcOk(page)) return { badCrc: true };
7480
+ eph = asm.inav.push(prn, page);
7481
+ } else {
7482
+ src = "fnav";
7483
+ if (!FNAV_SOURCES.includes(source)) return {};
7484
+ if (isFnavDummy(page)) return {};
7485
+ if (!galFnavPageCrcOk(page)) return { badCrc: true };
7486
+ eph = asm.fnav.push(prn, page);
7487
+ }
7488
+ return eph ? { eph: { ...eph, source: src } } : {};
7489
+ }
7401
7490
  function parseSbfGalNav(data) {
7402
7491
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7403
7492
  const ephemerides = [];
7404
- const inav = new GalInavAssembler();
7405
- const fnav = new GalFnavAssembler();
7493
+ const asm = newGalAssemblers();
7406
7494
  let badCrc = 0;
7407
7495
  let messages = 0;
7408
7496
  scanSbfFrames(data, view, (id, b, len) => {
7409
7497
  if (id !== 4022 && id !== 4023 || len < 52) return;
7410
7498
  messages++;
7411
- const prnStr = svidToPrn(data[b + 14]);
7412
- if (!prnStr || prnStr[0] !== "E") return;
7413
- const prn = parseInt(prnStr.slice(1), 10);
7414
- const source = data[b + 17] & 31;
7415
- const page = new Uint8Array(32);
7416
- for (let k = 0; k < 8; k++) {
7417
- const w = view.getUint32(b + 20 + 4 * k, true);
7418
- page[4 * k] = w >>> 24;
7419
- page[4 * k + 1] = w >>> 16 & 255;
7420
- page[4 * k + 2] = w >>> 8 & 255;
7421
- page[4 * k + 3] = w & 255;
7422
- }
7423
- let eph;
7424
- let src;
7425
- if (id === 4023) {
7426
- src = "inav";
7427
- if (!INAV_SOURCES.includes(source)) return;
7428
- if (getBitU22(page, 0) !== 0 || getBitU22(page, 114) !== 1) return;
7429
- if (!galInavPageCrcOk(page)) {
7430
- badCrc++;
7431
- return;
7432
- }
7433
- eph = inav.push(prn, page);
7434
- } else {
7435
- src = "fnav";
7436
- if (!FNAV_SOURCES.includes(source)) return;
7437
- if (isFnavDummy(page)) return;
7438
- if (!galFnavPageCrcOk(page)) {
7439
- badCrc++;
7440
- return;
7441
- }
7442
- eph = fnav.push(prn, page);
7443
- }
7444
- if (eph) ephemerides.push({ ...eph, source: src });
7499
+ const r = feedGalBlock(data, view, b, id, asm);
7500
+ if (r.eph) ephemerides.push(r.eph);
7501
+ else if (r.badCrc) badCrc++;
7445
7502
  });
7446
7503
  return { ephemerides, badCrc, messages };
7447
7504
  }
@@ -7456,7 +7513,26 @@ function isFnavDummy(page) {
7456
7513
  var GPS_EPOCH_MS10 = Date.UTC(1980, 0, 6);
7457
7514
  var MS_PER_WEEK5 = 7 * 86400 * 1e3;
7458
7515
  var TOW_DNU = 4294967295;
7459
- var WNC_DNU = 65535;
7516
+ var WNC_DNU2 = 65535;
7517
+ function feedBdsBlock(view, b, assembler) {
7518
+ const prn = svidToPrn(view.getUint8(b + 14));
7519
+ if (!prn || prn[0] !== "C") return {};
7520
+ const sf = new Uint8Array(38);
7521
+ for (let k = 0; k < 10; k++) {
7522
+ const w = view.getUint32(b + 20 + 4 * k, true);
7523
+ sf[4 * k] = w >>> 24;
7524
+ if (k < 9) {
7525
+ sf[4 * k + 1] = w >>> 16 & 255;
7526
+ sf[4 * k + 2] = w >>> 8 & 255;
7527
+ sf[4 * k + 3] = w & 255;
7528
+ } else {
7529
+ sf[37] = w >>> 16 & 240;
7530
+ }
7531
+ }
7532
+ if (!bdsSubframeParityOk(sf)) return { badCrc: true };
7533
+ const eph = assembler.push(prn, sf);
7534
+ return eph ? { eph } : {};
7535
+ }
7460
7536
  function parseSbfBdsNav(data) {
7461
7537
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7462
7538
  const ephemerides = [];
@@ -7466,29 +7542,41 @@ function parseSbfBdsNav(data) {
7466
7542
  scanSbfFrames(data, view, (id, b, len) => {
7467
7543
  if (id !== 4047 || len < 60) return;
7468
7544
  messages++;
7469
- const prn = svidToPrn(view.getUint8(b + 14));
7470
- if (!prn || prn[0] !== "C") return;
7471
- const sf = new Uint8Array(38);
7472
- for (let k = 0; k < 10; k++) {
7473
- const w = view.getUint32(b + 20 + 4 * k, true);
7474
- sf[4 * k] = w >>> 24;
7475
- if (k < 9) {
7476
- sf[4 * k + 1] = w >>> 16 & 255;
7477
- sf[4 * k + 2] = w >>> 8 & 255;
7478
- sf[4 * k + 3] = w & 255;
7479
- } else {
7480
- sf[37] = w >>> 16 & 240;
7481
- }
7482
- }
7483
- if (!bdsSubframeParityOk(sf)) {
7484
- badCrc++;
7485
- return;
7486
- }
7487
- const eph = assembler.push(prn, sf);
7488
- if (eph) ephemerides.push(eph);
7545
+ const r = feedBdsBlock(view, b, assembler);
7546
+ if (r.eph) ephemerides.push(r.eph);
7547
+ else if (r.badCrc) badCrc++;
7489
7548
  });
7490
7549
  return { ephemerides, badCrc, messages };
7491
7550
  }
7551
+ function feedGloBlock(view, b, assembler) {
7552
+ const prn = svidToPrn(view.getUint8(b + 14));
7553
+ if (!prn || prn[0] !== "R") return {};
7554
+ const towMs = view.getUint32(b + 8, true);
7555
+ const wnc = view.getUint16(b + 12, true);
7556
+ if (towMs === TOW_DNU || wnc === WNC_DNU2) return {};
7557
+ const str = new Uint8Array(11);
7558
+ for (let k = 0; k < 3; k++) {
7559
+ const w = view.getUint32(b + 20 + 4 * k, true);
7560
+ str[4 * k] = w >>> 24;
7561
+ if (k < 2) {
7562
+ str[4 * k + 1] = w >>> 16 & 255;
7563
+ str[4 * k + 2] = w >>> 8 & 255;
7564
+ str[4 * k + 3] = w & 255;
7565
+ } else {
7566
+ str[9] = w >>> 16 & 255;
7567
+ str[10] = w >>> 8 & 248;
7568
+ }
7569
+ }
7570
+ if (!testGloString(str)) return { badCrc: true };
7571
+ const eph = assembler.push(
7572
+ prn,
7573
+ str,
7574
+ new Date(GPS_EPOCH_MS10 + wnc * MS_PER_WEEK5 + towMs),
7575
+ view.getUint8(b + 18) - 8
7576
+ // FreqNr, offset by 8
7577
+ );
7578
+ return eph ? { eph } : {};
7579
+ }
7492
7580
  function parseSbfGloNav(data) {
7493
7581
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7494
7582
  const ephemerides = [];
@@ -7498,38 +7586,161 @@ function parseSbfGloNav(data) {
7498
7586
  scanSbfFrames(data, view, (id, b, len) => {
7499
7587
  if (id !== 4026 || len < 32) return;
7500
7588
  messages++;
7501
- const prn = svidToPrn(view.getUint8(b + 14));
7502
- if (!prn || prn[0] !== "R") return;
7503
- const towMs = view.getUint32(b + 8, true);
7504
- const wnc = view.getUint16(b + 12, true);
7505
- if (towMs === TOW_DNU || wnc === WNC_DNU) return;
7506
- const str = new Uint8Array(11);
7507
- for (let k = 0; k < 3; k++) {
7508
- const w = view.getUint32(b + 20 + 4 * k, true);
7509
- str[4 * k] = w >>> 24;
7510
- if (k < 2) {
7511
- str[4 * k + 1] = w >>> 16 & 255;
7512
- str[4 * k + 2] = w >>> 8 & 255;
7513
- str[4 * k + 3] = w & 255;
7514
- } else {
7515
- str[9] = w >>> 16 & 255;
7516
- str[10] = w >>> 8 & 248;
7517
- }
7589
+ const r = feedGloBlock(view, b, assembler);
7590
+ if (r.eph) ephemerides.push(r.eph);
7591
+ else if (r.badCrc) badCrc++;
7592
+ });
7593
+ return { ephemerides, badCrc, messages };
7594
+ }
7595
+
7596
+ // src/sbf/navigation.ts
7597
+ var F4_DNU3 = -2e10;
7598
+ function ephKey(e) {
7599
+ const source = "source" in e ? e.source : "";
7600
+ return `${e.prn}|${e.tocDate.getTime()}|${source}`;
7601
+ }
7602
+ function decodeSbfNavigation(data) {
7603
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7604
+ const ephemerides = [];
7605
+ const seen = /* @__PURE__ */ new Set();
7606
+ const cnav = [];
7607
+ const ionoCorrections = {};
7608
+ let leapSeconds = null;
7609
+ const counts = {
7610
+ decodedNav: 0,
7611
+ gpsLnavRaw: 0,
7612
+ cnavRaw: 0,
7613
+ galRaw: 0,
7614
+ gloRaw: 0,
7615
+ bdsRaw: 0,
7616
+ badFrames: 0
7617
+ };
7618
+ const cnavAsm = newCnavAssemblers();
7619
+ const galAsm = newGalAssemblers();
7620
+ const gloAsm = new GloStringAssembler();
7621
+ const bdsAsm = new BdsAssembler();
7622
+ const lnavAsm = new GpsLnavAssembler();
7623
+ const addEph = (e) => {
7624
+ if (!e) return;
7625
+ const key = ephKey(e);
7626
+ if (seen.has(key)) return;
7627
+ seen.add(key);
7628
+ ephemerides.push(e);
7629
+ };
7630
+ const f4s = (b, n) => {
7631
+ const out = [];
7632
+ for (let k = 0; k < n; k++) {
7633
+ const v = view.getFloat32(b + 4 * k, true);
7634
+ if (v === F4_DNU3) return null;
7635
+ out.push(v);
7518
7636
  }
7519
- if (!testGloString(str)) {
7520
- badCrc++;
7521
- return;
7637
+ return out;
7638
+ };
7639
+ scanSbfFrames(data, view, (id, b, len) => {
7640
+ switch (id) {
7641
+ /* ---- decoded ephemeris blocks ---- */
7642
+ case 5891:
7643
+ if (len >= 140) {
7644
+ counts.decodedNav++;
7645
+ addEph(decodeGpsQzsNav(view, b, "G"));
7646
+ }
7647
+ return;
7648
+ case 4095:
7649
+ if (len >= 140) {
7650
+ counts.decodedNav++;
7651
+ addEph(decodeGpsQzsNav(view, b, "J"));
7652
+ }
7653
+ return;
7654
+ case 4002:
7655
+ if (len >= 149) {
7656
+ counts.decodedNav++;
7657
+ addEph(decodeGalNav(view, b));
7658
+ }
7659
+ return;
7660
+ case 4081:
7661
+ if (len >= 140) {
7662
+ counts.decodedNav++;
7663
+ addEph(decodeBdsNav(view, b));
7664
+ }
7665
+ return;
7666
+ case 4004:
7667
+ if (len >= 96) {
7668
+ counts.decodedNav++;
7669
+ addEph(decodeGloNav(view, b));
7670
+ }
7671
+ return;
7672
+ /* ---- raw navigation-frame blocks ---- */
7673
+ case 4017:
7674
+ // GPSRawCA
7675
+ case 4066: {
7676
+ if (len < 60) return;
7677
+ counts.gpsLnavRaw++;
7678
+ const r = feedGpsLnavBlock(view, b, lnavAsm);
7679
+ if (r.eph) addEph(r.eph);
7680
+ else if (r.badParity) counts.badFrames++;
7681
+ return;
7682
+ }
7683
+ case 4018:
7684
+ // GPSRawL2C
7685
+ case 4019: {
7686
+ if (len < 60) return;
7687
+ counts.cnavRaw++;
7688
+ const r = feedCnavBlock(view, b, id, cnavAsm);
7689
+ if (r.eph) cnav.push(r.eph);
7690
+ else if (r.badCrc) counts.badFrames++;
7691
+ return;
7692
+ }
7693
+ case 4022:
7694
+ // GALRawFNAV
7695
+ case 4023: {
7696
+ if (len < 52) return;
7697
+ counts.galRaw++;
7698
+ const r = feedGalBlock(data, view, b, id, galAsm);
7699
+ if (r.eph) addEph(r.eph);
7700
+ else if (r.badCrc) counts.badFrames++;
7701
+ return;
7702
+ }
7703
+ case 4026: {
7704
+ if (len < 32) return;
7705
+ counts.gloRaw++;
7706
+ const r = feedGloBlock(view, b, gloAsm);
7707
+ if (r.eph) addEph(r.eph);
7708
+ else if (r.badCrc) counts.badFrames++;
7709
+ return;
7710
+ }
7711
+ case 4047: {
7712
+ if (len < 60) return;
7713
+ counts.bdsRaw++;
7714
+ const r = feedBdsBlock(view, b, bdsAsm);
7715
+ if (r.eph) addEph(r.eph);
7716
+ else if (r.badCrc) counts.badFrames++;
7717
+ return;
7718
+ }
7719
+ /* ---- iono / UTC ---- */
7720
+ case 5893:
7721
+ // GPSIon
7722
+ case 4120: {
7723
+ if (len < 48) return;
7724
+ const alpha = f4s(b + 16, 4);
7725
+ const beta = f4s(b + 32, 4);
7726
+ if (!alpha || !beta) return;
7727
+ const sys = id === 5893 ? "GPS" : "BDS";
7728
+ ionoCorrections[`${sys}A`] = alpha;
7729
+ ionoCorrections[`${sys}B`] = beta;
7730
+ return;
7731
+ }
7732
+ case 4030: {
7733
+ if (len < 28) return;
7734
+ const ai = f4s(b + 16, 3);
7735
+ if (ai) ionoCorrections["GAL"] = ai;
7736
+ return;
7737
+ }
7738
+ case 5894:
7739
+ if (len >= 37) leapSeconds = view.getInt8(b + 33);
7740
+ return;
7522
7741
  }
7523
- const eph = assembler.push(
7524
- prn,
7525
- str,
7526
- new Date(GPS_EPOCH_MS10 + wnc * MS_PER_WEEK5 + towMs),
7527
- view.getUint8(b + 18) - 8
7528
- // FreqNr, offset by 8
7529
- );
7530
- if (eph) ephemerides.push(eph);
7531
7742
  });
7532
- return { ephemerides, badCrc, messages };
7743
+ return { ephemerides, cnav, ionoCorrections, leapSeconds, counts };
7533
7744
  }
7534
7745
 
7535
7746
  // src/navbits/rs255.ts
@@ -13357,6 +13568,28 @@ function solvePpp(epochs, sp3, opts) {
13357
13568
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
13358
13569
  });
13359
13570
  }
13571
+ let finalState;
13572
+ if (opts.exposeState) {
13573
+ const ambs = [];
13574
+ for (const [prn, index] of ambIdx) {
13575
+ const s = arcSnap.get(prn);
13576
+ if (!s || s.nEpochs < 10) continue;
13577
+ ambs.push({
13578
+ prn,
13579
+ index,
13580
+ aIF: x[index],
13581
+ mwCyc: mwState.get(prn)?.mean ?? s.mwCyc,
13582
+ f1: s.f1,
13583
+ f2: s.f2,
13584
+ elevDeg: s.elevSum / s.nEpochs
13585
+ });
13586
+ }
13587
+ finalState = {
13588
+ position: [x[IDX_X], x[IDX_Y], x[IDX_Z]],
13589
+ covariance: P.map((row) => row.slice()),
13590
+ ambiguities: ambs
13591
+ };
13592
+ }
13360
13593
  for (const prn of [...arcSnap.keys()]) flushArc(prn);
13361
13594
  const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
13362
13595
  const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
@@ -13373,7 +13606,8 @@ function solvePpp(epochs, sp3, opts) {
13373
13606
  epochsUsed: epochs.length,
13374
13607
  convergenceSec,
13375
13608
  finalError3d,
13376
- arcs
13609
+ arcs,
13610
+ finalState
13377
13611
  };
13378
13612
  }
13379
13613
 
@@ -13466,6 +13700,117 @@ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
13466
13700
  rejectedWl
13467
13701
  };
13468
13702
  }
13703
+ function solveSpd(A, b) {
13704
+ const n = b.length;
13705
+ const L = Array.from({ length: n }, () => new Array(n).fill(0));
13706
+ for (let i = 0; i < n; i++) {
13707
+ for (let j = 0; j <= i; j++) {
13708
+ let s = A[i][j];
13709
+ for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
13710
+ if (i === j) {
13711
+ if (s <= 0) return null;
13712
+ L[i][j] = Math.sqrt(s);
13713
+ } else {
13714
+ L[i][j] = s / L[j][j];
13715
+ }
13716
+ }
13717
+ }
13718
+ const y = new Array(n).fill(0);
13719
+ for (let i = 0; i < n; i++) {
13720
+ let s = b[i];
13721
+ for (let k = 0; k < i; k++) s -= L[i][k] * y[k];
13722
+ y[i] = s / L[i][i];
13723
+ }
13724
+ const x = new Array(n).fill(0);
13725
+ for (let i = n - 1; i >= 0; i--) {
13726
+ let s = y[i];
13727
+ for (let k = i + 1; k < n; k++) s -= L[k][i] * x[k];
13728
+ x[i] = s / L[i][i];
13729
+ }
13730
+ return x;
13731
+ }
13732
+ function fixPppPosition(state, fcb, opts = {}) {
13733
+ const floatPos = state.position;
13734
+ const noFix = {
13735
+ fixed: false,
13736
+ position: floatPos,
13737
+ floatPosition: floatPos,
13738
+ shift: [0, 0, 0],
13739
+ nFixed: 0,
13740
+ ratio: 0,
13741
+ refPrn: null
13742
+ };
13743
+ const amb = state.ambiguities;
13744
+ if (amb.length < 3) return noFix;
13745
+ const sats = amb.map((a) => ({
13746
+ prn: a.prn,
13747
+ aIF: a.aIF,
13748
+ mwCyc: a.mwCyc,
13749
+ f1: a.f1,
13750
+ f2: a.f2,
13751
+ wlBiasCyc: fcb.satWlFcb.get(a.prn) ?? 0,
13752
+ nlBiasCyc: fcb.satNlFcb.get(a.prn) ?? 0,
13753
+ elevDeg: a.elevDeg
13754
+ }));
13755
+ const n = sats.length;
13756
+ const Qaif = new Float64Array(n * n);
13757
+ for (let i = 0; i < n; i++)
13758
+ for (let j = 0; j < n; j++)
13759
+ Qaif[i * n + j] = state.covariance[amb[i].index][amb[j].index];
13760
+ const res = resolvePppAmbiguities(sats, Qaif, opts);
13761
+ if (!res.fixed || res.refPrn === null) {
13762
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
13763
+ }
13764
+ const idxOfPrn = new Map(amb.map((a) => [a.prn, a.index]));
13765
+ const floatOfPrn = new Map(amb.map((a) => [a.prn, a.aIF]));
13766
+ const kept = res.sats.filter((s) => idxOfPrn.has(s.prn));
13767
+ const refPos = kept.findIndex((s) => s.prn === res.refPrn);
13768
+ if (refPos < 0 || kept.length < 2) {
13769
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
13770
+ }
13771
+ const ambIdx = kept.map((s) => idxOfPrn.get(s.prn));
13772
+ const aFloat = kept.map((s) => floatOfPrn.get(s.prn));
13773
+ const aFixed = kept.map((s) => s.aIF);
13774
+ const others = kept.map((_, i) => i).filter((i) => i !== refPos);
13775
+ const m = others.length;
13776
+ const P = state.covariance;
13777
+ const qAA = (i, j) => P[ambIdx[i]][ambIdx[j]];
13778
+ const qXA = (r, i) => P[r][ambIdx[i]];
13779
+ const Qzz = Array.from({ length: m }, () => new Array(m).fill(0));
13780
+ const z = new Array(m).fill(0);
13781
+ for (let a = 0; a < m; a++) {
13782
+ const ia = others[a];
13783
+ z[a] = aFloat[ia] - aFloat[refPos] - (aFixed[ia] - aFixed[refPos]);
13784
+ for (let b = 0; b < m; b++) {
13785
+ const ib = others[b];
13786
+ Qzz[a][b] = qAA(ia, ib) - qAA(ia, refPos) - qAA(refPos, ib) + qAA(refPos, refPos);
13787
+ }
13788
+ }
13789
+ const Qxz = [0, 1, 2].map(
13790
+ (r) => others.map((ia) => qXA(r, ia) - qXA(r, refPos))
13791
+ );
13792
+ const y = solveSpd(Qzz, z);
13793
+ if (!y) return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
13794
+ const shift = [0, 0, 0];
13795
+ for (let r = 0; r < 3; r++) {
13796
+ let s = 0;
13797
+ for (let a = 0; a < m; a++) s += Qxz[r][a] * y[a];
13798
+ shift[r] = -s;
13799
+ }
13800
+ return {
13801
+ fixed: true,
13802
+ position: [
13803
+ floatPos[0] + shift[0],
13804
+ floatPos[1] + shift[1],
13805
+ floatPos[2] + shift[2]
13806
+ ],
13807
+ floatPosition: floatPos,
13808
+ shift,
13809
+ nFixed: m,
13810
+ ratio: res.ratio,
13811
+ refPrn: res.refPrn
13812
+ };
13813
+ }
13469
13814
 
13470
13815
  // src/positioning/ppp-fcb.ts
13471
13816
  function extractWidelaneArcs(station, epochs, opts = {}) {
@@ -16263,6 +16608,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16263
16608
  decodeBinexEph,
16264
16609
  decodeEphemeris,
16265
16610
  decodeMsmFull,
16611
+ decodeSbfNavigation,
16266
16612
  deg2dms,
16267
16613
  deg2rad,
16268
16614
  detrendIonoArcs,
@@ -16277,6 +16623,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16277
16623
  extractWidelaneArcs,
16278
16624
  fetchSourcetable,
16279
16625
  findSatBias,
16626
+ fixPppPosition,
16280
16627
  fmtD,
16281
16628
  fmtF,
16282
16629
  formatUTCTime,
@@ -16380,6 +16727,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16380
16727
  parseSbfCnav,
16381
16728
  parseSbfGalNav,
16382
16729
  parseSbfGloNav,
16730
+ parseSbfGpsNav,
16383
16731
  parseSbfHas,
16384
16732
  parseSbfIonoUtc,
16385
16733
  parseSbfMeas,