gnss-js 1.14.1 → 1.16.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
@@ -76,6 +76,7 @@ __export(src_exports, {
76
76
  MILLISECONDS_IN_WEEK: () => MILLISECONDS_IN_WEEK,
77
77
  MILLISECONDS_TT_TAI: () => MILLISECONDS_TT_TAI,
78
78
  MultipathAccumulator: () => MultipathAccumulator,
79
+ OEM4_HLEN: () => OEM4_HLEN,
79
80
  OMEGA_E: () => OMEGA_E,
80
81
  REFERENCE_FRAMES: () => REFERENCE_FRAMES,
81
82
  RINEX_CODES: () => RINEX_CODES,
@@ -119,6 +120,7 @@ __export(src_exports, {
119
120
  computeVisibility: () => computeVisibility,
120
121
  computeVisibilityFromPositions: () => computeVisibilityFromPositions,
121
122
  connectToMountpoint: () => connectToMountpoint,
123
+ crc32: () => crc32,
122
124
  createGzipLineSink: () => createGzipLineSink,
123
125
  createStationMeta: () => createStationMeta,
124
126
  createStreamStats: () => createStreamStats,
@@ -208,6 +210,7 @@ __export(src_exports, {
208
210
  msmEpochToDate: () => msmEpochToDate,
209
211
  navTimesFromEph: () => navTimesFromEph,
210
212
  nmeaCoordToDecimal: () => nmeaCoordToDecimal,
213
+ oem4Frames: () => oem4Frames,
211
214
  padL: () => padL,
212
215
  padR: () => padR,
213
216
  parseAntex: () => parseAntex,
@@ -215,12 +218,16 @@ __export(src_exports, {
215
218
  parseIonex: () => parseIonex,
216
219
  parseNavFile: () => parseNavFile,
217
220
  parseNmeaFile: () => parseNmeaFile,
221
+ parseNovatelNav: () => parseNovatelNav,
218
222
  parseNovatelRange: () => parseNovatelRange,
219
223
  parseRinexStream: () => parseRinexStream,
224
+ parseSbfAlmanac: () => parseSbfAlmanac,
220
225
  parseSbfMeas: () => parseSbfMeas,
226
+ parseSbfNav: () => parseSbfNav,
221
227
  parseSinexBiasDcb: () => parseSinexBiasDcb,
222
228
  parseSourcetable: () => parseSourcetable,
223
229
  parseSp3: () => parseSp3,
230
+ parseUbxNav: () => parseUbxNav,
224
231
  parseUbxRawx: () => parseUbxRawx,
225
232
  phiAltBOC: () => phiAltBOC,
226
233
  phiBOCc: () => phiBOCc,
@@ -234,6 +241,8 @@ __export(src_exports, {
234
241
  rhumbLine: () => rhumbLine,
235
242
  rtcm3Constellation: () => rtcm3Constellation,
236
243
  satClockCorrection: () => satClockCorrection,
244
+ sbfCrc16: () => crc16,
245
+ scanSbfFrames: () => scanSbfFrames,
237
246
  selectEphemeris: () => selectEphemeris,
238
247
  setGloFreqNumber: () => setGloFreqNumber,
239
248
  setRtcm3DebugHandler: () => setRtcm3DebugHandler,
@@ -243,6 +252,7 @@ __export(src_exports, {
243
252
  systemCmp: () => systemCmp,
244
253
  systemName: () => systemName,
245
254
  transformFrame: () => transformFrame,
255
+ ubxFrames: () => ubxFrames,
246
256
  updateStationMeta: () => updateStationMeta,
247
257
  updateStreamStats: () => updateStreamStats,
248
258
  verifyChecksum: () => verifyChecksum,
@@ -4592,8 +4602,255 @@ function updateStreamStats(stats, frames, rawBytes) {
4592
4602
  }
4593
4603
  }
4594
4604
 
4595
- // src/ubx/index.ts
4605
+ // src/ubx/frame.ts
4606
+ function* ubxFrames(data, stats = { badChecksums: 0 }) {
4607
+ let i = 0;
4608
+ while (i + 8 <= data.length) {
4609
+ if (data[i] !== 181 || data[i + 1] !== 98) {
4610
+ i++;
4611
+ continue;
4612
+ }
4613
+ const len = data[i + 4] | data[i + 5] << 8;
4614
+ const end = i + 6 + len + 2;
4615
+ if (end > data.length) break;
4616
+ let ckA = 0;
4617
+ let ckB = 0;
4618
+ for (let j = i + 2; j < i + 6 + len; j++) {
4619
+ ckA = ckA + data[j] & 255;
4620
+ ckB = ckB + ckA & 255;
4621
+ }
4622
+ if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
4623
+ stats.badChecksums++;
4624
+ i++;
4625
+ continue;
4626
+ }
4627
+ yield {
4628
+ msgClass: data[i + 2],
4629
+ msgId: data[i + 3],
4630
+ payload: data.subarray(i + 6, i + 6 + len),
4631
+ payloadStart: i + 6
4632
+ };
4633
+ i = end;
4634
+ }
4635
+ }
4636
+
4637
+ // src/navbits/index.ts
4638
+ var GPS_PI = 3.1415926535898;
4596
4639
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
4640
+ var SEC_PER_WEEK = 7 * 86400;
4641
+ function getBitU(buff, pos, len) {
4642
+ let bits = 0;
4643
+ for (let i = pos; i < pos + len; i++) {
4644
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
4645
+ }
4646
+ return bits;
4647
+ }
4648
+ function getBitS(buff, pos, len) {
4649
+ const bits = getBitU(buff, pos, len);
4650
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
4651
+ return bits - 2 ** len;
4652
+ }
4653
+ function setBitU(buff, pos, len, data) {
4654
+ let mask = 2 ** (len - 1);
4655
+ for (let i = pos; i < pos + len; i++, mask /= 2) {
4656
+ if (data >= mask) {
4657
+ buff[i >> 3] |= 1 << 7 - (i & 7);
4658
+ data -= mask;
4659
+ } else {
4660
+ buff[i >> 3] &= ~(1 << 7 - (i & 7));
4661
+ }
4662
+ }
4663
+ }
4664
+ function decodeGpsLnavFrame(subframes, opts = {}) {
4665
+ if (subframes.length < 90) return null;
4666
+ const b = subframes;
4667
+ let i = 24;
4668
+ const tow1 = getBitU(b, i, 17) * 6;
4669
+ i += 17 + 2;
4670
+ const id1 = getBitU(b, i, 3);
4671
+ i += 3 + 2;
4672
+ const week10 = getBitU(b, i, 10);
4673
+ i += 10;
4674
+ i += 2;
4675
+ i += 4;
4676
+ const svHealth = getBitU(b, i, 6);
4677
+ i += 6;
4678
+ const iodc0 = getBitU(b, i, 2);
4679
+ i += 2;
4680
+ i += 1 + 87;
4681
+ const tgdRaw = getBitS(b, i, 8);
4682
+ i += 8;
4683
+ const iodc1 = getBitU(b, i, 8);
4684
+ i += 8;
4685
+ const tocSec = getBitU(b, i, 16) * 16;
4686
+ i += 16;
4687
+ const af2 = getBitS(b, i, 8) * 2 ** -55;
4688
+ i += 8;
4689
+ const af1 = getBitS(b, i, 16) * 2 ** -43;
4690
+ i += 16;
4691
+ const af0 = getBitS(b, i, 22) * 2 ** -31;
4692
+ i = 240 + 24;
4693
+ i += 17 + 2;
4694
+ const id2 = getBitU(b, i, 3);
4695
+ i += 3 + 2;
4696
+ const iode = getBitU(b, i, 8);
4697
+ i += 8;
4698
+ const crs = getBitS(b, i, 16) * 2 ** -5;
4699
+ i += 16;
4700
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
4701
+ i += 16;
4702
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4703
+ i += 32;
4704
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
4705
+ i += 16;
4706
+ const e = getBitU(b, i, 32) * 2 ** -33;
4707
+ i += 32;
4708
+ const cus = getBitS(b, i, 16) * 2 ** -29;
4709
+ i += 16;
4710
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
4711
+ i += 32;
4712
+ const toes = getBitU(b, i, 16) * 16;
4713
+ i = 480 + 24;
4714
+ i += 17 + 2;
4715
+ const id3 = getBitU(b, i, 3);
4716
+ i += 3 + 2;
4717
+ const cic = getBitS(b, i, 16) * 2 ** -29;
4718
+ i += 16;
4719
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4720
+ i += 32;
4721
+ const cis = getBitS(b, i, 16) * 2 ** -29;
4722
+ i += 16;
4723
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4724
+ i += 32;
4725
+ const crc = getBitS(b, i, 16) * 2 ** -5;
4726
+ i += 16;
4727
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4728
+ i += 32;
4729
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
4730
+ i += 24;
4731
+ const iode3 = getBitU(b, i, 8);
4732
+ i += 8;
4733
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
4734
+ const iodc = iodc0 * 256 + iodc1;
4735
+ if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
4736
+ if (iode3 !== iode || iode !== (iodc & 255)) return null;
4737
+ const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
4738
+ let week = week10 + 1024 * Math.round((ref - week10) / 1024);
4739
+ if (toes < tow1 - 302400) week++;
4740
+ else if (toes > tow1 + 302400) week--;
4741
+ const prn = opts.prn ?? "G00";
4742
+ const tocDate = new Date(
4743
+ GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
4744
+ );
4745
+ return {
4746
+ system: prn[0] === "J" ? "J" : "G",
4747
+ prn,
4748
+ tocDate,
4749
+ // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
4750
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
4751
+ af0,
4752
+ af1,
4753
+ af2,
4754
+ iode,
4755
+ crs,
4756
+ deltaN,
4757
+ m0,
4758
+ cuc,
4759
+ e,
4760
+ cus,
4761
+ sqrtA,
4762
+ toe: toes,
4763
+ cic,
4764
+ omega0,
4765
+ cis,
4766
+ i0,
4767
+ crc,
4768
+ omega,
4769
+ omegaDot,
4770
+ idot,
4771
+ week,
4772
+ svHealth,
4773
+ tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
4774
+ // IS-GPS-200: -128 reserved
4775
+ };
4776
+ }
4777
+
4778
+ // src/ubx/nav.ts
4779
+ var PREAMB_CNAV = 139;
4780
+ function parseUbxNav(data, opts = {}) {
4781
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
4782
+ const ephemerides = [];
4783
+ let badParity = 0;
4784
+ let refWeek = opts.refWeek;
4785
+ if (refWeek === void 0) {
4786
+ for (const f of ubxFrames(data)) {
4787
+ if (f.msgClass === 2 && f.msgId === 21 && f.payload.length >= 16) {
4788
+ const week = view.getUint16(f.payloadStart + 8, true);
4789
+ if (week > 0) {
4790
+ refWeek = week;
4791
+ break;
4792
+ }
4793
+ }
4794
+ }
4795
+ }
4796
+ if (refWeek === void 0) return { ephemerides, badParity };
4797
+ const subframes = /* @__PURE__ */ new Map();
4798
+ const last = /* @__PURE__ */ new Map();
4799
+ for (const f of ubxFrames(data)) {
4800
+ 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);
4823
+ if (id < 1 || id > 5) {
4824
+ badParity++;
4825
+ continue;
4826
+ }
4827
+ if (id > 3) continue;
4828
+ const key = gnssId * 256 + svId;
4829
+ let sf = subframes.get(key);
4830
+ if (!sf) {
4831
+ sf = { buf: new Uint8Array(90), have: 0 };
4832
+ subframes.set(key, sf);
4833
+ }
4834
+ sf.buf.set(buff, (id - 1) * 30);
4835
+ sf.have |= 1 << id - 1;
4836
+ if (id !== 3 || sf.have !== 7) continue;
4837
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
4838
+ if (!eph) {
4839
+ badParity++;
4840
+ continue;
4841
+ }
4842
+ const prev = last.get(prn);
4843
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
4844
+ continue;
4845
+ }
4846
+ last.set(prn, eph);
4847
+ ephemerides.push(eph);
4848
+ }
4849
+ return { ephemerides, badParity };
4850
+ }
4851
+
4852
+ // src/ubx/index.ts
4853
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4597
4854
  var MS_PER_WEEK2 = 7 * 864e5;
4598
4855
  var SIGNALS = {
4599
4856
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -4610,20 +4867,20 @@ var SIGNALS = {
4610
4867
  // GLONASS L1OF, L2OF
4611
4868
  };
4612
4869
  function prnFor(gnssId, svId) {
4613
- const two2 = (n) => String(n).padStart(2, "0");
4870
+ const two3 = (n) => String(n).padStart(2, "0");
4614
4871
  switch (gnssId) {
4615
4872
  case 0:
4616
- return svId >= 1 && svId <= 32 ? `G${two2(svId)}` : null;
4873
+ return svId >= 1 && svId <= 32 ? `G${two3(svId)}` : null;
4617
4874
  case 1:
4618
- return svId >= 120 && svId <= 158 ? `S${two2(svId - 100)}` : null;
4875
+ return svId >= 120 && svId <= 158 ? `S${two3(svId - 100)}` : null;
4619
4876
  case 2:
4620
- return svId >= 1 && svId <= 36 ? `E${two2(svId)}` : null;
4877
+ return svId >= 1 && svId <= 36 ? `E${two3(svId)}` : null;
4621
4878
  case 3:
4622
- return svId >= 1 && svId <= 63 ? `C${two2(svId)}` : null;
4879
+ return svId >= 1 && svId <= 63 ? `C${two3(svId)}` : null;
4623
4880
  case 5:
4624
- return svId >= 1 && svId <= 10 ? `J${two2(svId)}` : null;
4881
+ return svId >= 1 && svId <= 10 ? `J${two3(svId)}` : null;
4625
4882
  case 6:
4626
- return svId >= 1 && svId <= 32 ? `R${two2(svId)}` : null;
4883
+ return svId >= 1 && svId <= 32 ? `R${two3(svId)}` : null;
4627
4884
  default:
4628
4885
  return null;
4629
4886
  }
@@ -4633,33 +4890,14 @@ function parseUbxRawx(data) {
4633
4890
  const epochs = [];
4634
4891
  const messageCounts = {};
4635
4892
  const obsCodes = {};
4636
- let badChecksums = 0;
4637
- let i = 0;
4638
- while (i + 8 <= data.length) {
4639
- if (data[i] !== 181 || data[i + 1] !== 98) {
4640
- i++;
4641
- continue;
4642
- }
4643
- const cls = data[i + 2];
4644
- const id = data[i + 3];
4645
- const len = data[i + 4] | data[i + 5] << 8;
4646
- const end = i + 6 + len + 2;
4647
- if (end > data.length) break;
4648
- let ckA = 0;
4649
- let ckB = 0;
4650
- for (let j = i + 2; j < i + 6 + len; j++) {
4651
- ckA = ckA + data[j] & 255;
4652
- ckB = ckB + ckA & 255;
4653
- }
4654
- if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
4655
- badChecksums++;
4656
- i++;
4657
- continue;
4658
- }
4893
+ const stats = { badChecksums: 0 };
4894
+ for (const frame of ubxFrames(data, stats)) {
4895
+ const { msgClass: cls, msgId: id } = frame;
4896
+ const len = frame.payload.length;
4659
4897
  const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
4660
4898
  messageCounts[key] = (messageCounts[key] ?? 0) + 1;
4661
4899
  if (cls === 2 && id === 21 && len >= 16) {
4662
- const p = i + 6;
4900
+ const p = frame.payloadStart;
4663
4901
  const rcvTow = view.getFloat64(p, true);
4664
4902
  const week = view.getUint16(p + 8, true);
4665
4903
  const leapS = view.getInt8(p + 10);
@@ -4695,20 +4933,16 @@ function parseUbxRawx(data) {
4695
4933
  if (!codes.includes(sig[1])) codes.push(sig[1]);
4696
4934
  }
4697
4935
  epochs.push({
4698
- timeMs: GPS_EPOCH_MS + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
4936
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
4699
4937
  leapS: leapValid ? leapS : null,
4700
4938
  meas
4701
4939
  });
4702
4940
  }
4703
- i = end;
4704
4941
  }
4705
- return { epochs, messageCounts, obsCodes, badChecksums };
4942
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
4706
4943
  }
4707
4944
 
4708
- // src/sbf/index.ts
4709
- var CLIGHT = 299792458;
4710
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4711
- var MS_PER_WEEK3 = 7 * 864e5;
4945
+ // src/sbf/frame.ts
4712
4946
  var CRC_TABLE = (() => {
4713
4947
  const t = new Uint16Array(256);
4714
4948
  for (let n = 0; n < 256; n++) {
@@ -4725,6 +4959,362 @@ function crc16(data, start, end) {
4725
4959
  crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
4726
4960
  return crc;
4727
4961
  }
4962
+ function scanSbfFrames(data, view, onBlock) {
4963
+ let badCrc = 0;
4964
+ let i = 0;
4965
+ while (i + 8 <= data.length) {
4966
+ if (data[i] !== 36 || data[i + 1] !== 64) {
4967
+ i++;
4968
+ continue;
4969
+ }
4970
+ const len = view.getUint16(i + 6, true);
4971
+ if (len < 8 || len % 4 !== 0) {
4972
+ i++;
4973
+ continue;
4974
+ }
4975
+ if (i + len > data.length) break;
4976
+ if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
4977
+ badCrc++;
4978
+ i++;
4979
+ continue;
4980
+ }
4981
+ onBlock(view.getUint16(i + 4, true) & 8191, i, len);
4982
+ i += len;
4983
+ }
4984
+ return badCrc;
4985
+ }
4986
+ var two = (n) => String(n).padStart(2, "0");
4987
+ function svidToPrn(svid) {
4988
+ if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
4989
+ if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
4990
+ if (svid <= 62) return null;
4991
+ if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
4992
+ if (svid <= 70) return null;
4993
+ if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
4994
+ if (svid <= 119) return null;
4995
+ if (svid <= 140) return `S${two(svid - 100)}`;
4996
+ if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
4997
+ if (svid <= 190) return `J${two(svid - 180)}`;
4998
+ if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
4999
+ if (svid <= 215) return `S${two(svid - 157)}`;
5000
+ if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
5001
+ if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
5002
+ return null;
5003
+ }
5004
+
5005
+ // src/sbf/nav.ts
5006
+ var PI2 = Math.PI;
5007
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5008
+ 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;
5011
+ var F4_DNU = -2e10;
5012
+ var U4_DNU = 4294967295;
5013
+ function adjustWeek(refWeek, wn, mod) {
5014
+ let offset = refWeek % mod - wn;
5015
+ if (offset > mod / 2) offset -= mod;
5016
+ if (offset < -(mod / 2 - 1)) offset += mod;
5017
+ return refWeek - offset;
5018
+ }
5019
+ var gpsMs = (week, sec) => GPS_EPOCH_MS3 + week * MS_PER_WEEK3 + sec * 1e3;
5020
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK2;
5021
+ function decodeGpsQzsNav(view, b, sys) {
5022
+ const svid = view.getUint8(b + 14);
5023
+ const prn = sys === "G" ? svid : svid - 180;
5024
+ const max = sys === "G" ? 32 : 10;
5025
+ if (prn < 1 || prn > max) return null;
5026
+ const wnc = view.getUint16(b + 12, true);
5027
+ const tgdRaw = view.getFloat32(b + 28, true);
5028
+ const tocs = view.getUint32(b + 32, true);
5029
+ const toes = view.getUint32(b + 88, true);
5030
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 136, true), 1024);
5031
+ const tocDate = new Date(gpsMs(wnToc, tocs));
5032
+ return {
5033
+ system: sys,
5034
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
5035
+ toc: sowOf(tocDate.getTime()),
5036
+ tocDate,
5037
+ af0: view.getFloat32(b + 44, true),
5038
+ af1: view.getFloat32(b + 40, true),
5039
+ af2: view.getFloat32(b + 36, true),
5040
+ iode: view.getUint8(b + 24),
5041
+ crs: view.getFloat32(b + 48, true),
5042
+ deltaN: view.getFloat32(b + 52, true) * PI2,
5043
+ m0: view.getFloat64(b + 56, true) * PI2,
5044
+ cuc: view.getFloat32(b + 64, true),
5045
+ e: view.getFloat64(b + 68, true),
5046
+ cus: view.getFloat32(b + 76, true),
5047
+ sqrtA: view.getFloat64(b + 80, true),
5048
+ toe: toes,
5049
+ cic: view.getFloat32(b + 92, true),
5050
+ omega0: view.getFloat64(b + 96, true) * PI2,
5051
+ cis: view.getFloat32(b + 104, true),
5052
+ i0: view.getFloat64(b + 108, true) * PI2,
5053
+ crc: view.getFloat32(b + 116, true),
5054
+ omega: view.getFloat64(b + 120, true) * PI2,
5055
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
5056
+ idot: view.getFloat32(b + 132, true) * PI2,
5057
+ // RINEX "GPS week to go with toe": RTKLIB writes WNc for GPS and
5058
+ // the week of toc for QZSS — mirrored here so records compare
5059
+ // 1:1 against convbin output. wnToe is still used for `toe` above.
5060
+ week: sys === "G" ? wnc : wnToc,
5061
+ svHealth: view.getUint8(b + 20),
5062
+ tgd: tgdRaw !== F4_DNU ? tgdRaw : 0
5063
+ };
5064
+ }
5065
+ function decodeGalNav(view, b) {
5066
+ const prn = view.getUint8(b + 14) - 70;
5067
+ if (prn < 1 || prn > 36) return null;
5068
+ const source = view.getUint8(b + 15);
5069
+ if (source !== 2 && source !== 16) return null;
5070
+ const wnc = view.getUint16(b + 12, true);
5071
+ const toes = view.getUint32(b + 100, true);
5072
+ const tocs = view.getUint32(b + 104, true);
5073
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 126, true), 4096);
5074
+ const tocDate = new Date(gpsMs(wnToc, tocs));
5075
+ const health = view.getUint16(b + 130, true);
5076
+ let svh = 0;
5077
+ if (health & 1) svh |= health >> 1 & 7;
5078
+ if (health & 16) svh |= (health >> 5 & 7) << 6;
5079
+ if (health & 256) svh |= (health >> 9 & 7) << 3;
5080
+ const bgdE5a = view.getFloat32(b + 136, true);
5081
+ return {
5082
+ system: "E",
5083
+ prn: `E${String(prn).padStart(2, "0")}`,
5084
+ toc: sowOf(tocDate.getTime()),
5085
+ tocDate,
5086
+ af0: view.getFloat64(b + 116, true),
5087
+ af1: view.getFloat32(b + 112, true),
5088
+ af2: view.getFloat32(b + 108, true),
5089
+ iode: view.getUint16(b + 128, true),
5090
+ // IODnav
5091
+ crs: view.getFloat32(b + 88, true),
5092
+ deltaN: view.getFloat32(b + 72, true) * PI2,
5093
+ m0: view.getFloat64(b + 24, true) * PI2,
5094
+ cuc: view.getFloat32(b + 76, true),
5095
+ e: view.getFloat64(b + 32, true),
5096
+ cus: view.getFloat32(b + 80, true),
5097
+ sqrtA: view.getFloat64(b + 16, true),
5098
+ toe: toes,
5099
+ cic: view.getFloat32(b + 92, true),
5100
+ omega0: view.getFloat64(b + 56, true) * PI2,
5101
+ cis: view.getFloat32(b + 96, true),
5102
+ i0: view.getFloat64(b + 40, true) * PI2,
5103
+ crc: view.getFloat32(b + 84, true),
5104
+ omega: view.getFloat64(b + 48, true) * PI2,
5105
+ omegaDot: view.getFloat32(b + 64, true) * PI2,
5106
+ idot: view.getFloat32(b + 68, true) * PI2,
5107
+ week: wnc,
5108
+ // RINEX GAL week is GPS-aligned and continuous
5109
+ svHealth: svh,
5110
+ tgd: bgdE5a !== F4_DNU ? bgdE5a : 0
5111
+ // BGD E5a/E1 (RINEX slot)
5112
+ };
5113
+ }
5114
+ function decodeBdsNav(view, b) {
5115
+ const prn = svidToPrn(view.getUint8(b + 14));
5116
+ if (!prn || prn[0] !== "C") return null;
5117
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
5118
+ const tgd1 = view.getFloat32(b + 24, true);
5119
+ const tocs = view.getUint32(b + 32, true);
5120
+ const toes = view.getUint32(b + 88, true);
5121
+ const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
5122
+ const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
5123
+ const tocDate = new Date(BDT_EPOCH_MS + wnToc * MS_PER_WEEK3 + tocs * 1e3);
5124
+ return {
5125
+ system: "C",
5126
+ prn,
5127
+ toc: sowOf(tocDate.getTime()),
5128
+ tocDate,
5129
+ af0: view.getFloat32(b + 44, true),
5130
+ af1: view.getFloat32(b + 40, true),
5131
+ af2: view.getFloat32(b + 36, true),
5132
+ iode: view.getUint8(b + 21),
5133
+ // AODE
5134
+ crs: view.getFloat32(b + 48, true),
5135
+ deltaN: view.getFloat32(b + 52, true) * PI2,
5136
+ m0: view.getFloat64(b + 56, true) * PI2,
5137
+ cuc: view.getFloat32(b + 64, true),
5138
+ e: view.getFloat64(b + 68, true),
5139
+ cus: view.getFloat32(b + 76, true),
5140
+ sqrtA: view.getFloat64(b + 80, true),
5141
+ toe: toes,
5142
+ cic: view.getFloat32(b + 92, true),
5143
+ omega0: view.getFloat64(b + 96, true) * PI2,
5144
+ cis: view.getFloat32(b + 104, true),
5145
+ i0: view.getFloat64(b + 108, true) * PI2,
5146
+ crc: view.getFloat32(b + 116, true),
5147
+ omega: view.getFloat64(b + 120, true) * PI2,
5148
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
5149
+ idot: view.getFloat32(b + 132, true) * PI2,
5150
+ week: wnToe,
5151
+ // RINEX BDS week field is the BDT week
5152
+ svHealth: view.getUint8(b + 19),
5153
+ // SatH1
5154
+ tgd: tgd1 !== F4_DNU ? tgd1 : 0
5155
+ // TGD1 B1/B3 (RINEX slot)
5156
+ };
5157
+ }
5158
+ function decodeGloNav(view, b) {
5159
+ const prn = svidToPrn(view.getUint8(b + 14));
5160
+ if (!prn || prn[0] !== "R") return null;
5161
+ const wnc = view.getUint16(b + 12, true);
5162
+ const toes = view.getUint32(b + 76, true);
5163
+ const wnToe = adjustWeek(wnc, view.getUint16(b + 80, true), 1024);
5164
+ const toeGpsMs = gpsMs(wnToe, toes);
5165
+ const leapMs = getGpsLeap(new Date(toeGpsMs)) * 1e3;
5166
+ const tocDate = new Date(toeGpsMs - leapMs);
5167
+ const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
5168
+ const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
5169
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS3) / 1e3 % SEC_PER_WEEK2;
5170
+ return {
5171
+ system: "R",
5172
+ prn,
5173
+ tocDate,
5174
+ // RINEX stores −τn as the clock bias; SBF carries τn (ICD sign)
5175
+ tauN: -view.getFloat32(b + 68, true),
5176
+ gammaN: view.getFloat32(b + 64, true),
5177
+ messageFrameTime,
5178
+ x: view.getFloat64(b + 16, true),
5179
+ // SBF unit is km, like RINEX
5180
+ xDot: view.getFloat32(b + 40, true),
5181
+ xAcc: view.getFloat32(b + 52, true),
5182
+ y: view.getFloat64(b + 24, true),
5183
+ yDot: view.getFloat32(b + 44, true),
5184
+ yAcc: view.getFloat32(b + 56, true),
5185
+ z: view.getFloat64(b + 32, true),
5186
+ zDot: view.getFloat32(b + 48, true),
5187
+ zAcc: view.getFloat32(b + 60, true),
5188
+ // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
5189
+ health: view.getUint8(b + 85) >> 2,
5190
+ freqNum: view.getUint8(b + 15) - 8
5191
+ };
5192
+ }
5193
+ function parseSbfNav(data) {
5194
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5195
+ const ephemerides = [];
5196
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
5197
+ let eph = null;
5198
+ if (id === 5891 && len >= 140) eph = decodeGpsQzsNav(view, b, "G");
5199
+ else if (id === 4095 && len >= 140) eph = decodeGpsQzsNav(view, b, "J");
5200
+ else if (id === 4002 && len >= 149) eph = decodeGalNav(view, b);
5201
+ else if (id === 4081 && len >= 140) eph = decodeBdsNav(view, b);
5202
+ else if (id === 4004 && len >= 96) eph = decodeGloNav(view, b);
5203
+ if (eph) ephemerides.push(eph);
5204
+ });
5205
+ return { ephemerides, badCrc };
5206
+ }
5207
+ var GAL_SQRT_A_NOMINAL = Math.sqrt(296e5);
5208
+ var GAL_I_NOMINAL = 56 / 180 * PI2;
5209
+ var I_REF_03 = 0.3 * PI2;
5210
+ function decodeGpsGalAlm(view, b, sys) {
5211
+ const wnc = view.getUint16(b + 12, true);
5212
+ const toa = view.getUint32(b + 20, true);
5213
+ if (toa === U4_DNU) return null;
5214
+ let prn;
5215
+ let weekAlm;
5216
+ let i0;
5217
+ let sqrtA = view.getFloat32(b + 32, true);
5218
+ let health;
5219
+ if (sys === "G") {
5220
+ prn = view.getUint8(b + 14);
5221
+ if (prn < 1 || prn > 32) return null;
5222
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 256);
5223
+ i0 = I_REF_03 + view.getFloat32(b + 24, true) * PI2;
5224
+ health = view.getUint8(b + 58);
5225
+ } else {
5226
+ prn = view.getUint8(b + 57) - 70;
5227
+ if (prn < 1 || prn > 36) return null;
5228
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 4);
5229
+ i0 = GAL_I_NOMINAL + view.getFloat32(b + 24, true) * PI2;
5230
+ sqrtA += GAL_SQRT_A_NOMINAL;
5231
+ health = view.getUint16(b + 58, true);
5232
+ }
5233
+ return {
5234
+ system: sys,
5235
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
5236
+ weekAlm,
5237
+ toaSec: toa,
5238
+ sqrtA,
5239
+ e: view.getFloat32(b + 16, true),
5240
+ i0OrDeltaI: i0,
5241
+ omega0: view.getFloat32(b + 36, true) * PI2,
5242
+ omega: view.getFloat32(b + 40, true) * PI2,
5243
+ m0: view.getFloat32(b + 44, true) * PI2,
5244
+ omegaDot: view.getFloat32(b + 28, true) * PI2,
5245
+ af0: view.getFloat32(b + 52, true),
5246
+ af1: view.getFloat32(b + 48, true),
5247
+ health
5248
+ };
5249
+ }
5250
+ function decodeBdsAlm(view, b) {
5251
+ const prnStr = svidToPrn(view.getUint8(b + 14));
5252
+ if (!prnStr || prnStr[0] !== "C") return null;
5253
+ const prn = parseInt(prnStr.slice(1), 10);
5254
+ const toa = view.getUint32(b + 16, true);
5255
+ if (toa === U4_DNU) return null;
5256
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
5257
+ const geo = prn <= 5 || prn >= 59;
5258
+ return {
5259
+ system: "C",
5260
+ prn: prnStr,
5261
+ weekAlm: adjustWeek(bdsWeekRef, view.getUint8(b + 15), 256),
5262
+ toaSec: toa,
5263
+ sqrtA: view.getFloat32(b + 20, true),
5264
+ e: view.getFloat32(b + 24, true),
5265
+ i0OrDeltaI: (geo ? 0 : I_REF_03) + view.getFloat32(b + 44, true) * PI2,
5266
+ omega0: view.getFloat32(b + 36, true) * PI2,
5267
+ omega: view.getFloat32(b + 28, true) * PI2,
5268
+ m0: view.getFloat32(b + 32, true) * PI2,
5269
+ omegaDot: view.getFloat32(b + 40, true) * PI2,
5270
+ af0: view.getFloat32(b + 48, true),
5271
+ af1: view.getFloat32(b + 52, true),
5272
+ health: view.getUint16(b + 56, true)
5273
+ };
5274
+ }
5275
+ function decodeGloAlm(view, b) {
5276
+ const prn = svidToPrn(view.getUint8(b + 14));
5277
+ if (!prn || prn[0] !== "R") return null;
5278
+ const wnc = view.getUint16(b + 12, true);
5279
+ const toa = view.getUint32(b + 20, true);
5280
+ if (toa === U4_DNU) return null;
5281
+ return {
5282
+ system: "R",
5283
+ prn,
5284
+ freqNr: view.getUint8(b + 15) - 8,
5285
+ weekAlm: adjustWeek(wnc, view.getUint8(b + 52), 256),
5286
+ toaSec: toa,
5287
+ epsilon: view.getFloat32(b + 16, true),
5288
+ deltaI: view.getFloat32(b + 24, true) * PI2,
5289
+ lambda: view.getFloat32(b + 28, true) * PI2,
5290
+ tLambda: view.getFloat32(b + 32, true),
5291
+ omega: view.getFloat32(b + 36, true) * PI2,
5292
+ deltaT: view.getFloat32(b + 40, true),
5293
+ deltaTDot: view.getFloat32(b + 44, true),
5294
+ tau: view.getFloat32(b + 48, true),
5295
+ health: view.getUint8(b + 53),
5296
+ nDay: view.getUint16(b + 54, true),
5297
+ n4: view.getUint8(b + 57)
5298
+ };
5299
+ }
5300
+ function parseSbfAlmanac(data) {
5301
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5302
+ const almanacs = [];
5303
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
5304
+ let alm = null;
5305
+ if (id === 5892 && len >= 60) alm = decodeGpsGalAlm(view, b, "G");
5306
+ else if (id === 4003 && len >= 61) alm = decodeGpsGalAlm(view, b, "E");
5307
+ else if (id === 4119 && len >= 60) alm = decodeBdsAlm(view, b);
5308
+ else if (id === 4005 && len >= 60) alm = decodeGloAlm(view, b);
5309
+ if (alm) almanacs.push(alm);
5310
+ });
5311
+ return { almanacs, badCrc };
5312
+ }
5313
+
5314
+ // src/sbf/index.ts
5315
+ var CLIGHT = 299792458;
5316
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5317
+ var MS_PER_WEEK4 = 7 * 864e5;
4728
5318
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
4729
5319
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
4730
5320
  var M3_INTERVALS = [
@@ -4823,44 +5413,27 @@ var MEAS2_SIG = [
4823
5413
  ["J", "1E"],
4824
5414
  ["J", "5P"]
4825
5415
  ];
4826
- var two = (n) => String(n).padStart(2, "0");
5416
+ var two2 = (n) => String(n).padStart(2, "0");
4827
5417
  function m3Prn(navsys, svid) {
4828
5418
  switch (navsys) {
4829
5419
  case 0:
4830
- return svid < 32 ? `G${two(svid + 1)}` : null;
5420
+ return svid < 32 ? `G${two2(svid + 1)}` : null;
4831
5421
  case 1:
4832
- return svid < 27 ? `R${two(svid + 1)}` : null;
5422
+ return svid < 27 ? `R${two2(svid + 1)}` : null;
4833
5423
  case 2:
4834
- return svid < 36 ? `E${two(svid + 1)}` : null;
5424
+ return svid < 36 ? `E${two2(svid + 1)}` : null;
4835
5425
  case 3:
4836
- return svid < 50 ? `C${two(svid + 1)}` : null;
5426
+ return svid < 50 ? `C${two2(svid + 1)}` : null;
4837
5427
  case 4:
4838
- return svid <= 38 ? `S${two(svid + 20)}` : null;
5428
+ return svid <= 38 ? `S${two2(svid + 20)}` : null;
4839
5429
  case 5:
4840
- return svid < 10 ? `J${two(svid + 1)}` : null;
5430
+ return svid < 10 ? `J${two2(svid + 1)}` : null;
4841
5431
  case 6:
4842
- return svid < 14 ? `I${two(svid + 1)}` : null;
5432
+ return svid < 14 ? `I${two2(svid + 1)}` : null;
4843
5433
  default:
4844
5434
  return null;
4845
5435
  }
4846
5436
  }
4847
- function svidToPrn(svid) {
4848
- if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
4849
- if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
4850
- if (svid <= 62) return null;
4851
- if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
4852
- if (svid <= 70) return null;
4853
- if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
4854
- if (svid <= 119) return null;
4855
- if (svid <= 140) return `S${two(svid - 100)}`;
4856
- if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
4857
- if (svid <= 190) return `J${two(svid - 180)}`;
4858
- if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
4859
- if (svid <= 215) return `S${two(svid - 157)}`;
4860
- if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
4861
- if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
4862
- return null;
4863
- }
4864
5437
  function carrierFreq(sys, code, fcn) {
4865
5438
  const band = code.charCodeAt(0) - 48;
4866
5439
  switch (sys) {
@@ -4923,7 +5496,6 @@ function parseSbfMeas(data) {
4923
5496
  const epochs = [];
4924
5497
  const messageCounts = {};
4925
5498
  const obsCodes = {};
4926
- let badCrc = 0;
4927
5499
  let ref = freshRefEpoch(-1);
4928
5500
  let curTime = NaN;
4929
5501
  let curMeas = [];
@@ -4954,8 +5526,8 @@ function parseSbfMeas(data) {
4954
5526
  curTime = timeMs;
4955
5527
  }
4956
5528
  };
4957
- const decodeMeas3Ranges = (i2, tow) => {
4958
- const o = i2 + 8;
5529
+ const decodeMeas3Ranges = (i, tow) => {
5530
+ const o = i + 8;
4959
5531
  const constellations = view.getUint16(o + 8, true) & 127;
4960
5532
  const misc = data[o + 10];
4961
5533
  const version = data[o + 11];
@@ -5233,8 +5805,8 @@ function parseSbfMeas(data) {
5233
5805
  }
5234
5806
  }
5235
5807
  };
5236
- const decodeMeas3CN0 = (i2, len) => {
5237
- const flags = view.getUint16(i2 + 14, true);
5808
+ const decodeMeas3CN0 = (i, len) => {
5809
+ const flags = view.getUint16(i + 14, true);
5238
5810
  if ((flags & 7) !== 0) return;
5239
5811
  const maxNibbles = (len - 16) * 2;
5240
5812
  let off = 0;
@@ -5244,7 +5816,7 @@ function parseSbfMeas(data) {
5244
5816
  for (let k = 0; k < e.slots.length; k++) {
5245
5817
  const slot = e.slots[k];
5246
5818
  if (k > 0 && !slot) break;
5247
- const nib = (data[i2 + 16 + (off >> 1)] ?? 0) >> (off & 1) * 4 & 15;
5819
+ const nib = (data[i + 16 + (off >> 1)] ?? 0) >> (off & 1) * 4 & 15;
5248
5820
  if (slot && slot.cn0 !== null) slot.cn0 += nib * 0.0625 - 0.5;
5249
5821
  off++;
5250
5822
  }
@@ -5271,14 +5843,14 @@ function parseSbfMeas(data) {
5271
5843
  if ((v & 1) === 1) value = -value;
5272
5844
  return { value, size };
5273
5845
  };
5274
- const decodeMeas3Doppler = (i2, len) => {
5275
- const flags = view.getUint16(i2 + 14, true);
5846
+ const decodeMeas3Doppler = (i, len) => {
5847
+ const flags = view.getUint16(i + 14, true);
5276
5848
  if ((flags & 7) !== 0) return;
5277
5849
  let off = 0;
5278
5850
  for (const e of curSats) {
5279
5851
  if (off + 16 >= len) break;
5280
5852
  if (!e.prn) continue;
5281
- const master = readPrRate(i2 + 16, off);
5853
+ const master = readPrRate(i + 16, off);
5282
5854
  off += master.size;
5283
5855
  if (master.value === -268435455) continue;
5284
5856
  const refPrRate = ref.sats[e.navsys][e.svid]?.prRate ?? 0;
@@ -5288,20 +5860,20 @@ function parseSbfMeas(data) {
5288
5860
  for (let k = 1; k < e.slots.length; k++) {
5289
5861
  const slot = e.slots[k];
5290
5862
  if (!slot) break;
5291
- const slave = readPrRate(i2 + 16, off);
5863
+ const slave = readPrRate(i + 16, off);
5292
5864
  off += slave.size;
5293
5865
  if (m)
5294
5866
  slot.doppler = (mD * (CLIGHT / m.freq) * 1e3 - slave.value) * 1e-3 / (CLIGHT / slot.freq);
5295
5867
  }
5296
5868
  }
5297
5869
  };
5298
- const decodeMeasEpoch = (i2, len) => {
5299
- const n1 = data[i2 + 14];
5300
- const len1 = data[i2 + 15];
5301
- const len2 = data[i2 + 16];
5302
- if ((data[i2 + 17] & 128) !== 0) return;
5303
- const end = i2 + len;
5304
- let p = i2 + 20;
5870
+ const decodeMeasEpoch = (i, len) => {
5871
+ const n1 = data[i + 14];
5872
+ const len1 = data[i + 15];
5873
+ const len2 = data[i + 16];
5874
+ if ((data[i + 17] & 128) !== 0) return;
5875
+ const end = i + len;
5876
+ let p = i + 20;
5305
5877
  for (let b1 = 0; b1 < n1 && p + 20 <= end; b1++) {
5306
5878
  const ant = data[p + 1] >> 5;
5307
5879
  let sig = data[p + 1] & 31;
@@ -5388,50 +5960,158 @@ function parseSbfMeas(data) {
5388
5960
  }
5389
5961
  }
5390
5962
  };
5391
- let i = 0;
5392
- while (i + 8 <= data.length) {
5393
- if (data[i] !== 36 || data[i + 1] !== 64) {
5394
- i++;
5395
- continue;
5396
- }
5397
- const len = view.getUint16(i + 6, true);
5398
- if (len < 8 || len % 4 !== 0) {
5399
- i++;
5400
- continue;
5401
- }
5402
- if (i + len > data.length) break;
5403
- if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
5404
- badCrc++;
5405
- i++;
5406
- continue;
5407
- }
5408
- const id = view.getUint16(i + 4, true) & 8191;
5963
+ const badCrc = scanSbfFrames(data, view, (id, i, len) => {
5409
5964
  const key = String(id);
5410
5965
  messageCounts[key] = (messageCounts[key] ?? 0) + 1;
5411
5966
  if ((id === 4027 || id === 4109 || id === 4110 || id === 4111) && len >= 16) {
5412
5967
  const tow = view.getUint32(i + 8, true);
5413
5968
  const wnc = view.getUint16(i + 12, true);
5414
5969
  if (tow !== 4294967295 && wnc !== 65535) {
5415
- ensureEpoch(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK3 + tow);
5970
+ ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK4 + tow);
5416
5971
  if (id === 4109) decodeMeas3Ranges(i, tow);
5417
5972
  else if (id === 4110) decodeMeas3CN0(i, len);
5418
5973
  else if (id === 4111) decodeMeas3Doppler(i, len);
5419
5974
  else decodeMeasEpoch(i, len);
5420
5975
  }
5421
5976
  }
5422
- i += len;
5423
- }
5977
+ });
5424
5978
  flush();
5425
5979
  return { epochs, messageCounts, obsCodes, badCrc };
5426
5980
  }
5427
5981
 
5982
+ // src/novatel/frame.ts
5983
+ var OEM4_HLEN = 28;
5984
+ function crc32(data, start, len) {
5985
+ let crc = 0;
5986
+ for (let i = start; i < start + len; i++) {
5987
+ crc ^= data[i];
5988
+ for (let j = 0; j < 8; j++) {
5989
+ crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
5990
+ }
5991
+ }
5992
+ return crc >>> 0;
5993
+ }
5994
+ function* oem4Frames(data, view, stats) {
5995
+ let i = 0;
5996
+ while (i + OEM4_HLEN + 4 <= data.length) {
5997
+ if (data[i] !== 170 || data[i + 1] !== 68 || data[i + 2] !== 18) {
5998
+ i++;
5999
+ continue;
6000
+ }
6001
+ const hlen = data[i + 3];
6002
+ const msgLen = view.getUint16(i + 8, true);
6003
+ const total = hlen + msgLen + 4;
6004
+ if (hlen < OEM4_HLEN || i + total > data.length) {
6005
+ i++;
6006
+ continue;
6007
+ }
6008
+ if (crc32(data, i, hlen + msgLen) !== view.getUint32(i + hlen + msgLen, true)) {
6009
+ stats.badCrc++;
6010
+ i++;
6011
+ continue;
6012
+ }
6013
+ yield {
6014
+ id: view.getUint16(i + 4, true),
6015
+ start: i,
6016
+ payload: i + hlen,
6017
+ msgLen,
6018
+ binary: (data[i + 6] >> 4 & 3) === 0,
6019
+ week: view.getUint16(i + 14, true),
6020
+ towMs: view.getUint32(i + 16, true)
6021
+ };
6022
+ i += total;
6023
+ }
6024
+ }
6025
+
6026
+ // src/novatel/nav.ts
6027
+ var ID_RAWEPHEM = 41;
6028
+ var ID_GLOEPHEMERIS = 723;
6029
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
6030
+ var SEC_PER_WEEK3 = 7 * 86400;
6031
+ var SEC_PER_DAY = 86400;
6032
+ function decodeRawEphem(data, view, p) {
6033
+ const prn = view.getUint32(p, true);
6034
+ if (prn < 1 || prn > 32) return null;
6035
+ const refWeek = view.getUint32(p + 4, true);
6036
+ return decodeGpsLnavFrame(data.subarray(p + 12, p + 102), {
6037
+ prn: `G${String(prn).padStart(2, "0")}`,
6038
+ refWeek
6039
+ });
6040
+ }
6041
+ function decodeGloEphemeris(view, p) {
6042
+ const slot = view.getUint16(p, true) - 37;
6043
+ if (slot < 1 || slot > 27) return null;
6044
+ const freqNum = view.getUint16(p + 2, true) - 7;
6045
+ const week = view.getUint16(p + 6, true);
6046
+ const tow = Math.floor(view.getUint32(p + 8, true) / 1e3 + 0.5);
6047
+ const toff = view.getUint32(p + 12, true);
6048
+ const health = view.getUint32(p + 24, true) < 4 ? 0 : 1;
6049
+ let tof = view.getUint32(p + 124, true) - toff;
6050
+ tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
6051
+ if (tof < tow - 43200) tof += SEC_PER_DAY;
6052
+ 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;
6056
+ return {
6057
+ system: "R",
6058
+ prn: `R${String(slot).padStart(2, "0")}`,
6059
+ tocDate: getUtcDate(new Date(toeGpsMs)),
6060
+ tauN: -view.getFloat64(p + 100, true),
6061
+ // RINEX stores −τ_n
6062
+ gammaN: view.getFloat64(p + 116, true),
6063
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK3 + SEC_PER_WEEK3) % SEC_PER_WEEK3,
6064
+ x: view.getFloat64(p + 28, true) / 1e3,
6065
+ y: view.getFloat64(p + 36, true) / 1e3,
6066
+ z: view.getFloat64(p + 44, true) / 1e3,
6067
+ xDot: view.getFloat64(p + 52, true) / 1e3,
6068
+ yDot: view.getFloat64(p + 60, true) / 1e3,
6069
+ zDot: view.getFloat64(p + 68, true) / 1e3,
6070
+ xAcc: view.getFloat64(p + 76, true) / 1e3,
6071
+ yAcc: view.getFloat64(p + 84, true) / 1e3,
6072
+ zAcc: view.getFloat64(p + 92, true) / 1e3,
6073
+ health,
6074
+ freqNum
6075
+ };
6076
+ }
6077
+ function parseNovatelNav(data) {
6078
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6079
+ const stats = { badCrc: 0 };
6080
+ const ephemerides = [];
6081
+ const lastGps = /* @__PURE__ */ new Map();
6082
+ const lastGlo = /* @__PURE__ */ new Map();
6083
+ for (const frame of oem4Frames(data, view, stats)) {
6084
+ if (!frame.binary) continue;
6085
+ if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
6086
+ 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;
6091
+ }
6092
+ lastGps.set(eph.prn, eph);
6093
+ ephemerides.push(eph);
6094
+ } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
6095
+ const eph = decodeGloEphemeris(view, frame.payload);
6096
+ if (!eph) continue;
6097
+ const prev = lastGlo.get(eph.prn);
6098
+ if (prev && Math.abs(prev.tocDate.getTime() - eph.tocDate.getTime()) < 1e3 && prev.health === eph.health) {
6099
+ continue;
6100
+ }
6101
+ lastGlo.set(eph.prn, eph);
6102
+ ephemerides.push(eph);
6103
+ }
6104
+ }
6105
+ return { ephemerides, badCrc: stats.badCrc };
6106
+ }
6107
+
5428
6108
  // src/novatel/index.ts
5429
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5430
- var MS_PER_WEEK4 = 7 * 864e5;
6109
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6110
+ var MS_PER_WEEK5 = 7 * 864e5;
5431
6111
  var HDR = 28;
5432
6112
  var ID_RANGE = 43;
5433
6113
  var ID_RANGECMP = 140;
5434
- var ID_GLOEPHEMERIS = 723;
6114
+ var ID_GLOEPHEMERIS2 = 723;
5435
6115
  var C_LIGHT2 = 299792458;
5436
6116
  var ADR_ROLL = 8388608;
5437
6117
  function carrierFreq2(sys, code, gloK) {
@@ -5459,16 +6139,6 @@ function carrierFreq2(sys, code, gloK) {
5459
6139
  function exsign(v, bits) {
5460
6140
  return v & 1 << bits - 1 ? v - 2 ** bits : v;
5461
6141
  }
5462
- function crc32(data, start, len) {
5463
- let crc = 0;
5464
- for (let i = start; i < start + len; i++) {
5465
- crc ^= data[i];
5466
- for (let j = 0; j < 8; j++) {
5467
- crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
5468
- }
5469
- }
5470
- return crc >>> 0;
5471
- }
5472
6142
  var SIGNALS2 = {
5473
6143
  0: {
5474
6144
  0: ["G", "1C"],
@@ -5522,7 +6192,7 @@ function parseNovatelRange(data) {
5522
6192
  const hl = data[j + 3];
5523
6193
  const id = view.getUint16(j + 4, true);
5524
6194
  const ln = view.getUint16(j + 8, true);
5525
- if (id !== ID_GLOEPHEMERIS || hl < HDR || j + hl + ln + 4 > data.length)
6195
+ if (id !== ID_GLOEPHEMERIS2 || hl < HDR || j + hl + ln + 4 > data.length)
5526
6196
  continue;
5527
6197
  if (crc32(data, j, hl + ln) !== view.getUint32(j + hl + ln, true)) continue;
5528
6198
  const slot = view.getUint16(j + hl, true) - 37;
@@ -5606,7 +6276,7 @@ function parseNovatelRange(data) {
5606
6276
  if (!codes.includes(code)) codes.push(code);
5607
6277
  }
5608
6278
  epochs.push({
5609
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK4 + towMs,
6279
+ timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
5610
6280
  meas
5611
6281
  });
5612
6282
  }
@@ -5658,7 +6328,7 @@ function parseNovatelRange(data) {
5658
6328
  if (!codes.includes(code)) codes.push(code);
5659
6329
  }
5660
6330
  epochs.push({
5661
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK4 + towMs,
6331
+ timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
5662
6332
  meas
5663
6333
  });
5664
6334
  }
@@ -6017,10 +6687,10 @@ function selectBest(ephs, timeMs) {
6017
6687
  }
6018
6688
  return best;
6019
6689
  }
6020
- var GPS_EPOCH_MS4 = START_GPS_TIME.getTime();
6021
- var MS_PER_WEEK5 = 7 * 864e5;
6690
+ var GPS_EPOCH_MS7 = START_GPS_TIME.getTime();
6691
+ var MS_PER_WEEK6 = 7 * 864e5;
6022
6692
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
6023
- var GAL_EPOCH_MS = GPS_EPOCH_MS4 + 1024 * MS_PER_WEEK5;
6693
+ var GAL_EPOCH_MS = GPS_EPOCH_MS7 + 1024 * MS_PER_WEEK6;
6024
6694
  function ephInfoToEphemeris(info) {
6025
6695
  const sys = info.prn.charAt(0);
6026
6696
  if (sys === "R") {
@@ -6071,11 +6741,11 @@ function ephInfoToEphemeris(info) {
6071
6741
  } else if (sys === "E") {
6072
6742
  epochMs = GAL_EPOCH_MS;
6073
6743
  } else {
6074
- epochMs = GPS_EPOCH_MS4;
6075
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS4) / MS_PER_WEEK5;
6744
+ epochMs = GPS_EPOCH_MS7;
6745
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS7) / MS_PER_WEEK6;
6076
6746
  week += 1024 * Math.round((weeksNow - week) / 1024);
6077
6747
  }
6078
- const tocDate = new Date(epochMs + week * MS_PER_WEEK5 + tocSec * 1e3);
6748
+ const tocDate = new Date(epochMs + week * MS_PER_WEEK6 + tocSec * 1e3);
6079
6749
  return {
6080
6750
  system: sys,
6081
6751
  prn: info.prn,
@@ -9028,6 +9698,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9028
9698
  MILLISECONDS_IN_WEEK,
9029
9699
  MILLISECONDS_TT_TAI,
9030
9700
  MultipathAccumulator,
9701
+ OEM4_HLEN,
9031
9702
  OMEGA_E,
9032
9703
  REFERENCE_FRAMES,
9033
9704
  RINEX_CODES,
@@ -9071,6 +9742,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9071
9742
  computeVisibility,
9072
9743
  computeVisibilityFromPositions,
9073
9744
  connectToMountpoint,
9745
+ crc32,
9074
9746
  createGzipLineSink,
9075
9747
  createStationMeta,
9076
9748
  createStreamStats,
@@ -9160,6 +9832,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9160
9832
  msmEpochToDate,
9161
9833
  navTimesFromEph,
9162
9834
  nmeaCoordToDecimal,
9835
+ oem4Frames,
9163
9836
  padL,
9164
9837
  padR,
9165
9838
  parseAntex,
@@ -9167,12 +9840,16 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9167
9840
  parseIonex,
9168
9841
  parseNavFile,
9169
9842
  parseNmeaFile,
9843
+ parseNovatelNav,
9170
9844
  parseNovatelRange,
9171
9845
  parseRinexStream,
9846
+ parseSbfAlmanac,
9172
9847
  parseSbfMeas,
9848
+ parseSbfNav,
9173
9849
  parseSinexBiasDcb,
9174
9850
  parseSourcetable,
9175
9851
  parseSp3,
9852
+ parseUbxNav,
9176
9853
  parseUbxRawx,
9177
9854
  phiAltBOC,
9178
9855
  phiBOCc,
@@ -9186,6 +9863,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9186
9863
  rhumbLine,
9187
9864
  rtcm3Constellation,
9188
9865
  satClockCorrection,
9866
+ sbfCrc16,
9867
+ scanSbfFrames,
9189
9868
  selectEphemeris,
9190
9869
  setGloFreqNumber,
9191
9870
  setRtcm3DebugHandler,
@@ -9195,6 +9874,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9195
9874
  systemCmp,
9196
9875
  systemName,
9197
9876
  transformFrame,
9877
+ ubxFrames,
9198
9878
  updateStationMeta,
9199
9879
  updateStreamStats,
9200
9880
  verifyChecksum,