gnss-js 1.15.0 → 1.17.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,14 @@ __export(src_exports, {
222
222
  parseNovatelRange: () => parseNovatelRange,
223
223
  parseRinexStream: () => parseRinexStream,
224
224
  parseSbfAlmanac: () => parseSbfAlmanac,
225
+ parseSbfIonoUtc: () => parseSbfIonoUtc,
225
226
  parseSbfMeas: () => parseSbfMeas,
226
227
  parseSbfNav: () => parseSbfNav,
227
228
  parseSinexBiasDcb: () => parseSinexBiasDcb,
228
229
  parseSourcetable: () => parseSourcetable,
229
230
  parseSp3: () => parseSp3,
231
+ parseUbxIonoUtc: () => parseUbxIonoUtc,
232
+ parseUbxNav: () => parseUbxNav,
230
233
  parseUbxRawx: () => parseUbxRawx,
231
234
  phiAltBOC: () => phiAltBOC,
232
235
  phiBOCc: () => phiBOCc,
@@ -240,6 +243,8 @@ __export(src_exports, {
240
243
  rhumbLine: () => rhumbLine,
241
244
  rtcm3Constellation: () => rtcm3Constellation,
242
245
  satClockCorrection: () => satClockCorrection,
246
+ sbfCrc16: () => crc16,
247
+ scanSbfFrames: () => scanSbfFrames,
243
248
  selectEphemeris: () => selectEphemeris,
244
249
  setGloFreqNumber: () => setGloFreqNumber,
245
250
  setRtcm3DebugHandler: () => setRtcm3DebugHandler,
@@ -249,6 +254,7 @@ __export(src_exports, {
249
254
  systemCmp: () => systemCmp,
250
255
  systemName: () => systemName,
251
256
  transformFrame: () => transformFrame,
257
+ ubxFrames: () => ubxFrames,
252
258
  updateStationMeta: () => updateStationMeta,
253
259
  updateStreamStats: () => updateStreamStats,
254
260
  verifyChecksum: () => verifyChecksum,
@@ -4598,8 +4604,288 @@ function updateStreamStats(stats, frames, rawBytes) {
4598
4604
  }
4599
4605
  }
4600
4606
 
4601
- // src/ubx/index.ts
4607
+ // src/ubx/frame.ts
4608
+ function* ubxFrames(data, stats = { badChecksums: 0 }) {
4609
+ let i = 0;
4610
+ while (i + 8 <= data.length) {
4611
+ if (data[i] !== 181 || data[i + 1] !== 98) {
4612
+ i++;
4613
+ continue;
4614
+ }
4615
+ const len = data[i + 4] | data[i + 5] << 8;
4616
+ const end = i + 6 + len + 2;
4617
+ if (end > data.length) break;
4618
+ let ckA = 0;
4619
+ let ckB = 0;
4620
+ for (let j = i + 2; j < i + 6 + len; j++) {
4621
+ ckA = ckA + data[j] & 255;
4622
+ ckB = ckB + ckA & 255;
4623
+ }
4624
+ if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
4625
+ stats.badChecksums++;
4626
+ i++;
4627
+ continue;
4628
+ }
4629
+ yield {
4630
+ msgClass: data[i + 2],
4631
+ msgId: data[i + 3],
4632
+ payload: data.subarray(i + 6, i + 6 + len),
4633
+ payloadStart: i + 6
4634
+ };
4635
+ i = end;
4636
+ }
4637
+ }
4638
+
4639
+ // src/navbits/index.ts
4640
+ var GPS_PI = 3.1415926535898;
4602
4641
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
4642
+ var SEC_PER_WEEK = 7 * 86400;
4643
+ function getBitU(buff, pos, len) {
4644
+ let bits = 0;
4645
+ for (let i = pos; i < pos + len; i++) {
4646
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
4647
+ }
4648
+ return bits;
4649
+ }
4650
+ function getBitS(buff, pos, len) {
4651
+ const bits = getBitU(buff, pos, len);
4652
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
4653
+ return bits - 2 ** len;
4654
+ }
4655
+ function setBitU(buff, pos, len, data) {
4656
+ let mask = 2 ** (len - 1);
4657
+ for (let i = pos; i < pos + len; i++, mask /= 2) {
4658
+ if (data >= mask) {
4659
+ buff[i >> 3] |= 1 << 7 - (i & 7);
4660
+ data -= mask;
4661
+ } else {
4662
+ buff[i >> 3] &= ~(1 << 7 - (i & 7));
4663
+ }
4664
+ }
4665
+ }
4666
+ function decodeGpsLnavFrame(subframes, opts = {}) {
4667
+ if (subframes.length < 90) return null;
4668
+ const b = subframes;
4669
+ let i = 24;
4670
+ const tow1 = getBitU(b, i, 17) * 6;
4671
+ i += 17 + 2;
4672
+ const id1 = getBitU(b, i, 3);
4673
+ i += 3 + 2;
4674
+ const week10 = getBitU(b, i, 10);
4675
+ i += 10;
4676
+ i += 2;
4677
+ i += 4;
4678
+ const svHealth = getBitU(b, i, 6);
4679
+ i += 6;
4680
+ const iodc0 = getBitU(b, i, 2);
4681
+ i += 2;
4682
+ i += 1 + 87;
4683
+ const tgdRaw = getBitS(b, i, 8);
4684
+ i += 8;
4685
+ const iodc1 = getBitU(b, i, 8);
4686
+ i += 8;
4687
+ const tocSec = getBitU(b, i, 16) * 16;
4688
+ i += 16;
4689
+ const af2 = getBitS(b, i, 8) * 2 ** -55;
4690
+ i += 8;
4691
+ const af1 = getBitS(b, i, 16) * 2 ** -43;
4692
+ i += 16;
4693
+ const af0 = getBitS(b, i, 22) * 2 ** -31;
4694
+ i = 240 + 24;
4695
+ i += 17 + 2;
4696
+ const id2 = getBitU(b, i, 3);
4697
+ i += 3 + 2;
4698
+ const iode = getBitU(b, i, 8);
4699
+ i += 8;
4700
+ const crs = getBitS(b, i, 16) * 2 ** -5;
4701
+ i += 16;
4702
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
4703
+ i += 16;
4704
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4705
+ i += 32;
4706
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
4707
+ i += 16;
4708
+ const e = getBitU(b, i, 32) * 2 ** -33;
4709
+ i += 32;
4710
+ const cus = getBitS(b, i, 16) * 2 ** -29;
4711
+ i += 16;
4712
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
4713
+ i += 32;
4714
+ const toes = getBitU(b, i, 16) * 16;
4715
+ i = 480 + 24;
4716
+ i += 17 + 2;
4717
+ const id3 = getBitU(b, i, 3);
4718
+ i += 3 + 2;
4719
+ const cic = getBitS(b, i, 16) * 2 ** -29;
4720
+ i += 16;
4721
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4722
+ i += 32;
4723
+ const cis = getBitS(b, i, 16) * 2 ** -29;
4724
+ i += 16;
4725
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4726
+ i += 32;
4727
+ const crc = getBitS(b, i, 16) * 2 ** -5;
4728
+ i += 16;
4729
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4730
+ i += 32;
4731
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
4732
+ i += 24;
4733
+ const iode3 = getBitU(b, i, 8);
4734
+ i += 8;
4735
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
4736
+ const iodc = iodc0 * 256 + iodc1;
4737
+ if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
4738
+ if (iode3 !== iode || iode !== (iodc & 255)) return null;
4739
+ const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
4740
+ let week = week10 + 1024 * Math.round((ref - week10) / 1024);
4741
+ if (toes < tow1 - 302400) week++;
4742
+ else if (toes > tow1 + 302400) week--;
4743
+ const prn = opts.prn ?? "G00";
4744
+ const tocDate = new Date(
4745
+ GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
4746
+ );
4747
+ return {
4748
+ system: prn[0] === "J" ? "J" : "G",
4749
+ prn,
4750
+ tocDate,
4751
+ // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
4752
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
4753
+ af0,
4754
+ af1,
4755
+ af2,
4756
+ iode,
4757
+ crs,
4758
+ deltaN,
4759
+ m0,
4760
+ cuc,
4761
+ e,
4762
+ cus,
4763
+ sqrtA,
4764
+ toe: toes,
4765
+ cic,
4766
+ omega0,
4767
+ cis,
4768
+ i0,
4769
+ crc,
4770
+ omega,
4771
+ omegaDot,
4772
+ idot,
4773
+ week,
4774
+ svHealth,
4775
+ tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
4776
+ // IS-GPS-200: -128 reserved
4777
+ };
4778
+ }
4779
+
4780
+ // src/ubx/nav.ts
4781
+ var PREAMB_CNAV = 139;
4782
+ function readLnavSubframe(view, f) {
4783
+ const p = f.payload;
4784
+ if (p.length < 8) return null;
4785
+ const gnssId = p[0];
4786
+ const svId = p[1];
4787
+ let prn;
4788
+ if (gnssId === 0 && svId >= 1 && svId <= 32) {
4789
+ prn = `G${String(svId).padStart(2, "0")}`;
4790
+ } else if (gnssId === 5 && svId >= 1 && svId <= 10) {
4791
+ if (p.length === 44) return null;
4792
+ prn = `J${String(svId).padStart(2, "0")}`;
4793
+ } else {
4794
+ return null;
4795
+ }
4796
+ if (p.length < 8 + 40) return null;
4797
+ const base = f.payloadStart + 8;
4798
+ if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) return null;
4799
+ const buff = new Uint8Array(30);
4800
+ for (let k = 0; k < 10; k++) {
4801
+ const dwrd = view.getUint32(base + 4 * k, true);
4802
+ setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
4803
+ }
4804
+ return { prn, gnssId, svId, buff, id: getBitU(buff, 43, 3) };
4805
+ }
4806
+ function parseUbxNav(data, opts = {}) {
4807
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
4808
+ const ephemerides = [];
4809
+ let badParity = 0;
4810
+ let refWeek = opts.refWeek;
4811
+ if (refWeek === void 0) {
4812
+ for (const f of ubxFrames(data)) {
4813
+ if (f.msgClass === 2 && f.msgId === 21 && f.payload.length >= 16) {
4814
+ const week = view.getUint16(f.payloadStart + 8, true);
4815
+ if (week > 0) {
4816
+ refWeek = week;
4817
+ break;
4818
+ }
4819
+ }
4820
+ }
4821
+ }
4822
+ if (refWeek === void 0) return { ephemerides, badParity };
4823
+ const subframes = /* @__PURE__ */ new Map();
4824
+ const last = /* @__PURE__ */ new Map();
4825
+ for (const f of ubxFrames(data)) {
4826
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
4827
+ const lnav = readLnavSubframe(view, f);
4828
+ if (!lnav) continue;
4829
+ const { prn, gnssId, svId, buff, id } = lnav;
4830
+ if (id < 1 || id > 5) {
4831
+ badParity++;
4832
+ continue;
4833
+ }
4834
+ if (id > 3) continue;
4835
+ const key = gnssId * 256 + svId;
4836
+ let sf = subframes.get(key);
4837
+ if (!sf) {
4838
+ sf = { buf: new Uint8Array(90), have: 0 };
4839
+ subframes.set(key, sf);
4840
+ }
4841
+ sf.buf.set(buff, (id - 1) * 30);
4842
+ sf.have |= 1 << id - 1;
4843
+ if (id !== 3 || sf.have !== 7) continue;
4844
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
4845
+ if (!eph) {
4846
+ badParity++;
4847
+ continue;
4848
+ }
4849
+ const prev = last.get(prn);
4850
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
4851
+ continue;
4852
+ }
4853
+ last.set(prn, eph);
4854
+ ephemerides.push(eph);
4855
+ }
4856
+ return { ephemerides, badParity };
4857
+ }
4858
+
4859
+ // src/ubx/iono.ts
4860
+ function parseUbxIonoUtc(data) {
4861
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
4862
+ const ionoCorrections = {};
4863
+ let leapSeconds = null;
4864
+ for (const f of ubxFrames(data)) {
4865
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
4866
+ const sf = readLnavSubframe(view, f);
4867
+ if (!sf || sf.gnssId !== 0 || sf.id !== 4) continue;
4868
+ const b = sf.buff;
4869
+ if (getBitU(b, 48, 2) !== 1 || getBitU(b, 50, 6) !== 56) continue;
4870
+ ionoCorrections["GPSA"] = [
4871
+ getBitS(b, 56, 8) * 2 ** -30,
4872
+ getBitS(b, 64, 8) * 2 ** -27,
4873
+ getBitS(b, 72, 8) * 2 ** -24,
4874
+ getBitS(b, 80, 8) * 2 ** -24
4875
+ ];
4876
+ ionoCorrections["GPSB"] = [
4877
+ getBitS(b, 88, 8) * 2 ** 11,
4878
+ getBitS(b, 96, 8) * 2 ** 14,
4879
+ getBitS(b, 104, 8) * 2 ** 16,
4880
+ getBitS(b, 112, 8) * 2 ** 16
4881
+ ];
4882
+ leapSeconds = getBitS(b, 192, 8);
4883
+ }
4884
+ return { ionoCorrections, leapSeconds };
4885
+ }
4886
+
4887
+ // src/ubx/index.ts
4888
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4603
4889
  var MS_PER_WEEK2 = 7 * 864e5;
4604
4890
  var SIGNALS = {
4605
4891
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -4639,33 +4925,14 @@ function parseUbxRawx(data) {
4639
4925
  const epochs = [];
4640
4926
  const messageCounts = {};
4641
4927
  const obsCodes = {};
4642
- let badChecksums = 0;
4643
- let i = 0;
4644
- while (i + 8 <= data.length) {
4645
- if (data[i] !== 181 || data[i + 1] !== 98) {
4646
- i++;
4647
- continue;
4648
- }
4649
- const cls = data[i + 2];
4650
- const id = data[i + 3];
4651
- const len = data[i + 4] | data[i + 5] << 8;
4652
- const end = i + 6 + len + 2;
4653
- if (end > data.length) break;
4654
- let ckA = 0;
4655
- let ckB = 0;
4656
- for (let j = i + 2; j < i + 6 + len; j++) {
4657
- ckA = ckA + data[j] & 255;
4658
- ckB = ckB + ckA & 255;
4659
- }
4660
- if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
4661
- badChecksums++;
4662
- i++;
4663
- continue;
4664
- }
4928
+ const stats = { badChecksums: 0 };
4929
+ for (const frame of ubxFrames(data, stats)) {
4930
+ const { msgClass: cls, msgId: id } = frame;
4931
+ const len = frame.payload.length;
4665
4932
  const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
4666
4933
  messageCounts[key] = (messageCounts[key] ?? 0) + 1;
4667
4934
  if (cls === 2 && id === 21 && len >= 16) {
4668
- const p = i + 6;
4935
+ const p = frame.payloadStart;
4669
4936
  const rcvTow = view.getFloat64(p, true);
4670
4937
  const week = view.getUint16(p + 8, true);
4671
4938
  const leapS = view.getInt8(p + 10);
@@ -4701,14 +4968,13 @@ function parseUbxRawx(data) {
4701
4968
  if (!codes.includes(sig[1])) codes.push(sig[1]);
4702
4969
  }
4703
4970
  epochs.push({
4704
- timeMs: GPS_EPOCH_MS + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
4971
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
4705
4972
  leapS: leapValid ? leapS : null,
4706
4973
  meas
4707
4974
  });
4708
4975
  }
4709
- i = end;
4710
4976
  }
4711
- return { epochs, messageCounts, obsCodes, badChecksums };
4977
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
4712
4978
  }
4713
4979
 
4714
4980
  // src/sbf/frame.ts
@@ -4773,10 +5039,10 @@ function svidToPrn(svid) {
4773
5039
 
4774
5040
  // src/sbf/nav.ts
4775
5041
  var PI2 = Math.PI;
4776
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
5042
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
4777
5043
  var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
4778
- var SEC_PER_WEEK = 7 * 86400;
4779
- var MS_PER_WEEK3 = SEC_PER_WEEK * 1e3;
5044
+ var SEC_PER_WEEK2 = 7 * 86400;
5045
+ var MS_PER_WEEK3 = SEC_PER_WEEK2 * 1e3;
4780
5046
  var F4_DNU = -2e10;
4781
5047
  var U4_DNU = 4294967295;
4782
5048
  function adjustWeek(refWeek, wn, mod) {
@@ -4785,8 +5051,8 @@ function adjustWeek(refWeek, wn, mod) {
4785
5051
  if (offset < -(mod / 2 - 1)) offset += mod;
4786
5052
  return refWeek - offset;
4787
5053
  }
4788
- var gpsMs = (week, sec) => GPS_EPOCH_MS2 + week * MS_PER_WEEK3 + sec * 1e3;
4789
- var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK;
5054
+ var gpsMs = (week, sec) => GPS_EPOCH_MS3 + week * MS_PER_WEEK3 + sec * 1e3;
5055
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK2;
4790
5056
  function decodeGpsQzsNav(view, b, sys) {
4791
5057
  const svid = view.getUint8(b + 14);
4792
5058
  const prn = sys === "G" ? svid : svid - 180;
@@ -4935,7 +5201,7 @@ function decodeGloNav(view, b) {
4935
5201
  const tocDate = new Date(toeGpsMs - leapMs);
4936
5202
  const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
4937
5203
  const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
4938
- const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS2) / 1e3 % SEC_PER_WEEK;
5204
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS3) / 1e3 % SEC_PER_WEEK2;
4939
5205
  return {
4940
5206
  system: "R",
4941
5207
  prn,
@@ -5080,9 +5346,42 @@ function parseSbfAlmanac(data) {
5080
5346
  return { almanacs, badCrc };
5081
5347
  }
5082
5348
 
5349
+ // src/sbf/iono.ts
5350
+ var F4_DNU2 = -2e10;
5351
+ function parseSbfIonoUtc(data) {
5352
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5353
+ const ionoCorrections = {};
5354
+ let leapSeconds = null;
5355
+ const f4s = (b, n) => {
5356
+ const out = [];
5357
+ for (let k = 0; k < n; k++) {
5358
+ const v = view.getFloat32(b + 4 * k, true);
5359
+ if (v === F4_DNU2) return null;
5360
+ out.push(v);
5361
+ }
5362
+ return out;
5363
+ };
5364
+ scanSbfFrames(data, view, (id, b, len) => {
5365
+ if ((id === 5893 || id === 4120) && len >= 48) {
5366
+ const alpha = f4s(b + 16, 4);
5367
+ const beta = f4s(b + 32, 4);
5368
+ if (!alpha || !beta) return;
5369
+ const sys = id === 5893 ? "GPS" : "BDS";
5370
+ ionoCorrections[`${sys}A`] = alpha;
5371
+ ionoCorrections[`${sys}B`] = beta;
5372
+ } else if (id === 4030 && len >= 28) {
5373
+ const ai = f4s(b + 16, 3);
5374
+ if (ai) ionoCorrections["GAL"] = ai;
5375
+ } else if (id === 5894 && len >= 37) {
5376
+ leapSeconds = view.getInt8(b + 33);
5377
+ }
5378
+ });
5379
+ return { ionoCorrections, leapSeconds };
5380
+ }
5381
+
5083
5382
  // src/sbf/index.ts
5084
5383
  var CLIGHT = 299792458;
5085
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5384
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5086
5385
  var MS_PER_WEEK4 = 7 * 864e5;
5087
5386
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
5088
5387
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -5736,7 +6035,7 @@ function parseSbfMeas(data) {
5736
6035
  const tow = view.getUint32(i + 8, true);
5737
6036
  const wnc = view.getUint16(i + 12, true);
5738
6037
  if (tow !== 4294967295 && wnc !== 65535) {
5739
- ensureEpoch(GPS_EPOCH_MS3 + wnc * MS_PER_WEEK4 + tow);
6038
+ ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK4 + tow);
5740
6039
  if (id === 4109) decodeMeas3Ranges(i, tow);
5741
6040
  else if (id === 4110) decodeMeas3CN0(i, len);
5742
6041
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -5792,142 +6091,27 @@ function* oem4Frames(data, view, stats) {
5792
6091
  }
5793
6092
  }
5794
6093
 
5795
- // src/navbits/index.ts
5796
- var GPS_PI = 3.1415926535898;
5797
- var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5798
- var SEC_PER_WEEK2 = 7 * 86400;
5799
- function getBitU(buff, pos, len) {
5800
- let bits = 0;
5801
- for (let i = pos; i < pos + len; i++) {
5802
- bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
5803
- }
5804
- return bits;
5805
- }
5806
- function getBitS(buff, pos, len) {
5807
- const bits = getBitU(buff, pos, len);
5808
- if (len <= 0 || bits < 2 ** (len - 1)) return bits;
5809
- return bits - 2 ** len;
5810
- }
5811
- function decodeGpsLnavFrame(subframes, opts = {}) {
5812
- if (subframes.length < 90) return null;
5813
- const b = subframes;
5814
- let i = 24;
5815
- const tow1 = getBitU(b, i, 17) * 6;
5816
- i += 17 + 2;
5817
- const id1 = getBitU(b, i, 3);
5818
- i += 3 + 2;
5819
- const week10 = getBitU(b, i, 10);
5820
- i += 10;
5821
- i += 2;
5822
- i += 4;
5823
- const svHealth = getBitU(b, i, 6);
5824
- i += 6;
5825
- const iodc0 = getBitU(b, i, 2);
5826
- i += 2;
5827
- i += 1 + 87;
5828
- const tgdRaw = getBitS(b, i, 8);
5829
- i += 8;
5830
- const iodc1 = getBitU(b, i, 8);
5831
- i += 8;
5832
- const tocSec = getBitU(b, i, 16) * 16;
5833
- i += 16;
5834
- const af2 = getBitS(b, i, 8) * 2 ** -55;
5835
- i += 8;
5836
- const af1 = getBitS(b, i, 16) * 2 ** -43;
5837
- i += 16;
5838
- const af0 = getBitS(b, i, 22) * 2 ** -31;
5839
- i = 240 + 24;
5840
- i += 17 + 2;
5841
- const id2 = getBitU(b, i, 3);
5842
- i += 3 + 2;
5843
- const iode = getBitU(b, i, 8);
5844
- i += 8;
5845
- const crs = getBitS(b, i, 16) * 2 ** -5;
5846
- i += 16;
5847
- const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
5848
- i += 16;
5849
- const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5850
- i += 32;
5851
- const cuc = getBitS(b, i, 16) * 2 ** -29;
5852
- i += 16;
5853
- const e = getBitU(b, i, 32) * 2 ** -33;
5854
- i += 32;
5855
- const cus = getBitS(b, i, 16) * 2 ** -29;
5856
- i += 16;
5857
- const sqrtA = getBitU(b, i, 32) * 2 ** -19;
5858
- i += 32;
5859
- const toes = getBitU(b, i, 16) * 16;
5860
- i = 480 + 24;
5861
- i += 17 + 2;
5862
- const id3 = getBitU(b, i, 3);
5863
- i += 3 + 2;
5864
- const cic = getBitS(b, i, 16) * 2 ** -29;
5865
- i += 16;
5866
- const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5867
- i += 32;
5868
- const cis = getBitS(b, i, 16) * 2 ** -29;
5869
- i += 16;
5870
- const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5871
- i += 32;
5872
- const crc = getBitS(b, i, 16) * 2 ** -5;
5873
- i += 16;
5874
- const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5875
- i += 32;
5876
- const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
5877
- i += 24;
5878
- const iode3 = getBitU(b, i, 8);
5879
- i += 8;
5880
- const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
5881
- const iodc = iodc0 * 256 + iodc1;
5882
- if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
5883
- if (iode3 !== iode || iode !== (iodc & 255)) return null;
5884
- const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS4) / 1e3 / SEC_PER_WEEK2);
5885
- let week = week10 + 1024 * Math.round((ref - week10) / 1024);
5886
- if (toes < tow1 - 302400) week++;
5887
- else if (toes > tow1 + 302400) week--;
5888
- const prn = opts.prn ?? "G00";
5889
- const tocDate = new Date(
5890
- GPS_EPOCH_MS4 + (week * SEC_PER_WEEK2 + tocSec) * 1e3
5891
- );
5892
- return {
5893
- system: prn[0] === "J" ? "J" : "G",
5894
- prn,
5895
- tocDate,
5896
- // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
5897
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK2,
5898
- af0,
5899
- af1,
5900
- af2,
5901
- iode,
5902
- crs,
5903
- deltaN,
5904
- m0,
5905
- cuc,
5906
- e,
5907
- cus,
5908
- sqrtA,
5909
- toe: toes,
5910
- cic,
5911
- omega0,
5912
- cis,
5913
- i0,
5914
- crc,
5915
- omega,
5916
- omegaDot,
5917
- idot,
5918
- week,
5919
- svHealth,
5920
- tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
5921
- // IS-GPS-200: -128 reserved
5922
- };
5923
- }
5924
-
5925
6094
  // src/novatel/nav.ts
6095
+ var ID_IONUTC = 8;
5926
6096
  var ID_RAWEPHEM = 41;
5927
6097
  var ID_GLOEPHEMERIS = 723;
6098
+ var ID_GALEPHEMERIS = 1122;
6099
+ var ID_QZSSEPHEMERIS = 1336;
6100
+ var ID_BDSEPHEMERIS = 1696;
5928
6101
  var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
6102
+ var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
5929
6103
  var SEC_PER_WEEK3 = 7 * 86400;
5930
6104
  var SEC_PER_DAY = 86400;
6105
+ var MS_PER_WEEK5 = SEC_PER_WEEK3 * 1e3;
6106
+ var gpsMs2 = (week, sec) => GPS_EPOCH_MS5 + week * MS_PER_WEEK5 + sec * 1e3;
6107
+ var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK3;
6108
+ function nearWeekMs(refMs, sow) {
6109
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS5) / MS_PER_WEEK5);
6110
+ let ms = gpsMs2(refWeek, sow);
6111
+ if (ms < refMs - MS_PER_WEEK5 / 2) ms += MS_PER_WEEK5;
6112
+ else if (ms > refMs + MS_PER_WEEK5 / 2) ms -= MS_PER_WEEK5;
6113
+ return ms;
6114
+ }
5931
6115
  function decodeRawEphem(data, view, p) {
5932
6116
  const prn = view.getUint32(p, true);
5933
6117
  if (prn < 1 || prn > 32) return null;
@@ -5973,23 +6157,184 @@ function decodeGloEphemeris(view, p) {
5973
6157
  freqNum
5974
6158
  };
5975
6159
  }
6160
+ function decodeGalEphemeris(view, p, headerMs) {
6161
+ const prn = view.getUint32(p, true);
6162
+ if (prn < 1 || prn > 36) return null;
6163
+ const fnav = (view.getUint32(p + 4, true) & 1) !== 0;
6164
+ const svhE1b = view.getUint8(p + 12) & 3;
6165
+ const svhE5a = view.getUint8(p + 13) & 3;
6166
+ const svhE5b = view.getUint8(p + 14) & 3;
6167
+ const dvsE1b = view.getUint8(p + 15) & 1;
6168
+ const dvsE5a = view.getUint8(p + 16) & 1;
6169
+ const dvsE5b = view.getUint8(p + 17) & 1;
6170
+ const iodNav = view.getUint32(p + 20, true);
6171
+ const toes = view.getUint32(p + 24, true);
6172
+ const c = fnav ? p + 148 : p + 176;
6173
+ const tocs = view.getUint32(c, true);
6174
+ const toeMs = nearWeekMs(headerMs, toes);
6175
+ const tocDate = new Date(nearWeekMs(headerMs, tocs));
6176
+ return {
6177
+ fnav,
6178
+ eph: {
6179
+ system: "E",
6180
+ prn: `E${String(prn).padStart(2, "0")}`,
6181
+ toc: sowOf2(tocDate.getTime()),
6182
+ tocDate,
6183
+ af0: view.getFloat64(c + 4, true),
6184
+ af1: view.getFloat64(c + 12, true),
6185
+ af2: view.getFloat64(c + 20, true),
6186
+ iode: iodNav,
6187
+ crs: view.getFloat64(p + 92, true),
6188
+ deltaN: view.getFloat64(p + 36, true),
6189
+ m0: view.getFloat64(p + 44, true),
6190
+ cuc: view.getFloat64(p + 68, true),
6191
+ e: view.getFloat64(p + 52, true),
6192
+ cus: view.getFloat64(p + 76, true),
6193
+ sqrtA: view.getFloat64(p + 28, true),
6194
+ toe: toes,
6195
+ cic: view.getFloat64(p + 100, true),
6196
+ omega0: view.getFloat64(p + 132, true),
6197
+ cis: view.getFloat64(p + 108, true),
6198
+ i0: view.getFloat64(p + 116, true),
6199
+ crc: view.getFloat64(p + 84, true),
6200
+ omega: view.getFloat64(p + 60, true),
6201
+ omegaDot: view.getFloat64(p + 140, true),
6202
+ idot: view.getFloat64(p + 124, true),
6203
+ week: Math.floor((toeMs - GPS_EPOCH_MS5) / MS_PER_WEEK5),
6204
+ // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
6205
+ // 3-5, E5b in 6-8) — same packing as RTKLIB.
6206
+ svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
6207
+ tgd: view.getFloat64(p + 204, true)
6208
+ // BGD E5a/E1 (RINEX slot)
6209
+ }
6210
+ };
6211
+ }
6212
+ function decodeBdsEphemeris2(view, p) {
6213
+ const prn = view.getUint32(p, true);
6214
+ if (prn < 1 || prn > 63) return null;
6215
+ const week = view.getUint32(p + 4, true);
6216
+ const health = view.getUint32(p + 16, true) & 1;
6217
+ const tgd1 = view.getFloat64(p + 20, true);
6218
+ const tocs = view.getUint32(p + 40, true);
6219
+ const toes = view.getUint32(p + 72, true);
6220
+ const tocDate = new Date(BDT_EPOCH_MS2 + week * MS_PER_WEEK5 + tocs * 1e3);
6221
+ return {
6222
+ system: "C",
6223
+ prn: `C${String(prn).padStart(2, "0")}`,
6224
+ toc: sowOf2(tocDate.getTime()),
6225
+ tocDate,
6226
+ af0: view.getFloat64(p + 44, true),
6227
+ af1: view.getFloat64(p + 52, true),
6228
+ af2: view.getFloat64(p + 60, true),
6229
+ iode: view.getUint32(p + 68, true),
6230
+ // AODE
6231
+ crs: view.getFloat64(p + 172, true),
6232
+ deltaN: view.getFloat64(p + 100, true),
6233
+ m0: view.getFloat64(p + 108, true),
6234
+ cuc: view.getFloat64(p + 148, true),
6235
+ e: view.getFloat64(p + 84, true),
6236
+ cus: view.getFloat64(p + 156, true),
6237
+ sqrtA: view.getFloat64(p + 76, true),
6238
+ toe: toes,
6239
+ cic: view.getFloat64(p + 180, true),
6240
+ omega0: view.getFloat64(p + 116, true),
6241
+ cis: view.getFloat64(p + 188, true),
6242
+ i0: view.getFloat64(p + 132, true),
6243
+ crc: view.getFloat64(p + 164, true),
6244
+ omega: view.getFloat64(p + 92, true),
6245
+ omegaDot: view.getFloat64(p + 124, true),
6246
+ idot: view.getFloat64(p + 140, true),
6247
+ week,
6248
+ // RINEX BDS week field is the BDT week
6249
+ svHealth: health,
6250
+ tgd: tgd1
6251
+ // TGD1 (B1) — RINEX slot
6252
+ };
6253
+ }
6254
+ function decodeQzssEphemeris2(view, p) {
6255
+ const prn = view.getUint32(p, true);
6256
+ if (prn < 193 || prn > 202) return null;
6257
+ const health = view.getUint32(p + 12, true) & 63;
6258
+ const iode1 = view.getUint32(p + 16, true);
6259
+ const week = view.getUint32(p + 24, true);
6260
+ const toes = view.getFloat64(p + 32, true);
6261
+ const toeMs = gpsMs2(week, toes);
6262
+ const tocs = view.getFloat64(p + 164, true);
6263
+ const tocDate = new Date(nearWeekMs(toeMs, tocs));
6264
+ return {
6265
+ system: "J",
6266
+ prn: `J${String(prn - 192).padStart(2, "0")}`,
6267
+ toc: sowOf2(tocDate.getTime()),
6268
+ tocDate,
6269
+ af0: view.getFloat64(p + 180, true),
6270
+ af1: view.getFloat64(p + 188, true),
6271
+ af2: view.getFloat64(p + 196, true),
6272
+ iode: iode1,
6273
+ crs: view.getFloat64(p + 104, true),
6274
+ deltaN: view.getFloat64(p + 48, true),
6275
+ m0: view.getFloat64(p + 56, true),
6276
+ cuc: view.getFloat64(p + 80, true),
6277
+ e: view.getFloat64(p + 64, true),
6278
+ cus: view.getFloat64(p + 88, true),
6279
+ sqrtA: Math.sqrt(view.getFloat64(p + 40, true)),
6280
+ toe: toes,
6281
+ cic: view.getFloat64(p + 112, true),
6282
+ omega0: view.getFloat64(p + 144, true),
6283
+ cis: view.getFloat64(p + 120, true),
6284
+ i0: view.getFloat64(p + 128, true),
6285
+ crc: view.getFloat64(p + 96, true),
6286
+ omega: view.getFloat64(p + 72, true),
6287
+ omegaDot: view.getFloat64(p + 152, true),
6288
+ idot: view.getFloat64(p + 136, true),
6289
+ week,
6290
+ svHealth: health,
6291
+ tgd: view.getFloat64(p + 172, true)
6292
+ };
6293
+ }
5976
6294
  function parseNovatelNav(data) {
5977
6295
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5978
6296
  const stats = { badCrc: 0 };
5979
6297
  const ephemerides = [];
5980
- const lastGps = /* @__PURE__ */ new Map();
6298
+ const ionoCorrections = {};
6299
+ let leapSeconds = null;
6300
+ const lastKep = /* @__PURE__ */ new Map();
5981
6301
  const lastGlo = /* @__PURE__ */ new Map();
6302
+ const pushKepler = (key, eph) => {
6303
+ const prev = lastKep.get(key);
6304
+ if (prev && prev.iode === eph.iode && prev.toe === eph.toe && prev.tocDate.getTime() === eph.tocDate.getTime()) {
6305
+ return;
6306
+ }
6307
+ lastKep.set(key, eph);
6308
+ ephemerides.push(eph);
6309
+ };
5982
6310
  for (const frame of oem4Frames(data, view, stats)) {
5983
6311
  if (!frame.binary) continue;
5984
6312
  if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
5985
6313
  const eph = decodeRawEphem(data, view, frame.payload);
5986
- if (!eph) continue;
5987
- const prev = lastGps.get(eph.prn);
5988
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
5989
- continue;
6314
+ if (eph) pushKepler(eph.prn, eph);
6315
+ } else if (frame.id === ID_GALEPHEMERIS && frame.msgLen >= 220) {
6316
+ if (frame.week === 0) continue;
6317
+ const headerMs = gpsMs2(frame.week, frame.towMs / 1e3);
6318
+ const dec = decodeGalEphemeris(view, frame.payload, headerMs);
6319
+ if (dec)
6320
+ pushKepler(`${dec.eph.prn}:${dec.fnav ? "fnav" : "inav"}`, dec.eph);
6321
+ } else if (frame.id === ID_BDSEPHEMERIS && frame.msgLen >= 196) {
6322
+ const eph = decodeBdsEphemeris2(view, frame.payload);
6323
+ if (eph) pushKepler(eph.prn, eph);
6324
+ } else if (frame.id === ID_QZSSEPHEMERIS && frame.msgLen >= 204) {
6325
+ const eph = decodeQzssEphemeris2(view, frame.payload);
6326
+ if (eph) pushKepler(eph.prn, eph);
6327
+ } else if (frame.id === ID_IONUTC && frame.msgLen >= 108) {
6328
+ const p = frame.payload;
6329
+ const alpha = [];
6330
+ const beta = [];
6331
+ for (let i = 0; i < 4; i++) {
6332
+ alpha.push(view.getFloat64(p + i * 8, true));
6333
+ beta.push(view.getFloat64(p + 32 + i * 8, true));
5990
6334
  }
5991
- lastGps.set(eph.prn, eph);
5992
- ephemerides.push(eph);
6335
+ ionoCorrections["GPSA"] = alpha;
6336
+ ionoCorrections["GPSB"] = beta;
6337
+ leapSeconds = view.getInt32(p + 96, true);
5993
6338
  } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
5994
6339
  const eph = decodeGloEphemeris(view, frame.payload);
5995
6340
  if (!eph) continue;
@@ -6001,12 +6346,12 @@ function parseNovatelNav(data) {
6001
6346
  ephemerides.push(eph);
6002
6347
  }
6003
6348
  }
6004
- return { ephemerides, badCrc: stats.badCrc };
6349
+ return { ephemerides, ionoCorrections, leapSeconds, badCrc: stats.badCrc };
6005
6350
  }
6006
6351
 
6007
6352
  // src/novatel/index.ts
6008
6353
  var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6009
- var MS_PER_WEEK5 = 7 * 864e5;
6354
+ var MS_PER_WEEK6 = 7 * 864e5;
6010
6355
  var HDR = 28;
6011
6356
  var ID_RANGE = 43;
6012
6357
  var ID_RANGECMP = 140;
@@ -6175,7 +6520,7 @@ function parseNovatelRange(data) {
6175
6520
  if (!codes.includes(code)) codes.push(code);
6176
6521
  }
6177
6522
  epochs.push({
6178
- timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
6523
+ timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK6 + towMs,
6179
6524
  meas
6180
6525
  });
6181
6526
  }
@@ -6227,7 +6572,7 @@ function parseNovatelRange(data) {
6227
6572
  if (!codes.includes(code)) codes.push(code);
6228
6573
  }
6229
6574
  epochs.push({
6230
- timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
6575
+ timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK6 + towMs,
6231
6576
  meas
6232
6577
  });
6233
6578
  }
@@ -6587,9 +6932,9 @@ function selectBest(ephs, timeMs) {
6587
6932
  return best;
6588
6933
  }
6589
6934
  var GPS_EPOCH_MS7 = START_GPS_TIME.getTime();
6590
- var MS_PER_WEEK6 = 7 * 864e5;
6935
+ var MS_PER_WEEK7 = 7 * 864e5;
6591
6936
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
6592
- var GAL_EPOCH_MS = GPS_EPOCH_MS7 + 1024 * MS_PER_WEEK6;
6937
+ var GAL_EPOCH_MS = GPS_EPOCH_MS7 + 1024 * MS_PER_WEEK7;
6593
6938
  function ephInfoToEphemeris(info) {
6594
6939
  const sys = info.prn.charAt(0);
6595
6940
  if (sys === "R") {
@@ -6641,10 +6986,10 @@ function ephInfoToEphemeris(info) {
6641
6986
  epochMs = GAL_EPOCH_MS;
6642
6987
  } else {
6643
6988
  epochMs = GPS_EPOCH_MS7;
6644
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS7) / MS_PER_WEEK6;
6989
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS7) / MS_PER_WEEK7;
6645
6990
  week += 1024 * Math.round((weeksNow - week) / 1024);
6646
6991
  }
6647
- const tocDate = new Date(epochMs + week * MS_PER_WEEK6 + tocSec * 1e3);
6992
+ const tocDate = new Date(epochMs + week * MS_PER_WEEK7 + tocSec * 1e3);
6648
6993
  return {
6649
6994
  system: sys,
6650
6995
  prn: info.prn,
@@ -9743,11 +10088,14 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9743
10088
  parseNovatelRange,
9744
10089
  parseRinexStream,
9745
10090
  parseSbfAlmanac,
10091
+ parseSbfIonoUtc,
9746
10092
  parseSbfMeas,
9747
10093
  parseSbfNav,
9748
10094
  parseSinexBiasDcb,
9749
10095
  parseSourcetable,
9750
10096
  parseSp3,
10097
+ parseUbxIonoUtc,
10098
+ parseUbxNav,
9751
10099
  parseUbxRawx,
9752
10100
  phiAltBOC,
9753
10101
  phiBOCc,
@@ -9761,6 +10109,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9761
10109
  rhumbLine,
9762
10110
  rtcm3Constellation,
9763
10111
  satClockCorrection,
10112
+ sbfCrc16,
10113
+ scanSbfFrames,
9764
10114
  selectEphemeris,
9765
10115
  setGloFreqNumber,
9766
10116
  setRtcm3DebugHandler,
@@ -9770,6 +10120,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9770
10120
  systemCmp,
9771
10121
  systemName,
9772
10122
  transformFrame,
10123
+ ubxFrames,
9773
10124
  updateStationMeta,
9774
10125
  updateStreamStats,
9775
10126
  verifyChecksum,