gnss-js 1.16.0 → 1.18.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
@@ -222,11 +222,15 @@ __export(src_exports, {
222
222
  parseNovatelRange: () => parseNovatelRange,
223
223
  parseRinexStream: () => parseRinexStream,
224
224
  parseSbfAlmanac: () => parseSbfAlmanac,
225
+ parseSbfCnav: () => parseSbfCnav,
226
+ parseSbfIonoUtc: () => parseSbfIonoUtc,
225
227
  parseSbfMeas: () => parseSbfMeas,
226
228
  parseSbfNav: () => parseSbfNav,
227
229
  parseSinexBiasDcb: () => parseSinexBiasDcb,
228
230
  parseSourcetable: () => parseSourcetable,
229
231
  parseSp3: () => parseSp3,
232
+ parseUbxCnav: () => parseUbxCnav,
233
+ parseUbxIonoUtc: () => parseUbxIonoUtc,
230
234
  parseUbxNav: () => parseUbxNav,
231
235
  parseUbxRawx: () => parseUbxRawx,
232
236
  phiAltBOC: () => phiAltBOC,
@@ -4777,6 +4781,30 @@ function decodeGpsLnavFrame(subframes, opts = {}) {
4777
4781
 
4778
4782
  // src/ubx/nav.ts
4779
4783
  var PREAMB_CNAV = 139;
4784
+ function readLnavSubframe(view, f) {
4785
+ const p = f.payload;
4786
+ if (p.length < 8) return null;
4787
+ const gnssId = p[0];
4788
+ const svId = p[1];
4789
+ let prn;
4790
+ if (gnssId === 0 && svId >= 1 && svId <= 32) {
4791
+ prn = `G${String(svId).padStart(2, "0")}`;
4792
+ } else if (gnssId === 5 && svId >= 1 && svId <= 10) {
4793
+ if (p.length === 44) return null;
4794
+ prn = `J${String(svId).padStart(2, "0")}`;
4795
+ } else {
4796
+ return null;
4797
+ }
4798
+ if (p.length < 8 + 40) return null;
4799
+ const base = f.payloadStart + 8;
4800
+ if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) return null;
4801
+ const buff = new Uint8Array(30);
4802
+ for (let k = 0; k < 10; k++) {
4803
+ const dwrd = view.getUint32(base + 4 * k, true);
4804
+ setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
4805
+ }
4806
+ return { prn, gnssId, svId, buff, id: getBitU(buff, 43, 3) };
4807
+ }
4780
4808
  function parseUbxNav(data, opts = {}) {
4781
4809
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
4782
4810
  const ephemerides = [];
@@ -4798,28 +4826,9 @@ function parseUbxNav(data, opts = {}) {
4798
4826
  const last = /* @__PURE__ */ new Map();
4799
4827
  for (const f of ubxFrames(data)) {
4800
4828
  if (f.msgClass !== 2 || f.msgId !== 19) continue;
4801
- const p = f.payload;
4802
- if (p.length < 8) continue;
4803
- const gnssId = p[0];
4804
- const svId = p[1];
4805
- let prn;
4806
- if (gnssId === 0 && svId >= 1 && svId <= 32) {
4807
- prn = `G${String(svId).padStart(2, "0")}`;
4808
- } else if (gnssId === 5 && svId >= 1 && svId <= 10) {
4809
- if (p.length === 44) continue;
4810
- prn = `J${String(svId).padStart(2, "0")}`;
4811
- } else {
4812
- continue;
4813
- }
4814
- if (p.length < 8 + 40) continue;
4815
- const base = f.payloadStart + 8;
4816
- if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) continue;
4817
- const buff = new Uint8Array(30);
4818
- for (let k = 0; k < 10; k++) {
4819
- const dwrd = view.getUint32(base + 4 * k, true);
4820
- setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
4821
- }
4822
- const id = getBitU(buff, 43, 3);
4829
+ const lnav = readLnavSubframe(view, f);
4830
+ if (!lnav) continue;
4831
+ const { prn, gnssId, svId, buff, id } = lnav;
4823
4832
  if (id < 1 || id > 5) {
4824
4833
  badParity++;
4825
4834
  continue;
@@ -4849,8 +4858,266 @@ function parseUbxNav(data, opts = {}) {
4849
4858
  return { ephemerides, badParity };
4850
4859
  }
4851
4860
 
4852
- // src/ubx/index.ts
4861
+ // src/ubx/iono.ts
4862
+ function parseUbxIonoUtc(data) {
4863
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
4864
+ const ionoCorrections = {};
4865
+ let leapSeconds = null;
4866
+ for (const f of ubxFrames(data)) {
4867
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
4868
+ const sf = readLnavSubframe(view, f);
4869
+ if (!sf || sf.gnssId !== 0 || sf.id !== 4) continue;
4870
+ const b = sf.buff;
4871
+ if (getBitU(b, 48, 2) !== 1 || getBitU(b, 50, 6) !== 56) continue;
4872
+ ionoCorrections["GPSA"] = [
4873
+ getBitS(b, 56, 8) * 2 ** -30,
4874
+ getBitS(b, 64, 8) * 2 ** -27,
4875
+ getBitS(b, 72, 8) * 2 ** -24,
4876
+ getBitS(b, 80, 8) * 2 ** -24
4877
+ ];
4878
+ ionoCorrections["GPSB"] = [
4879
+ getBitS(b, 88, 8) * 2 ** 11,
4880
+ getBitS(b, 96, 8) * 2 ** 14,
4881
+ getBitS(b, 104, 8) * 2 ** 16,
4882
+ getBitS(b, 112, 8) * 2 ** 16
4883
+ ];
4884
+ leapSeconds = getBitS(b, 192, 8);
4885
+ }
4886
+ return { ionoCorrections, leapSeconds };
4887
+ }
4888
+
4889
+ // src/navbits/cnav.ts
4853
4890
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4891
+ var SEC_PER_WEEK2 = 7 * 86400;
4892
+ var HALF_WEEK = 302400;
4893
+ var CNAV_PREAMBLE = 139;
4894
+ var CNAV_A_REF = 26559710;
4895
+ var CNAV_OMEGA_DOT_REF = -26e-10;
4896
+ function crc24q2(buff, bitLen) {
4897
+ let crc = 0;
4898
+ for (let i = 0; i < bitLen; i++) {
4899
+ crc ^= (buff[i >> 3] >> 7 - (i & 7) & 1) << 23;
4900
+ const msb = crc & 8388608;
4901
+ crc = crc << 1 & 16777215;
4902
+ if (msb) crc ^= 8801531;
4903
+ }
4904
+ return crc;
4905
+ }
4906
+ function cnavCrcOk(msg) {
4907
+ if (msg.length < 38) return false;
4908
+ return crc24q2(msg, 276) === getBitU(msg, 276, 24);
4909
+ }
4910
+ function getBitU33(b, pos) {
4911
+ return getBitU(b, pos, 1) * 2 ** 32 + getBitU(b, pos + 1, 32);
4912
+ }
4913
+ function getBitS33(b, pos) {
4914
+ const u = getBitU33(b, pos);
4915
+ return u < 2 ** 32 ? u : u - 2 ** 33;
4916
+ }
4917
+ function decodeType10(b, tow) {
4918
+ return {
4919
+ tow,
4920
+ week: getBitU(b, 38, 13),
4921
+ health: getBitU(b, 51, 3),
4922
+ top: getBitU(b, 54, 11) * 300,
4923
+ uraEd: getBitS(b, 65, 5),
4924
+ toe: getBitU(b, 70, 11) * 300,
4925
+ deltaA: getBitS(b, 81, 26) * 2 ** -9,
4926
+ aDot: getBitS(b, 107, 25) * 2 ** -21,
4927
+ deltaN0: getBitS(b, 132, 17) * 2 ** -44 * GPS_PI,
4928
+ deltaN0Dot: getBitS(b, 149, 23) * 2 ** -57 * GPS_PI,
4929
+ m0: getBitS33(b, 172) * 2 ** -32 * GPS_PI,
4930
+ e: getBitU33(b, 205) * 2 ** -34,
4931
+ omega: getBitS33(b, 238) * 2 ** -32 * GPS_PI,
4932
+ integrityFlag: getBitU(b, 271, 1) === 1,
4933
+ l2cPhasing: getBitU(b, 272, 1) === 1
4934
+ };
4935
+ }
4936
+ function decodeType11(b) {
4937
+ return {
4938
+ toe: getBitU(b, 38, 11) * 300,
4939
+ omega0: getBitS33(b, 49) * 2 ** -32 * GPS_PI,
4940
+ i0: getBitS33(b, 82) * 2 ** -32 * GPS_PI,
4941
+ deltaOmegaDot: getBitS(b, 115, 17) * 2 ** -44 * GPS_PI,
4942
+ i0Dot: getBitS(b, 132, 15) * 2 ** -44 * GPS_PI,
4943
+ cis: getBitS(b, 147, 16) * 2 ** -30,
4944
+ cic: getBitS(b, 163, 16) * 2 ** -30,
4945
+ crs: getBitS(b, 179, 24) * 2 ** -8,
4946
+ crc: getBitS(b, 203, 24) * 2 ** -8,
4947
+ cus: getBitS(b, 227, 21) * 2 ** -30,
4948
+ cuc: getBitS(b, 248, 21) * 2 ** -30
4949
+ };
4950
+ }
4951
+ var isc = (b, pos) => {
4952
+ const raw = getBitS(b, pos, 13);
4953
+ return raw === -4096 ? null : raw * 2 ** -35;
4954
+ };
4955
+ function decodeClock(b, msgType) {
4956
+ const clock = {
4957
+ msgType,
4958
+ top: getBitU(b, 38, 11) * 300,
4959
+ uraNed0: getBitS(b, 49, 5),
4960
+ uraNed1: getBitU(b, 54, 3),
4961
+ uraNed2: getBitU(b, 57, 3),
4962
+ toc: getBitU(b, 60, 11) * 300,
4963
+ af0: getBitS(b, 71, 26) * 2 ** -35,
4964
+ af1: getBitS(b, 97, 20) * 2 ** -48,
4965
+ af2: getBitS(b, 117, 10) * 2 ** -60
4966
+ };
4967
+ if (msgType === 30) {
4968
+ clock.tgd = isc(b, 127);
4969
+ clock.iscL1ca = isc(b, 140);
4970
+ clock.iscL2c = isc(b, 153);
4971
+ clock.iscL5i5 = isc(b, 166);
4972
+ clock.iscL5q5 = isc(b, 179);
4973
+ clock.ionoAlpha = [
4974
+ getBitS(b, 192, 8) * 2 ** -30,
4975
+ getBitS(b, 200, 8) * 2 ** -27,
4976
+ getBitS(b, 208, 8) * 2 ** -24,
4977
+ getBitS(b, 216, 8) * 2 ** -24
4978
+ ];
4979
+ clock.ionoBeta = [
4980
+ getBitS(b, 224, 8) * 2 ** 11,
4981
+ getBitS(b, 232, 8) * 2 ** 14,
4982
+ getBitS(b, 240, 8) * 2 ** 16,
4983
+ getBitS(b, 248, 8) * 2 ** 16
4984
+ ];
4985
+ clock.wnOp = getBitU(b, 256, 8);
4986
+ }
4987
+ return clock;
4988
+ }
4989
+ function resolveWeek(week, towSec, sec) {
4990
+ if (sec < towSec - HALF_WEEK) return week + 1;
4991
+ if (sec > towSec + HALF_WEEK) return week - 1;
4992
+ return week;
4993
+ }
4994
+ function gpsDate(week, sec) {
4995
+ return new Date(GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + sec) * 1e3);
4996
+ }
4997
+ var CnavAssembler = class {
4998
+ sats = /* @__PURE__ */ new Map();
4999
+ /**
5000
+ * Push one 300-bit CNAV message (38+ bytes, bit 0 = first bit of
5001
+ * the preamble). Returns the newly completed ephemeris, or null.
5002
+ * Messages that are not MT10/MT11/MT30-37, or whose preamble/PRN
5003
+ * are out of range, are ignored.
5004
+ */
5005
+ push(msg) {
5006
+ if (msg.length < 38 || getBitU(msg, 0, 8) !== CNAV_PREAMBLE) return null;
5007
+ const prn = getBitU(msg, 8, 6);
5008
+ if (prn < 1 || prn > 32) return null;
5009
+ const type = getBitU(msg, 14, 6);
5010
+ const tow = getBitU(msg, 20, 17) * 6;
5011
+ let sat = this.sats.get(prn);
5012
+ if (!sat) {
5013
+ sat = {};
5014
+ this.sats.set(prn, sat);
5015
+ }
5016
+ if (type === 10) sat.t10 = decodeType10(msg, tow);
5017
+ else if (type === 11) sat.t11 = decodeType11(msg);
5018
+ else if (type >= 30 && type <= 37) {
5019
+ sat.clock = decodeClock(msg, type);
5020
+ if (type === 30) sat.extras = sat.clock;
5021
+ } else return null;
5022
+ return this.tryEmit(prn, sat);
5023
+ }
5024
+ tryEmit(prn, sat) {
5025
+ const { t10, t11, clock, extras } = sat;
5026
+ if (!t10 || !t11 || !clock) return null;
5027
+ if (t10.toe !== t11.toe || clock.toc !== t10.toe) return null;
5028
+ const key = `${t10.week}:${t10.toe}:${clock.af0}:${clock.af1}`;
5029
+ if (key === sat.lastKey) return null;
5030
+ sat.lastKey = key;
5031
+ const weekToe = resolveWeek(t10.week, t10.tow, t10.toe);
5032
+ const a = CNAV_A_REF + t10.deltaA;
5033
+ return {
5034
+ system: "G",
5035
+ prn: `G${String(prn).padStart(2, "0")}`,
5036
+ week: t10.week,
5037
+ health: t10.health,
5038
+ uraEd: t10.uraEd,
5039
+ uraNed0: clock.uraNed0,
5040
+ uraNed1: clock.uraNed1,
5041
+ uraNed2: clock.uraNed2,
5042
+ top: t10.top,
5043
+ toe: t10.toe,
5044
+ toeDate: gpsDate(weekToe, t10.toe),
5045
+ a,
5046
+ deltaA: t10.deltaA,
5047
+ aDot: t10.aDot,
5048
+ deltaN0: t10.deltaN0,
5049
+ deltaN0Dot: t10.deltaN0Dot,
5050
+ m0: t10.m0,
5051
+ e: t10.e,
5052
+ omega: t10.omega,
5053
+ omega0: t11.omega0,
5054
+ i0: t11.i0,
5055
+ omegaDot: CNAV_OMEGA_DOT_REF * GPS_PI + t11.deltaOmegaDot,
5056
+ deltaOmegaDot: t11.deltaOmegaDot,
5057
+ i0Dot: t11.i0Dot,
5058
+ cis: t11.cis,
5059
+ cic: t11.cic,
5060
+ crs: t11.crs,
5061
+ crc: t11.crc,
5062
+ cus: t11.cus,
5063
+ cuc: t11.cuc,
5064
+ toc: clock.toc,
5065
+ tocDate: gpsDate(weekToe, clock.toc),
5066
+ af0: clock.af0,
5067
+ af1: clock.af1,
5068
+ af2: clock.af2,
5069
+ clockMsgType: clock.msgType,
5070
+ tgd: extras?.tgd ?? null,
5071
+ iscL1ca: extras?.iscL1ca ?? null,
5072
+ iscL2c: extras?.iscL2c ?? null,
5073
+ iscL5i5: extras?.iscL5i5 ?? null,
5074
+ iscL5q5: extras?.iscL5q5 ?? null,
5075
+ ...extras?.ionoAlpha && { ionoAlpha: extras.ionoAlpha },
5076
+ ...extras?.ionoBeta && { ionoBeta: extras.ionoBeta },
5077
+ ...extras?.wnOp !== void 0 && { wnOp: extras.wnOp },
5078
+ integrityFlag: t10.integrityFlag,
5079
+ l2cPhasing: t10.l2cPhasing,
5080
+ tow: t10.tow
5081
+ };
5082
+ }
5083
+ };
5084
+
5085
+ // src/ubx/cnav.ts
5086
+ function parseUbxCnav(data) {
5087
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5088
+ const assembler = new CnavAssembler();
5089
+ const ephemerides = [];
5090
+ let badCrc = 0;
5091
+ let messages = 0;
5092
+ for (const f of ubxFrames(data)) {
5093
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
5094
+ const p = f.payload;
5095
+ if (p.length < 8 + 40) continue;
5096
+ if (p[0] !== 0 || p[2] !== 3 && p[2] !== 4) continue;
5097
+ const svId = p[1];
5098
+ if (svId < 1 || svId > 32) continue;
5099
+ messages++;
5100
+ const msg = new Uint8Array(40);
5101
+ const base = f.payloadStart + 8;
5102
+ for (let k = 0; k < 10; k++) {
5103
+ const w = view.getUint32(base + 4 * k, true);
5104
+ msg[4 * k] = w >>> 24;
5105
+ msg[4 * k + 1] = w >>> 16 & 255;
5106
+ msg[4 * k + 2] = w >>> 8 & 255;
5107
+ msg[4 * k + 3] = w & 255;
5108
+ }
5109
+ if (!cnavCrcOk(msg)) {
5110
+ badCrc++;
5111
+ continue;
5112
+ }
5113
+ const eph = assembler.push(msg);
5114
+ if (eph) ephemerides.push(eph);
5115
+ }
5116
+ return { ephemerides, badCrc, messages };
5117
+ }
5118
+
5119
+ // src/ubx/index.ts
5120
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
4854
5121
  var MS_PER_WEEK2 = 7 * 864e5;
4855
5122
  var SIGNALS = {
4856
5123
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -4933,7 +5200,7 @@ function parseUbxRawx(data) {
4933
5200
  if (!codes.includes(sig[1])) codes.push(sig[1]);
4934
5201
  }
4935
5202
  epochs.push({
4936
- timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
5203
+ timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
4937
5204
  leapS: leapValid ? leapS : null,
4938
5205
  meas
4939
5206
  });
@@ -5004,10 +5271,10 @@ function svidToPrn(svid) {
5004
5271
 
5005
5272
  // src/sbf/nav.ts
5006
5273
  var PI2 = Math.PI;
5007
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5274
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5008
5275
  var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
5009
- var SEC_PER_WEEK2 = 7 * 86400;
5010
- var MS_PER_WEEK3 = SEC_PER_WEEK2 * 1e3;
5276
+ var SEC_PER_WEEK3 = 7 * 86400;
5277
+ var MS_PER_WEEK3 = SEC_PER_WEEK3 * 1e3;
5011
5278
  var F4_DNU = -2e10;
5012
5279
  var U4_DNU = 4294967295;
5013
5280
  function adjustWeek(refWeek, wn, mod) {
@@ -5016,8 +5283,8 @@ function adjustWeek(refWeek, wn, mod) {
5016
5283
  if (offset < -(mod / 2 - 1)) offset += mod;
5017
5284
  return refWeek - offset;
5018
5285
  }
5019
- var gpsMs = (week, sec) => GPS_EPOCH_MS3 + week * MS_PER_WEEK3 + sec * 1e3;
5020
- var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK2;
5286
+ var gpsMs = (week, sec) => GPS_EPOCH_MS4 + week * MS_PER_WEEK3 + sec * 1e3;
5287
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK3;
5021
5288
  function decodeGpsQzsNav(view, b, sys) {
5022
5289
  const svid = view.getUint8(b + 14);
5023
5290
  const prn = sys === "G" ? svid : svid - 180;
@@ -5166,7 +5433,7 @@ function decodeGloNav(view, b) {
5166
5433
  const tocDate = new Date(toeGpsMs - leapMs);
5167
5434
  const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
5168
5435
  const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
5169
- const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS3) / 1e3 % SEC_PER_WEEK2;
5436
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS4) / 1e3 % SEC_PER_WEEK3;
5170
5437
  return {
5171
5438
  system: "R",
5172
5439
  prn,
@@ -5311,9 +5578,78 @@ function parseSbfAlmanac(data) {
5311
5578
  return { almanacs, badCrc };
5312
5579
  }
5313
5580
 
5581
+ // src/sbf/iono.ts
5582
+ var F4_DNU2 = -2e10;
5583
+ function parseSbfIonoUtc(data) {
5584
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5585
+ const ionoCorrections = {};
5586
+ let leapSeconds = null;
5587
+ const f4s = (b, n) => {
5588
+ const out = [];
5589
+ for (let k = 0; k < n; k++) {
5590
+ const v = view.getFloat32(b + 4 * k, true);
5591
+ if (v === F4_DNU2) return null;
5592
+ out.push(v);
5593
+ }
5594
+ return out;
5595
+ };
5596
+ scanSbfFrames(data, view, (id, b, len) => {
5597
+ if ((id === 5893 || id === 4120) && len >= 48) {
5598
+ const alpha = f4s(b + 16, 4);
5599
+ const beta = f4s(b + 32, 4);
5600
+ if (!alpha || !beta) return;
5601
+ const sys = id === 5893 ? "GPS" : "BDS";
5602
+ ionoCorrections[`${sys}A`] = alpha;
5603
+ ionoCorrections[`${sys}B`] = beta;
5604
+ } else if (id === 4030 && len >= 28) {
5605
+ const ai = f4s(b + 16, 3);
5606
+ if (ai) ionoCorrections["GAL"] = ai;
5607
+ } else if (id === 5894 && len >= 37) {
5608
+ leapSeconds = view.getInt8(b + 33);
5609
+ }
5610
+ });
5611
+ return { ionoCorrections, leapSeconds };
5612
+ }
5613
+
5614
+ // src/sbf/rawnav.ts
5615
+ var SIGNAL_OF_BLOCK = {
5616
+ 4018: "L2C",
5617
+ 4019: "L5"
5618
+ };
5619
+ function parseSbfCnav(data) {
5620
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5621
+ const ephemerides = [];
5622
+ const assemblers = {
5623
+ L2C: new CnavAssembler(),
5624
+ L5: new CnavAssembler()
5625
+ };
5626
+ let badCrc = 0;
5627
+ let messages = 0;
5628
+ scanSbfFrames(data, view, (id, b, len) => {
5629
+ const signal = SIGNAL_OF_BLOCK[id];
5630
+ if (!signal || len < 60) return;
5631
+ messages++;
5632
+ const msg = new Uint8Array(40);
5633
+ for (let k = 0; k < 10; k++) {
5634
+ const w = view.getUint32(b + 20 + 4 * k, true);
5635
+ msg[4 * k] = w >>> 24;
5636
+ msg[4 * k + 1] = w >>> 16 & 255;
5637
+ msg[4 * k + 2] = w >>> 8 & 255;
5638
+ msg[4 * k + 3] = w & 255;
5639
+ }
5640
+ if (!cnavCrcOk(msg)) {
5641
+ badCrc++;
5642
+ return;
5643
+ }
5644
+ const eph = assemblers[signal].push(msg);
5645
+ if (eph) ephemerides.push({ ...eph, signal });
5646
+ });
5647
+ return { ephemerides, badCrc, messages };
5648
+ }
5649
+
5314
5650
  // src/sbf/index.ts
5315
5651
  var CLIGHT = 299792458;
5316
- var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5652
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5317
5653
  var MS_PER_WEEK4 = 7 * 864e5;
5318
5654
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
5319
5655
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -5967,7 +6303,7 @@ function parseSbfMeas(data) {
5967
6303
  const tow = view.getUint32(i + 8, true);
5968
6304
  const wnc = view.getUint16(i + 12, true);
5969
6305
  if (tow !== 4294967295 && wnc !== 65535) {
5970
- ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK4 + tow);
6306
+ ensureEpoch(GPS_EPOCH_MS5 + wnc * MS_PER_WEEK4 + tow);
5971
6307
  if (id === 4109) decodeMeas3Ranges(i, tow);
5972
6308
  else if (id === 4110) decodeMeas3CN0(i, len);
5973
6309
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -6024,11 +6360,27 @@ function* oem4Frames(data, view, stats) {
6024
6360
  }
6025
6361
 
6026
6362
  // src/novatel/nav.ts
6363
+ var ID_IONUTC = 8;
6027
6364
  var ID_RAWEPHEM = 41;
6028
6365
  var ID_GLOEPHEMERIS = 723;
6029
- var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
6030
- var SEC_PER_WEEK3 = 7 * 86400;
6366
+ var ID_GALEPHEMERIS = 1122;
6367
+ var ID_QZSSEPHEMERIS = 1336;
6368
+ var ID_GPSEPHEM = 7;
6369
+ var ID_BDSEPHEMERIS = 1696;
6370
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6371
+ var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
6372
+ var SEC_PER_WEEK4 = 7 * 86400;
6031
6373
  var SEC_PER_DAY = 86400;
6374
+ var MS_PER_WEEK5 = SEC_PER_WEEK4 * 1e3;
6375
+ var gpsMs2 = (week, sec) => GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + sec * 1e3;
6376
+ var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
6377
+ function nearWeekMs(refMs, sow) {
6378
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS6) / MS_PER_WEEK5);
6379
+ let ms = gpsMs2(refWeek, sow);
6380
+ if (ms < refMs - MS_PER_WEEK5 / 2) ms += MS_PER_WEEK5;
6381
+ else if (ms > refMs + MS_PER_WEEK5 / 2) ms -= MS_PER_WEEK5;
6382
+ return ms;
6383
+ }
6032
6384
  function decodeRawEphem(data, view, p) {
6033
6385
  const prn = view.getUint32(p, true);
6034
6386
  if (prn < 1 || prn > 32) return null;
@@ -6050,9 +6402,9 @@ function decodeGloEphemeris(view, p) {
6050
6402
  tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
6051
6403
  if (tof < tow - 43200) tof += SEC_PER_DAY;
6052
6404
  else if (tof > tow + 43200) tof -= SEC_PER_DAY;
6053
- const toeGpsMs = GPS_EPOCH_MS5 + (week * SEC_PER_WEEK3 + tow) * 1e3;
6054
- const tofGpsMs = GPS_EPOCH_MS5 + (week * SEC_PER_WEEK3 + tof) * 1e3;
6055
- const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS5) / 1e3;
6405
+ const toeGpsMs = GPS_EPOCH_MS6 + (week * SEC_PER_WEEK4 + tow) * 1e3;
6406
+ const tofGpsMs = GPS_EPOCH_MS6 + (week * SEC_PER_WEEK4 + tof) * 1e3;
6407
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS6) / 1e3;
6056
6408
  return {
6057
6409
  system: "R",
6058
6410
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -6060,7 +6412,7 @@ function decodeGloEphemeris(view, p) {
6060
6412
  tauN: -view.getFloat64(p + 100, true),
6061
6413
  // RINEX stores −τ_n
6062
6414
  gammaN: view.getFloat64(p + 116, true),
6063
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK3 + SEC_PER_WEEK3) % SEC_PER_WEEK3,
6415
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK4 + SEC_PER_WEEK4) % SEC_PER_WEEK4,
6064
6416
  x: view.getFloat64(p + 28, true) / 1e3,
6065
6417
  y: view.getFloat64(p + 36, true) / 1e3,
6066
6418
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -6074,23 +6426,188 @@ function decodeGloEphemeris(view, p) {
6074
6426
  freqNum
6075
6427
  };
6076
6428
  }
6429
+ function decodeGalEphemeris(view, p, headerMs) {
6430
+ const prn = view.getUint32(p, true);
6431
+ if (prn < 1 || prn > 36) return null;
6432
+ const fnav = (view.getUint32(p + 4, true) & 1) !== 0;
6433
+ const svhE1b = view.getUint8(p + 12) & 3;
6434
+ const svhE5a = view.getUint8(p + 13) & 3;
6435
+ const svhE5b = view.getUint8(p + 14) & 3;
6436
+ const dvsE1b = view.getUint8(p + 15) & 1;
6437
+ const dvsE5a = view.getUint8(p + 16) & 1;
6438
+ const dvsE5b = view.getUint8(p + 17) & 1;
6439
+ const iodNav = view.getUint32(p + 20, true);
6440
+ const toes = view.getUint32(p + 24, true);
6441
+ const c = fnav ? p + 148 : p + 176;
6442
+ const tocs = view.getUint32(c, true);
6443
+ const toeMs = nearWeekMs(headerMs, toes);
6444
+ const tocDate = new Date(nearWeekMs(headerMs, tocs));
6445
+ return {
6446
+ fnav,
6447
+ eph: {
6448
+ system: "E",
6449
+ prn: `E${String(prn).padStart(2, "0")}`,
6450
+ toc: sowOf2(tocDate.getTime()),
6451
+ tocDate,
6452
+ af0: view.getFloat64(c + 4, true),
6453
+ af1: view.getFloat64(c + 12, true),
6454
+ af2: view.getFloat64(c + 20, true),
6455
+ iode: iodNav,
6456
+ crs: view.getFloat64(p + 92, true),
6457
+ deltaN: view.getFloat64(p + 36, true),
6458
+ m0: view.getFloat64(p + 44, true),
6459
+ cuc: view.getFloat64(p + 68, true),
6460
+ e: view.getFloat64(p + 52, true),
6461
+ cus: view.getFloat64(p + 76, true),
6462
+ sqrtA: view.getFloat64(p + 28, true),
6463
+ toe: toes,
6464
+ cic: view.getFloat64(p + 100, true),
6465
+ omega0: view.getFloat64(p + 132, true),
6466
+ cis: view.getFloat64(p + 108, true),
6467
+ i0: view.getFloat64(p + 116, true),
6468
+ crc: view.getFloat64(p + 84, true),
6469
+ omega: view.getFloat64(p + 60, true),
6470
+ omegaDot: view.getFloat64(p + 140, true),
6471
+ idot: view.getFloat64(p + 124, true),
6472
+ week: Math.floor((toeMs - GPS_EPOCH_MS6) / MS_PER_WEEK5),
6473
+ // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
6474
+ // 3-5, E5b in 6-8) — same packing as RTKLIB.
6475
+ svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
6476
+ tgd: view.getFloat64(p + 204, true)
6477
+ // BGD E5a/E1 (RINEX slot)
6478
+ }
6479
+ };
6480
+ }
6481
+ function decodeBdsEphemeris2(view, p) {
6482
+ const prn = view.getUint32(p, true);
6483
+ if (prn < 1 || prn > 63) return null;
6484
+ const week = view.getUint32(p + 4, true);
6485
+ const health = view.getUint32(p + 16, true) & 1;
6486
+ const tgd1 = view.getFloat64(p + 20, true);
6487
+ const tocs = view.getUint32(p + 40, true);
6488
+ const toes = view.getUint32(p + 72, true);
6489
+ const tocDate = new Date(BDT_EPOCH_MS2 + week * MS_PER_WEEK5 + tocs * 1e3);
6490
+ return {
6491
+ system: "C",
6492
+ prn: `C${String(prn).padStart(2, "0")}`,
6493
+ toc: sowOf2(tocDate.getTime()),
6494
+ tocDate,
6495
+ af0: view.getFloat64(p + 44, true),
6496
+ af1: view.getFloat64(p + 52, true),
6497
+ af2: view.getFloat64(p + 60, true),
6498
+ iode: view.getUint32(p + 68, true),
6499
+ // AODE
6500
+ crs: view.getFloat64(p + 172, true),
6501
+ deltaN: view.getFloat64(p + 100, true),
6502
+ m0: view.getFloat64(p + 108, true),
6503
+ cuc: view.getFloat64(p + 148, true),
6504
+ e: view.getFloat64(p + 84, true),
6505
+ cus: view.getFloat64(p + 156, true),
6506
+ sqrtA: view.getFloat64(p + 76, true),
6507
+ toe: toes,
6508
+ cic: view.getFloat64(p + 180, true),
6509
+ omega0: view.getFloat64(p + 116, true),
6510
+ cis: view.getFloat64(p + 188, true),
6511
+ i0: view.getFloat64(p + 132, true),
6512
+ crc: view.getFloat64(p + 164, true),
6513
+ omega: view.getFloat64(p + 92, true),
6514
+ omegaDot: view.getFloat64(p + 124, true),
6515
+ idot: view.getFloat64(p + 140, true),
6516
+ week,
6517
+ // RINEX BDS week field is the BDT week
6518
+ svHealth: health,
6519
+ tgd: tgd1
6520
+ // TGD1 (B1) — RINEX slot
6521
+ };
6522
+ }
6523
+ function decodeGpsQzssEphemeris(view, p, sys) {
6524
+ const prn = view.getUint32(p, true);
6525
+ if (sys === "G" && (prn < 1 || prn > 32)) return null;
6526
+ if (sys === "J" && (prn < 193 || prn > 202)) return null;
6527
+ const health = view.getUint32(p + 12, true) & 63;
6528
+ const iode1 = view.getUint32(p + 16, true);
6529
+ const week = view.getUint32(p + 24, true);
6530
+ const toes = view.getFloat64(p + 32, true);
6531
+ const toeMs = gpsMs2(week, toes);
6532
+ const tocs = view.getFloat64(p + 164, true);
6533
+ const tocDate = new Date(nearWeekMs(toeMs, tocs));
6534
+ return {
6535
+ system: sys,
6536
+ prn: `${sys}${String(sys === "J" ? prn - 192 : prn).padStart(2, "0")}`,
6537
+ toc: sowOf2(tocDate.getTime()),
6538
+ tocDate,
6539
+ af0: view.getFloat64(p + 180, true),
6540
+ af1: view.getFloat64(p + 188, true),
6541
+ af2: view.getFloat64(p + 196, true),
6542
+ iode: iode1,
6543
+ crs: view.getFloat64(p + 104, true),
6544
+ deltaN: view.getFloat64(p + 48, true),
6545
+ m0: view.getFloat64(p + 56, true),
6546
+ cuc: view.getFloat64(p + 80, true),
6547
+ e: view.getFloat64(p + 64, true),
6548
+ cus: view.getFloat64(p + 88, true),
6549
+ sqrtA: Math.sqrt(view.getFloat64(p + 40, true)),
6550
+ toe: toes,
6551
+ cic: view.getFloat64(p + 112, true),
6552
+ omega0: view.getFloat64(p + 144, true),
6553
+ cis: view.getFloat64(p + 120, true),
6554
+ i0: view.getFloat64(p + 128, true),
6555
+ crc: view.getFloat64(p + 96, true),
6556
+ omega: view.getFloat64(p + 72, true),
6557
+ omegaDot: view.getFloat64(p + 152, true),
6558
+ idot: view.getFloat64(p + 136, true),
6559
+ week,
6560
+ svHealth: health,
6561
+ tgd: view.getFloat64(p + 172, true)
6562
+ };
6563
+ }
6077
6564
  function parseNovatelNav(data) {
6078
6565
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6079
6566
  const stats = { badCrc: 0 };
6080
6567
  const ephemerides = [];
6081
- const lastGps = /* @__PURE__ */ new Map();
6568
+ const ionoCorrections = {};
6569
+ let leapSeconds = null;
6570
+ const lastKep = /* @__PURE__ */ new Map();
6082
6571
  const lastGlo = /* @__PURE__ */ new Map();
6572
+ const pushKepler = (key, eph) => {
6573
+ const prev = lastKep.get(key);
6574
+ if (prev && prev.iode === eph.iode && prev.toe === eph.toe && prev.tocDate.getTime() === eph.tocDate.getTime()) {
6575
+ return;
6576
+ }
6577
+ lastKep.set(key, eph);
6578
+ ephemerides.push(eph);
6579
+ };
6083
6580
  for (const frame of oem4Frames(data, view, stats)) {
6084
6581
  if (!frame.binary) continue;
6085
6582
  if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
6086
6583
  const eph = decodeRawEphem(data, view, frame.payload);
6087
- if (!eph) continue;
6088
- const prev = lastGps.get(eph.prn);
6089
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
6090
- continue;
6584
+ if (eph) pushKepler(eph.prn, eph);
6585
+ } else if (frame.id === ID_GALEPHEMERIS && frame.msgLen >= 220) {
6586
+ if (frame.week === 0) continue;
6587
+ const headerMs = gpsMs2(frame.week, frame.towMs / 1e3);
6588
+ const dec = decodeGalEphemeris(view, frame.payload, headerMs);
6589
+ if (dec)
6590
+ pushKepler(`${dec.eph.prn}:${dec.fnav ? "fnav" : "inav"}`, dec.eph);
6591
+ } else if (frame.id === ID_BDSEPHEMERIS && frame.msgLen >= 196) {
6592
+ const eph = decodeBdsEphemeris2(view, frame.payload);
6593
+ if (eph) pushKepler(eph.prn, eph);
6594
+ } else if (frame.id === ID_GPSEPHEM && frame.msgLen >= 204) {
6595
+ const eph = decodeGpsQzssEphemeris(view, frame.payload, "G");
6596
+ if (eph) pushKepler(eph.prn, eph);
6597
+ } else if (frame.id === ID_QZSSEPHEMERIS && frame.msgLen >= 204) {
6598
+ const eph = decodeGpsQzssEphemeris(view, frame.payload, "J");
6599
+ if (eph) pushKepler(eph.prn, eph);
6600
+ } else if (frame.id === ID_IONUTC && frame.msgLen >= 108) {
6601
+ const p = frame.payload;
6602
+ const alpha = [];
6603
+ const beta = [];
6604
+ for (let i = 0; i < 4; i++) {
6605
+ alpha.push(view.getFloat64(p + i * 8, true));
6606
+ beta.push(view.getFloat64(p + 32 + i * 8, true));
6091
6607
  }
6092
- lastGps.set(eph.prn, eph);
6093
- ephemerides.push(eph);
6608
+ ionoCorrections["GPSA"] = alpha;
6609
+ ionoCorrections["GPSB"] = beta;
6610
+ leapSeconds = view.getInt32(p + 96, true);
6094
6611
  } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
6095
6612
  const eph = decodeGloEphemeris(view, frame.payload);
6096
6613
  if (!eph) continue;
@@ -6102,12 +6619,12 @@ function parseNovatelNav(data) {
6102
6619
  ephemerides.push(eph);
6103
6620
  }
6104
6621
  }
6105
- return { ephemerides, badCrc: stats.badCrc };
6622
+ return { ephemerides, ionoCorrections, leapSeconds, badCrc: stats.badCrc };
6106
6623
  }
6107
6624
 
6108
6625
  // src/novatel/index.ts
6109
- var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6110
- var MS_PER_WEEK5 = 7 * 864e5;
6626
+ var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6627
+ var MS_PER_WEEK6 = 7 * 864e5;
6111
6628
  var HDR = 28;
6112
6629
  var ID_RANGE = 43;
6113
6630
  var ID_RANGECMP = 140;
@@ -6276,7 +6793,7 @@ function parseNovatelRange(data) {
6276
6793
  if (!codes.includes(code)) codes.push(code);
6277
6794
  }
6278
6795
  epochs.push({
6279
- timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
6796
+ timeMs: GPS_EPOCH_MS7 + week * MS_PER_WEEK6 + towMs,
6280
6797
  meas
6281
6798
  });
6282
6799
  }
@@ -6328,7 +6845,7 @@ function parseNovatelRange(data) {
6328
6845
  if (!codes.includes(code)) codes.push(code);
6329
6846
  }
6330
6847
  epochs.push({
6331
- timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
6848
+ timeMs: GPS_EPOCH_MS7 + week * MS_PER_WEEK6 + towMs,
6332
6849
  meas
6333
6850
  });
6334
6851
  }
@@ -6687,10 +7204,10 @@ function selectBest(ephs, timeMs) {
6687
7204
  }
6688
7205
  return best;
6689
7206
  }
6690
- var GPS_EPOCH_MS7 = START_GPS_TIME.getTime();
6691
- var MS_PER_WEEK6 = 7 * 864e5;
7207
+ var GPS_EPOCH_MS8 = START_GPS_TIME.getTime();
7208
+ var MS_PER_WEEK7 = 7 * 864e5;
6692
7209
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
6693
- var GAL_EPOCH_MS = GPS_EPOCH_MS7 + 1024 * MS_PER_WEEK6;
7210
+ var GAL_EPOCH_MS = GPS_EPOCH_MS8 + 1024 * MS_PER_WEEK7;
6694
7211
  function ephInfoToEphemeris(info) {
6695
7212
  const sys = info.prn.charAt(0);
6696
7213
  if (sys === "R") {
@@ -6741,11 +7258,11 @@ function ephInfoToEphemeris(info) {
6741
7258
  } else if (sys === "E") {
6742
7259
  epochMs = GAL_EPOCH_MS;
6743
7260
  } else {
6744
- epochMs = GPS_EPOCH_MS7;
6745
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS7) / MS_PER_WEEK6;
7261
+ epochMs = GPS_EPOCH_MS8;
7262
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS8) / MS_PER_WEEK7;
6746
7263
  week += 1024 * Math.round((weeksNow - week) / 1024);
6747
7264
  }
6748
- const tocDate = new Date(epochMs + week * MS_PER_WEEK6 + tocSec * 1e3);
7265
+ const tocDate = new Date(epochMs + week * MS_PER_WEEK7 + tocSec * 1e3);
6749
7266
  return {
6750
7267
  system: sys,
6751
7268
  prn: info.prn,
@@ -9844,11 +10361,15 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9844
10361
  parseNovatelRange,
9845
10362
  parseRinexStream,
9846
10363
  parseSbfAlmanac,
10364
+ parseSbfCnav,
10365
+ parseSbfIonoUtc,
9847
10366
  parseSbfMeas,
9848
10367
  parseSbfNav,
9849
10368
  parseSinexBiasDcb,
9850
10369
  parseSourcetable,
9851
10370
  parseSp3,
10371
+ parseUbxCnav,
10372
+ parseUbxIonoUtc,
9852
10373
  parseUbxNav,
9853
10374
  parseUbxRawx,
9854
10375
  phiAltBOC,