gnss-js 1.56.0 → 1.58.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.
@@ -1270,6 +1270,17 @@ var DEGF = [
1270
1270
  58e-4
1271
1271
  ];
1272
1272
  var degfcorr = (ai) => ai > 0 && ai <= 15 ? DEGF[ai] : 58e-4;
1273
+ function sbasLongTermDeg(d, vel, ageSec) {
1274
+ if (vel) {
1275
+ if (ageSec > 0 && ageSec < d.iltcV1) return 0;
1276
+ return d.cltcLsb + d.cltcV1 * Math.max(0, -ageSec, ageSec - d.iltcV1);
1277
+ }
1278
+ return d.cltcV0 * Math.floor(Math.abs(ageSec) / d.iltcV0);
1279
+ }
1280
+ function sbasIonoDeg(d, ageSec) {
1281
+ const dt = Math.abs(ageSec);
1282
+ return d.cionoStep * Math.floor(dt / d.iiono) + d.cionoRamp * dt;
1283
+ }
1273
1284
  var x1 = [-75, -65, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 65, 75, 85];
1274
1285
  var x2 = [-55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
1275
1286
  var x3 = [-75, -65, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 65, 75];
@@ -1323,6 +1334,7 @@ var SbasProcessor = class {
1323
1334
  satIdx = /* @__PURE__ */ new Map();
1324
1335
  iodp = -1;
1325
1336
  tlat = 0;
1337
+ degr = null;
1326
1338
  ion = [];
1327
1339
  /** GEO PRN(s) whose messages have been ingested. */
1328
1340
  geoPrns = /* @__PURE__ */ new Set();
@@ -1349,6 +1361,8 @@ var SbasProcessor = class {
1349
1361
  return this.decodeIntegrity(msg) ? type : -1;
1350
1362
  case 7:
1351
1363
  return this.decodeDegradation(msg) ? type : -1;
1364
+ case 10:
1365
+ return this.decodeMt10(msg) ? type : -1;
1352
1366
  case 18:
1353
1367
  return this.decodeIgpMask(msg) ? type : -1;
1354
1368
  case 24:
@@ -1417,6 +1431,30 @@ var SbasProcessor = class {
1417
1431
  }
1418
1432
  return true;
1419
1433
  }
1434
+ /** MT10 degradation factors (DO-229D Table A-9). Fields follow the 8-bit
1435
+ * preamble + 6-bit type (start bit 14), MSB-first, with the listed LSBs.
1436
+ * Iiono / Iltc_v0 of 0 must be read as 1 (Table A-9, Note 3). */
1437
+ decodeMt10(msg) {
1438
+ const u = (p, n) => getBitU(msg, p, n);
1439
+ this.degr = {
1440
+ brrc: u(14, 10) * 2e-3,
1441
+ cltcLsb: u(24, 10) * 2e-3,
1442
+ cltcV1: u(34, 10) * 5e-5,
1443
+ iltcV1: u(44, 9),
1444
+ cltcV0: u(53, 10) * 2e-3,
1445
+ iltcV0: u(63, 9) || 1,
1446
+ cgeoLsb: u(72, 10) * 5e-4,
1447
+ cgeoV: u(82, 10) * 5e-5,
1448
+ igeo: u(92, 9),
1449
+ cer: u(101, 6) * 0.5,
1450
+ cionoStep: u(107, 10) * 1e-3,
1451
+ iiono: u(117, 9) || 1,
1452
+ cionoRamp: u(126, 10) * 5e-6,
1453
+ rssUdre: u(136, 1) === 1,
1454
+ rssIono: u(137, 1) === 1
1455
+ };
1456
+ return true;
1457
+ }
1420
1458
  decodeMixed(msg, week, tow) {
1421
1459
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
1422
1460
  const blk = getBitU(msg, 112, 2);
@@ -1463,7 +1501,8 @@ var SbasProcessor = class {
1463
1501
  dvel: [0, 0, 0],
1464
1502
  daf0: getBitS(msg, p + 41, 10) * P2_31,
1465
1503
  daf1: 0,
1466
- t0s: gpsSeconds(week, tow)
1504
+ t0s: gpsSeconds(week, tow),
1505
+ vel: false
1467
1506
  };
1468
1507
  return true;
1469
1508
  }
@@ -1487,7 +1526,8 @@ var SbasProcessor = class {
1487
1526
  ],
1488
1527
  daf0: getBitS(msg, p + 47, 11) * P2_31,
1489
1528
  daf1: getBitS(msg, p + 82, 8) * P2_39,
1490
- t0s: gpsSeconds(week, tow + t)
1529
+ t0s: gpsSeconds(week, tow + t),
1530
+ vel: true
1491
1531
  };
1492
1532
  return true;
1493
1533
  }
@@ -1608,9 +1648,16 @@ var SbasProcessor = class {
1608
1648
  const tf = now - s.fcorr.t0s + this.tlat;
1609
1649
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
1610
1650
  const prc = s.fcorr.prc;
1611
- const varM2 = varfcorr(s.fcorr.udre) + degfcorr(s.fcorr.ai) * tf * tf / 2;
1651
+ const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre));
1652
+ const efc = degfcorr(s.fcorr.ai) * tf * tf / 2;
1653
+ const eltc = this.degr && s.lcorr && s.lcorr.t0s !== 0 ? sbasLongTermDeg(this.degr, s.lcorr.vel, now - s.lcorr.t0s) : 0;
1654
+ const varM2 = this.degr?.rssUdre ? sigUdre * sigUdre + efc * efc + eltc * eltc : (sigUdre + efc + eltc) ** 2;
1612
1655
  return { dPos: drs, dClkS: dclk + prc / C_LIGHT, prcM: prc, varM2, iode };
1613
1656
  }
1657
+ /** The last-decoded MT10 degradation factors, or null if none seen. */
1658
+ get degradation() {
1659
+ return this.degr;
1660
+ }
1614
1661
  /**
1615
1662
  * Slant L1 ionospheric delay (m) at the pierce point for a receiver geodetic
1616
1663
  * position (lat/lon rad, height m) and a satellite az/el (rad), interpolated
@@ -1659,14 +1706,18 @@ var SbasProcessor = class {
1659
1706
  } else return null;
1660
1707
  const now = gpsSeconds(week, tow);
1661
1708
  let delay = 0;
1662
- let varM2 = 0;
1709
+ let varVert = 0;
1663
1710
  for (let i = 0; i < 4; i++) {
1664
1711
  const g = pts[i];
1665
1712
  if (!g) continue;
1666
1713
  delay += w[i] * g.delay;
1667
- varM2 += w[i] * varicorr(g.give) * 9e-8 * Math.abs(now - g.t0s);
1714
+ const sigGive = Math.sqrt(varicorr(g.give));
1715
+ const d = this.degr;
1716
+ const eiono = d ? sbasIonoDeg(d, now - g.t0s) : 0;
1717
+ const sig2 = d?.rssIono ? sigGive * sigGive + eiono * eiono : (sigGive + eiono) ** 2;
1718
+ varVert += w[i] * sig2;
1668
1719
  }
1669
- return { delayM: delay * fp, varM2: varM2 * fp * fp };
1720
+ return { delayM: delay * fp, varM2: varVert * fp * fp };
1670
1721
  }
1671
1722
  /** Find the four IGPs bracketing a pierce point (RTKLIB `searchigp`). */
1672
1723
  searchIgp(latP, lonP) {
@@ -3081,10 +3132,10 @@ function sagnac2(pos, travelTimeS) {
3081
3132
  function sbasAirTropoVar(elRad) {
3082
3133
  const elDeg = elRad * 180 / Math.PI;
3083
3134
  const sigMp = 0.13 + 0.53 * Math.exp(-elDeg / 10);
3084
- const sigNoise = 0.15 + 0.43 * Math.exp(-elDeg / 6.9);
3135
+ const NOISE_DIVG_AADA = 0.36;
3085
3136
  const m = 1.001 / Math.sqrt(2001e-6 + Math.sin(elRad) * Math.sin(elRad));
3086
3137
  const sigTropo = 0.12 * m;
3087
- return sigMp * sigMp + sigNoise * sigNoise + sigTropo * sigTropo;
3138
+ return sigMp * sigMp + NOISE_DIVG_AADA * NOISE_DIVG_AADA + sigTropo * sigTropo;
3088
3139
  }
3089
3140
  function invertMatrix(A) {
3090
3141
  const n = A.length;
@@ -3380,6 +3431,8 @@ export {
3380
3431
  solveDgnss,
3381
3432
  RtkFloatEngine,
3382
3433
  postProcessRtk,
3434
+ sbasLongTermDeg,
3435
+ sbasIonoDeg,
3383
3436
  SbasProcessor,
3384
3437
  niellMapping,
3385
3438
  gmst,
@@ -756,13 +756,42 @@ function parseSbfGloNav(data) {
756
756
  return { ephemerides, badCrc, messages };
757
757
  }
758
758
 
759
+ // src/navbits/sbas-l5.ts
760
+ var SBAS_L5_PREAMBLE_UW = 6056250;
761
+ function isSbasL1Preamble(msg) {
762
+ return msg[0] === 83 || msg[0] === 154 || msg[0] === 198;
763
+ }
764
+ function sbasL5MessageType(msg) {
765
+ return getBitU(msg, 4, 6);
766
+ }
767
+ var SBAS_L5_MT_NAMES = {
768
+ 0: "Do Not Use (test)",
769
+ 31: "SBAS satellite mask",
770
+ 32: "Satellite clock-ephemeris corrections + covariance",
771
+ 34: "Integrity (DFREI/DFRECI)",
772
+ 35: "Integrity (DFREI/DFRECI)",
773
+ 36: "Integrity (DFREI/DFRECI)",
774
+ 37: "Degradation parameters + DFREI scale table",
775
+ 39: "SBAS satellite clock/ephemeris/covariance-1",
776
+ 40: "SBAS satellite clock/ephemeris/covariance-2",
777
+ 42: "SNT-to-UTC offset",
778
+ 47: "SBAS satellite almanacs",
779
+ 62: "Reserved",
780
+ 63: "Null message"
781
+ };
782
+ function sbasL5MessageName(type) {
783
+ return SBAS_L5_MT_NAMES[type] ?? (type >= 1 && type <= 61 ? "Spare" : `Type ${type}`);
784
+ }
785
+ function isSbasL5Correction(type) {
786
+ return type === 31 || type === 32 || type >= 34 && type <= 37 || type === 39 || type === 40 || type === 42 || type === 47;
787
+ }
788
+
759
789
  // src/sbf/rawnav-sbas.ts
760
- function feedGeoBlock(view, b, onSbasMessage) {
790
+ function readGeoMessage(view, b) {
761
791
  const tow = view.getUint32(b + 8, true);
762
792
  const wnc = view.getUint16(b + 12, true);
763
- const svid = view.getUint8(b + 14);
764
- const prnStr = svidToPrn(svid);
765
- if (!prnStr || prnStr[0] !== "S") return {};
793
+ const prnStr = svidToPrn(view.getUint8(b + 14));
794
+ if (!prnStr || prnStr[0] !== "S") return null;
766
795
  const msg = new Uint8Array(32);
767
796
  for (let k = 0; k < 8; k++) {
768
797
  const w = view.getUint32(b + 20 + 4 * k, true);
@@ -771,12 +800,29 @@ function feedGeoBlock(view, b, onSbasMessage) {
771
800
  msg[4 * k + 2] = w >>> 8 & 255;
772
801
  msg[4 * k + 3] = w & 255;
773
802
  }
774
- if (!sbasCrcOk(msg)) return { badCrc: true };
775
- const prn = Number(prnStr.slice(1)) + 100;
776
- onSbasMessage?.(msg, prn, wnc, tow / 1e3);
777
- const eph = decodeSbasGeoNav(msg, prn, wnc, tow / 1e3);
803
+ return { msg, prn: Number(prnStr.slice(1)) + 100, wnc, towSec: tow / 1e3 };
804
+ }
805
+ function feedGeoBlock(view, b, onSbasMessage) {
806
+ const g = readGeoMessage(view, b);
807
+ if (!g) return {};
808
+ if (!sbasCrcOk(g.msg)) return { badCrc: true };
809
+ onSbasMessage?.(g.msg, g.prn, g.wnc, g.towSec);
810
+ const eph = decodeSbasGeoNav(g.msg, g.prn, g.wnc, g.towSec);
778
811
  return eph ? { eph } : {};
779
812
  }
813
+ function feedGeoL5Block(view, b, onSbasMessage, onDfmcMessage) {
814
+ const g = readGeoMessage(view, b);
815
+ if (!g) return {};
816
+ if (!sbasCrcOk(g.msg)) return { badCrc: true };
817
+ if (isSbasL1Preamble(g.msg)) {
818
+ onSbasMessage?.(g.msg, g.prn, g.wnc, g.towSec);
819
+ const eph = decodeSbasGeoNav(g.msg, g.prn, g.wnc, g.towSec);
820
+ return eph ? { kind: "l1", eph } : { kind: "l1" };
821
+ }
822
+ const dfmcType = sbasL5MessageType(g.msg);
823
+ onDfmcMessage?.(g.msg, g.prn, g.wnc, g.towSec, dfmcType);
824
+ return { kind: "l5", dfmcType };
825
+ }
780
826
  function parseSbfGeoNav(data, opts = {}) {
781
827
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
782
828
  const ephemerides = [];
@@ -799,6 +845,47 @@ function parseSbfGeoNav(data, opts = {}) {
799
845
  });
800
846
  return { ephemerides, badCrc, messages };
801
847
  }
848
+ function parseSbfGeoL5(data, opts = {}) {
849
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
850
+ const ephemerides = [];
851
+ const seen = /* @__PURE__ */ new Set();
852
+ const byType = {};
853
+ const dfmcPrns = /* @__PURE__ */ new Set();
854
+ let legacyMessages = 0;
855
+ let dfmcMessages = 0;
856
+ let badCrc = 0;
857
+ let messages = 0;
858
+ scanSbfFrames(data, view, (id, b, len) => {
859
+ if (id !== 4021 || len < 52) return;
860
+ messages++;
861
+ const r = feedGeoL5Block(view, b, opts.onSbasMessage, opts.onDfmcMessage);
862
+ if (r.badCrc) {
863
+ badCrc++;
864
+ } else if (r.kind === "l1") {
865
+ legacyMessages++;
866
+ if (r.eph) {
867
+ const key = `${r.eph.prn}|${r.eph.tocDate.getTime()}`;
868
+ if (!seen.has(key)) {
869
+ seen.add(key);
870
+ ephemerides.push(r.eph);
871
+ }
872
+ }
873
+ } else if (r.kind === "l5" && r.dfmcType !== void 0) {
874
+ dfmcMessages++;
875
+ byType[r.dfmcType] = (byType[r.dfmcType] ?? 0) + 1;
876
+ const prn = view.getUint8(b + 14);
877
+ const s = svidToPrn(prn);
878
+ if (s) dfmcPrns.add(s);
879
+ }
880
+ });
881
+ return {
882
+ ephemerides,
883
+ legacyMessages,
884
+ dfmc: { byType, prns: [...dfmcPrns].sort(), messages: dfmcMessages },
885
+ badCrc,
886
+ messages
887
+ };
888
+ }
802
889
 
803
890
  // src/sbf/navigation.ts
804
891
  var F4_DNU4 = -2e10;
@@ -821,8 +908,11 @@ function decodeSbfNavigation(data, opts = {}) {
821
908
  gloRaw: 0,
822
909
  bdsRaw: 0,
823
910
  sbasRaw: 0,
911
+ sbasL5Raw: 0,
824
912
  badFrames: 0
825
913
  };
914
+ const dfmcByType = {};
915
+ const dfmcPrns = /* @__PURE__ */ new Set();
826
916
  const cnavAsm = newCnavAssemblers();
827
917
  const galAsm = newGalAssemblers();
828
918
  const gloAsm = new GloStringAssembler();
@@ -932,6 +1022,25 @@ function decodeSbfNavigation(data, opts = {}) {
932
1022
  else if (r.badCrc) counts.badFrames++;
933
1023
  return;
934
1024
  }
1025
+ case 4021: {
1026
+ if (len < 52) return;
1027
+ counts.sbasL5Raw++;
1028
+ const r = feedGeoL5Block(
1029
+ view,
1030
+ b,
1031
+ opts.onSbasMessage,
1032
+ opts.onDfmcMessage
1033
+ );
1034
+ if (r.badCrc) counts.badFrames++;
1035
+ else if (r.kind === "l1") {
1036
+ if (r.eph) addEph(r.eph);
1037
+ } else if (r.kind === "l5" && r.dfmcType !== void 0) {
1038
+ dfmcByType[r.dfmcType] = (dfmcByType[r.dfmcType] ?? 0) + 1;
1039
+ const s = svidToPrn(view.getUint8(b + 14));
1040
+ if (s) dfmcPrns.add(s);
1041
+ }
1042
+ return;
1043
+ }
935
1044
  /* ---- iono / UTC ---- */
936
1045
  case 5893:
937
1046
  // GPSIon
@@ -956,7 +1065,12 @@ function decodeSbfNavigation(data, opts = {}) {
956
1065
  return;
957
1066
  }
958
1067
  });
959
- return { ephemerides, cnav, ionoCorrections, leapSeconds, counts };
1068
+ const dfmc = {
1069
+ byType: dfmcByType,
1070
+ prns: [...dfmcPrns].sort(),
1071
+ messages: Object.values(dfmcByType).reduce((a, v) => a + v, 0)
1072
+ };
1073
+ return { ephemerides, cnav, ionoCorrections, leapSeconds, dfmc, counts };
960
1074
  }
961
1075
 
962
1076
  // src/navbits/rs255.ts
@@ -2163,8 +2277,16 @@ export {
2163
2277
  parseSbfGalNav,
2164
2278
  parseSbfBdsNav,
2165
2279
  parseSbfGloNav,
2280
+ SBAS_L5_PREAMBLE_UW,
2281
+ isSbasL1Preamble,
2282
+ sbasL5MessageType,
2283
+ SBAS_L5_MT_NAMES,
2284
+ sbasL5MessageName,
2285
+ isSbasL5Correction,
2166
2286
  feedGeoBlock,
2287
+ feedGeoL5Block,
2167
2288
  parseSbfGeoNav,
2289
+ parseSbfGeoL5,
2168
2290
  decodeSbfNavigation,
2169
2291
  parseSbfHas,
2170
2292
  parseSbfMeas