gnss-js 1.14.0 → 1.15.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,9 +218,12 @@ __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,
@@ -4224,7 +4230,7 @@ function msmEpochToDate(sys, epochMs, refTime = /* @__PURE__ */ new Date()) {
4224
4230
  let t2 = refMs - refMs % dayMs + diff * dayMs + msOfDay - 3 * 36e5;
4225
4231
  while (t2 - refMs > 3 * dayMs) t2 -= 7 * dayMs;
4226
4232
  while (refMs - t2 > 3 * dayMs) t2 += 7 * dayMs;
4227
- return new Date(t2);
4233
+ return new Date(t2 + getGpsLeap(new Date(t2)) * 1e3);
4228
4234
  }
4229
4235
  if (sys === "C") {
4230
4236
  const BDS_EPOCH = START_BDS_TIME.getTime();
@@ -4232,14 +4238,12 @@ function msmEpochToDate(sys, epochMs, refTime = /* @__PURE__ */ new Date()) {
4232
4238
  let t2 = BDS_EPOCH + weeksSinceEpoch2 * MS_PER_WEEK + epochMs;
4233
4239
  if (t2 - refMs > MS_PER_WEEK / 2) t2 -= MS_PER_WEEK;
4234
4240
  else if (refMs - t2 > MS_PER_WEEK / 2) t2 += MS_PER_WEEK;
4235
- t2 -= 18e3;
4236
4241
  return new Date(t2);
4237
4242
  }
4238
4243
  const weeksSinceEpoch = Math.floor((refMs - GPS_EPOCH) / MS_PER_WEEK);
4239
4244
  let t = GPS_EPOCH + weeksSinceEpoch * MS_PER_WEEK + epochMs;
4240
4245
  if (t - refMs > MS_PER_WEEK / 2) t -= MS_PER_WEEK;
4241
4246
  else if (refMs - t > MS_PER_WEEK / 2) t += MS_PER_WEEK;
4242
- t -= 18e3;
4243
4247
  return new Date(t);
4244
4248
  }
4245
4249
  function setGloFreqNumber(slot, k) {
@@ -4612,20 +4616,20 @@ var SIGNALS = {
4612
4616
  // GLONASS L1OF, L2OF
4613
4617
  };
4614
4618
  function prnFor(gnssId, svId) {
4615
- const two2 = (n) => String(n).padStart(2, "0");
4619
+ const two3 = (n) => String(n).padStart(2, "0");
4616
4620
  switch (gnssId) {
4617
4621
  case 0:
4618
- return svId >= 1 && svId <= 32 ? `G${two2(svId)}` : null;
4622
+ return svId >= 1 && svId <= 32 ? `G${two3(svId)}` : null;
4619
4623
  case 1:
4620
- return svId >= 120 && svId <= 158 ? `S${two2(svId - 100)}` : null;
4624
+ return svId >= 120 && svId <= 158 ? `S${two3(svId - 100)}` : null;
4621
4625
  case 2:
4622
- return svId >= 1 && svId <= 36 ? `E${two2(svId)}` : null;
4626
+ return svId >= 1 && svId <= 36 ? `E${two3(svId)}` : null;
4623
4627
  case 3:
4624
- return svId >= 1 && svId <= 63 ? `C${two2(svId)}` : null;
4628
+ return svId >= 1 && svId <= 63 ? `C${two3(svId)}` : null;
4625
4629
  case 5:
4626
- return svId >= 1 && svId <= 10 ? `J${two2(svId)}` : null;
4630
+ return svId >= 1 && svId <= 10 ? `J${two3(svId)}` : null;
4627
4631
  case 6:
4628
- return svId >= 1 && svId <= 32 ? `R${two2(svId)}` : null;
4632
+ return svId >= 1 && svId <= 32 ? `R${two3(svId)}` : null;
4629
4633
  default:
4630
4634
  return null;
4631
4635
  }
@@ -4707,10 +4711,7 @@ function parseUbxRawx(data) {
4707
4711
  return { epochs, messageCounts, obsCodes, badChecksums };
4708
4712
  }
4709
4713
 
4710
- // src/sbf/index.ts
4711
- var CLIGHT = 299792458;
4712
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4713
- var MS_PER_WEEK3 = 7 * 864e5;
4714
+ // src/sbf/frame.ts
4714
4715
  var CRC_TABLE = (() => {
4715
4716
  const t = new Uint16Array(256);
4716
4717
  for (let n = 0; n < 256; n++) {
@@ -4727,6 +4728,362 @@ function crc16(data, start, end) {
4727
4728
  crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
4728
4729
  return crc;
4729
4730
  }
4731
+ function scanSbfFrames(data, view, onBlock) {
4732
+ let badCrc = 0;
4733
+ let i = 0;
4734
+ while (i + 8 <= data.length) {
4735
+ if (data[i] !== 36 || data[i + 1] !== 64) {
4736
+ i++;
4737
+ continue;
4738
+ }
4739
+ const len = view.getUint16(i + 6, true);
4740
+ if (len < 8 || len % 4 !== 0) {
4741
+ i++;
4742
+ continue;
4743
+ }
4744
+ if (i + len > data.length) break;
4745
+ if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
4746
+ badCrc++;
4747
+ i++;
4748
+ continue;
4749
+ }
4750
+ onBlock(view.getUint16(i + 4, true) & 8191, i, len);
4751
+ i += len;
4752
+ }
4753
+ return badCrc;
4754
+ }
4755
+ var two = (n) => String(n).padStart(2, "0");
4756
+ function svidToPrn(svid) {
4757
+ if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
4758
+ if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
4759
+ if (svid <= 62) return null;
4760
+ if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
4761
+ if (svid <= 70) return null;
4762
+ if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
4763
+ if (svid <= 119) return null;
4764
+ if (svid <= 140) return `S${two(svid - 100)}`;
4765
+ if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
4766
+ if (svid <= 190) return `J${two(svid - 180)}`;
4767
+ if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
4768
+ if (svid <= 215) return `S${two(svid - 157)}`;
4769
+ if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
4770
+ if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
4771
+ return null;
4772
+ }
4773
+
4774
+ // src/sbf/nav.ts
4775
+ var PI2 = Math.PI;
4776
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4777
+ 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;
4780
+ var F4_DNU = -2e10;
4781
+ var U4_DNU = 4294967295;
4782
+ function adjustWeek(refWeek, wn, mod) {
4783
+ let offset = refWeek % mod - wn;
4784
+ if (offset > mod / 2) offset -= mod;
4785
+ if (offset < -(mod / 2 - 1)) offset += mod;
4786
+ return refWeek - offset;
4787
+ }
4788
+ var gpsMs = (week, sec) => GPS_EPOCH_MS2 + week * MS_PER_WEEK3 + sec * 1e3;
4789
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK;
4790
+ function decodeGpsQzsNav(view, b, sys) {
4791
+ const svid = view.getUint8(b + 14);
4792
+ const prn = sys === "G" ? svid : svid - 180;
4793
+ const max = sys === "G" ? 32 : 10;
4794
+ if (prn < 1 || prn > max) return null;
4795
+ const wnc = view.getUint16(b + 12, true);
4796
+ const tgdRaw = view.getFloat32(b + 28, true);
4797
+ const tocs = view.getUint32(b + 32, true);
4798
+ const toes = view.getUint32(b + 88, true);
4799
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 136, true), 1024);
4800
+ const tocDate = new Date(gpsMs(wnToc, tocs));
4801
+ return {
4802
+ system: sys,
4803
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
4804
+ toc: sowOf(tocDate.getTime()),
4805
+ tocDate,
4806
+ af0: view.getFloat32(b + 44, true),
4807
+ af1: view.getFloat32(b + 40, true),
4808
+ af2: view.getFloat32(b + 36, true),
4809
+ iode: view.getUint8(b + 24),
4810
+ crs: view.getFloat32(b + 48, true),
4811
+ deltaN: view.getFloat32(b + 52, true) * PI2,
4812
+ m0: view.getFloat64(b + 56, true) * PI2,
4813
+ cuc: view.getFloat32(b + 64, true),
4814
+ e: view.getFloat64(b + 68, true),
4815
+ cus: view.getFloat32(b + 76, true),
4816
+ sqrtA: view.getFloat64(b + 80, true),
4817
+ toe: toes,
4818
+ cic: view.getFloat32(b + 92, true),
4819
+ omega0: view.getFloat64(b + 96, true) * PI2,
4820
+ cis: view.getFloat32(b + 104, true),
4821
+ i0: view.getFloat64(b + 108, true) * PI2,
4822
+ crc: view.getFloat32(b + 116, true),
4823
+ omega: view.getFloat64(b + 120, true) * PI2,
4824
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
4825
+ idot: view.getFloat32(b + 132, true) * PI2,
4826
+ // RINEX "GPS week to go with toe": RTKLIB writes WNc for GPS and
4827
+ // the week of toc for QZSS — mirrored here so records compare
4828
+ // 1:1 against convbin output. wnToe is still used for `toe` above.
4829
+ week: sys === "G" ? wnc : wnToc,
4830
+ svHealth: view.getUint8(b + 20),
4831
+ tgd: tgdRaw !== F4_DNU ? tgdRaw : 0
4832
+ };
4833
+ }
4834
+ function decodeGalNav(view, b) {
4835
+ const prn = view.getUint8(b + 14) - 70;
4836
+ if (prn < 1 || prn > 36) return null;
4837
+ const source = view.getUint8(b + 15);
4838
+ if (source !== 2 && source !== 16) return null;
4839
+ const wnc = view.getUint16(b + 12, true);
4840
+ const toes = view.getUint32(b + 100, true);
4841
+ const tocs = view.getUint32(b + 104, true);
4842
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 126, true), 4096);
4843
+ const tocDate = new Date(gpsMs(wnToc, tocs));
4844
+ const health = view.getUint16(b + 130, true);
4845
+ let svh = 0;
4846
+ if (health & 1) svh |= health >> 1 & 7;
4847
+ if (health & 16) svh |= (health >> 5 & 7) << 6;
4848
+ if (health & 256) svh |= (health >> 9 & 7) << 3;
4849
+ const bgdE5a = view.getFloat32(b + 136, true);
4850
+ return {
4851
+ system: "E",
4852
+ prn: `E${String(prn).padStart(2, "0")}`,
4853
+ toc: sowOf(tocDate.getTime()),
4854
+ tocDate,
4855
+ af0: view.getFloat64(b + 116, true),
4856
+ af1: view.getFloat32(b + 112, true),
4857
+ af2: view.getFloat32(b + 108, true),
4858
+ iode: view.getUint16(b + 128, true),
4859
+ // IODnav
4860
+ crs: view.getFloat32(b + 88, true),
4861
+ deltaN: view.getFloat32(b + 72, true) * PI2,
4862
+ m0: view.getFloat64(b + 24, true) * PI2,
4863
+ cuc: view.getFloat32(b + 76, true),
4864
+ e: view.getFloat64(b + 32, true),
4865
+ cus: view.getFloat32(b + 80, true),
4866
+ sqrtA: view.getFloat64(b + 16, true),
4867
+ toe: toes,
4868
+ cic: view.getFloat32(b + 92, true),
4869
+ omega0: view.getFloat64(b + 56, true) * PI2,
4870
+ cis: view.getFloat32(b + 96, true),
4871
+ i0: view.getFloat64(b + 40, true) * PI2,
4872
+ crc: view.getFloat32(b + 84, true),
4873
+ omega: view.getFloat64(b + 48, true) * PI2,
4874
+ omegaDot: view.getFloat32(b + 64, true) * PI2,
4875
+ idot: view.getFloat32(b + 68, true) * PI2,
4876
+ week: wnc,
4877
+ // RINEX GAL week is GPS-aligned and continuous
4878
+ svHealth: svh,
4879
+ tgd: bgdE5a !== F4_DNU ? bgdE5a : 0
4880
+ // BGD E5a/E1 (RINEX slot)
4881
+ };
4882
+ }
4883
+ function decodeBdsNav(view, b) {
4884
+ const prn = svidToPrn(view.getUint8(b + 14));
4885
+ if (!prn || prn[0] !== "C") return null;
4886
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
4887
+ const tgd1 = view.getFloat32(b + 24, true);
4888
+ const tocs = view.getUint32(b + 32, true);
4889
+ const toes = view.getUint32(b + 88, true);
4890
+ const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
4891
+ const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
4892
+ const tocDate = new Date(BDT_EPOCH_MS + wnToc * MS_PER_WEEK3 + tocs * 1e3);
4893
+ return {
4894
+ system: "C",
4895
+ prn,
4896
+ toc: sowOf(tocDate.getTime()),
4897
+ tocDate,
4898
+ af0: view.getFloat32(b + 44, true),
4899
+ af1: view.getFloat32(b + 40, true),
4900
+ af2: view.getFloat32(b + 36, true),
4901
+ iode: view.getUint8(b + 21),
4902
+ // AODE
4903
+ crs: view.getFloat32(b + 48, true),
4904
+ deltaN: view.getFloat32(b + 52, true) * PI2,
4905
+ m0: view.getFloat64(b + 56, true) * PI2,
4906
+ cuc: view.getFloat32(b + 64, true),
4907
+ e: view.getFloat64(b + 68, true),
4908
+ cus: view.getFloat32(b + 76, true),
4909
+ sqrtA: view.getFloat64(b + 80, true),
4910
+ toe: toes,
4911
+ cic: view.getFloat32(b + 92, true),
4912
+ omega0: view.getFloat64(b + 96, true) * PI2,
4913
+ cis: view.getFloat32(b + 104, true),
4914
+ i0: view.getFloat64(b + 108, true) * PI2,
4915
+ crc: view.getFloat32(b + 116, true),
4916
+ omega: view.getFloat64(b + 120, true) * PI2,
4917
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
4918
+ idot: view.getFloat32(b + 132, true) * PI2,
4919
+ week: wnToe,
4920
+ // RINEX BDS week field is the BDT week
4921
+ svHealth: view.getUint8(b + 19),
4922
+ // SatH1
4923
+ tgd: tgd1 !== F4_DNU ? tgd1 : 0
4924
+ // TGD1 B1/B3 (RINEX slot)
4925
+ };
4926
+ }
4927
+ function decodeGloNav(view, b) {
4928
+ const prn = svidToPrn(view.getUint8(b + 14));
4929
+ if (!prn || prn[0] !== "R") return null;
4930
+ const wnc = view.getUint16(b + 12, true);
4931
+ const toes = view.getUint32(b + 76, true);
4932
+ const wnToe = adjustWeek(wnc, view.getUint16(b + 80, true), 1024);
4933
+ const toeGpsMs = gpsMs(wnToe, toes);
4934
+ const leapMs = getGpsLeap(new Date(toeGpsMs)) * 1e3;
4935
+ const tocDate = new Date(toeGpsMs - leapMs);
4936
+ const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
4937
+ const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
4938
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS2) / 1e3 % SEC_PER_WEEK;
4939
+ return {
4940
+ system: "R",
4941
+ prn,
4942
+ tocDate,
4943
+ // RINEX stores −τn as the clock bias; SBF carries τn (ICD sign)
4944
+ tauN: -view.getFloat32(b + 68, true),
4945
+ gammaN: view.getFloat32(b + 64, true),
4946
+ messageFrameTime,
4947
+ x: view.getFloat64(b + 16, true),
4948
+ // SBF unit is km, like RINEX
4949
+ xDot: view.getFloat32(b + 40, true),
4950
+ xAcc: view.getFloat32(b + 52, true),
4951
+ y: view.getFloat64(b + 24, true),
4952
+ yDot: view.getFloat32(b + 44, true),
4953
+ yAcc: view.getFloat32(b + 56, true),
4954
+ z: view.getFloat64(b + 32, true),
4955
+ zDot: view.getFloat32(b + 48, true),
4956
+ zAcc: view.getFloat32(b + 60, true),
4957
+ // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
4958
+ health: view.getUint8(b + 85) >> 2,
4959
+ freqNum: view.getUint8(b + 15) - 8
4960
+ };
4961
+ }
4962
+ function parseSbfNav(data) {
4963
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
4964
+ const ephemerides = [];
4965
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
4966
+ let eph = null;
4967
+ if (id === 5891 && len >= 140) eph = decodeGpsQzsNav(view, b, "G");
4968
+ else if (id === 4095 && len >= 140) eph = decodeGpsQzsNav(view, b, "J");
4969
+ else if (id === 4002 && len >= 149) eph = decodeGalNav(view, b);
4970
+ else if (id === 4081 && len >= 140) eph = decodeBdsNav(view, b);
4971
+ else if (id === 4004 && len >= 96) eph = decodeGloNav(view, b);
4972
+ if (eph) ephemerides.push(eph);
4973
+ });
4974
+ return { ephemerides, badCrc };
4975
+ }
4976
+ var GAL_SQRT_A_NOMINAL = Math.sqrt(296e5);
4977
+ var GAL_I_NOMINAL = 56 / 180 * PI2;
4978
+ var I_REF_03 = 0.3 * PI2;
4979
+ function decodeGpsGalAlm(view, b, sys) {
4980
+ const wnc = view.getUint16(b + 12, true);
4981
+ const toa = view.getUint32(b + 20, true);
4982
+ if (toa === U4_DNU) return null;
4983
+ let prn;
4984
+ let weekAlm;
4985
+ let i0;
4986
+ let sqrtA = view.getFloat32(b + 32, true);
4987
+ let health;
4988
+ if (sys === "G") {
4989
+ prn = view.getUint8(b + 14);
4990
+ if (prn < 1 || prn > 32) return null;
4991
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 256);
4992
+ i0 = I_REF_03 + view.getFloat32(b + 24, true) * PI2;
4993
+ health = view.getUint8(b + 58);
4994
+ } else {
4995
+ prn = view.getUint8(b + 57) - 70;
4996
+ if (prn < 1 || prn > 36) return null;
4997
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 4);
4998
+ i0 = GAL_I_NOMINAL + view.getFloat32(b + 24, true) * PI2;
4999
+ sqrtA += GAL_SQRT_A_NOMINAL;
5000
+ health = view.getUint16(b + 58, true);
5001
+ }
5002
+ return {
5003
+ system: sys,
5004
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
5005
+ weekAlm,
5006
+ toaSec: toa,
5007
+ sqrtA,
5008
+ e: view.getFloat32(b + 16, true),
5009
+ i0OrDeltaI: i0,
5010
+ omega0: view.getFloat32(b + 36, true) * PI2,
5011
+ omega: view.getFloat32(b + 40, true) * PI2,
5012
+ m0: view.getFloat32(b + 44, true) * PI2,
5013
+ omegaDot: view.getFloat32(b + 28, true) * PI2,
5014
+ af0: view.getFloat32(b + 52, true),
5015
+ af1: view.getFloat32(b + 48, true),
5016
+ health
5017
+ };
5018
+ }
5019
+ function decodeBdsAlm(view, b) {
5020
+ const prnStr = svidToPrn(view.getUint8(b + 14));
5021
+ if (!prnStr || prnStr[0] !== "C") return null;
5022
+ const prn = parseInt(prnStr.slice(1), 10);
5023
+ const toa = view.getUint32(b + 16, true);
5024
+ if (toa === U4_DNU) return null;
5025
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
5026
+ const geo = prn <= 5 || prn >= 59;
5027
+ return {
5028
+ system: "C",
5029
+ prn: prnStr,
5030
+ weekAlm: adjustWeek(bdsWeekRef, view.getUint8(b + 15), 256),
5031
+ toaSec: toa,
5032
+ sqrtA: view.getFloat32(b + 20, true),
5033
+ e: view.getFloat32(b + 24, true),
5034
+ i0OrDeltaI: (geo ? 0 : I_REF_03) + view.getFloat32(b + 44, true) * PI2,
5035
+ omega0: view.getFloat32(b + 36, true) * PI2,
5036
+ omega: view.getFloat32(b + 28, true) * PI2,
5037
+ m0: view.getFloat32(b + 32, true) * PI2,
5038
+ omegaDot: view.getFloat32(b + 40, true) * PI2,
5039
+ af0: view.getFloat32(b + 48, true),
5040
+ af1: view.getFloat32(b + 52, true),
5041
+ health: view.getUint16(b + 56, true)
5042
+ };
5043
+ }
5044
+ function decodeGloAlm(view, b) {
5045
+ const prn = svidToPrn(view.getUint8(b + 14));
5046
+ if (!prn || prn[0] !== "R") return null;
5047
+ const wnc = view.getUint16(b + 12, true);
5048
+ const toa = view.getUint32(b + 20, true);
5049
+ if (toa === U4_DNU) return null;
5050
+ return {
5051
+ system: "R",
5052
+ prn,
5053
+ freqNr: view.getUint8(b + 15) - 8,
5054
+ weekAlm: adjustWeek(wnc, view.getUint8(b + 52), 256),
5055
+ toaSec: toa,
5056
+ epsilon: view.getFloat32(b + 16, true),
5057
+ deltaI: view.getFloat32(b + 24, true) * PI2,
5058
+ lambda: view.getFloat32(b + 28, true) * PI2,
5059
+ tLambda: view.getFloat32(b + 32, true),
5060
+ omega: view.getFloat32(b + 36, true) * PI2,
5061
+ deltaT: view.getFloat32(b + 40, true),
5062
+ deltaTDot: view.getFloat32(b + 44, true),
5063
+ tau: view.getFloat32(b + 48, true),
5064
+ health: view.getUint8(b + 53),
5065
+ nDay: view.getUint16(b + 54, true),
5066
+ n4: view.getUint8(b + 57)
5067
+ };
5068
+ }
5069
+ function parseSbfAlmanac(data) {
5070
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5071
+ const almanacs = [];
5072
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
5073
+ let alm = null;
5074
+ if (id === 5892 && len >= 60) alm = decodeGpsGalAlm(view, b, "G");
5075
+ else if (id === 4003 && len >= 61) alm = decodeGpsGalAlm(view, b, "E");
5076
+ else if (id === 4119 && len >= 60) alm = decodeBdsAlm(view, b);
5077
+ else if (id === 4005 && len >= 60) alm = decodeGloAlm(view, b);
5078
+ if (alm) almanacs.push(alm);
5079
+ });
5080
+ return { almanacs, badCrc };
5081
+ }
5082
+
5083
+ // src/sbf/index.ts
5084
+ var CLIGHT = 299792458;
5085
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5086
+ var MS_PER_WEEK4 = 7 * 864e5;
4730
5087
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
4731
5088
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
4732
5089
  var M3_INTERVALS = [
@@ -4825,44 +5182,27 @@ var MEAS2_SIG = [
4825
5182
  ["J", "1E"],
4826
5183
  ["J", "5P"]
4827
5184
  ];
4828
- var two = (n) => String(n).padStart(2, "0");
5185
+ var two2 = (n) => String(n).padStart(2, "0");
4829
5186
  function m3Prn(navsys, svid) {
4830
5187
  switch (navsys) {
4831
5188
  case 0:
4832
- return svid < 32 ? `G${two(svid + 1)}` : null;
5189
+ return svid < 32 ? `G${two2(svid + 1)}` : null;
4833
5190
  case 1:
4834
- return svid < 27 ? `R${two(svid + 1)}` : null;
5191
+ return svid < 27 ? `R${two2(svid + 1)}` : null;
4835
5192
  case 2:
4836
- return svid < 36 ? `E${two(svid + 1)}` : null;
5193
+ return svid < 36 ? `E${two2(svid + 1)}` : null;
4837
5194
  case 3:
4838
- return svid < 50 ? `C${two(svid + 1)}` : null;
5195
+ return svid < 50 ? `C${two2(svid + 1)}` : null;
4839
5196
  case 4:
4840
- return svid <= 38 ? `S${two(svid + 20)}` : null;
5197
+ return svid <= 38 ? `S${two2(svid + 20)}` : null;
4841
5198
  case 5:
4842
- return svid < 10 ? `J${two(svid + 1)}` : null;
5199
+ return svid < 10 ? `J${two2(svid + 1)}` : null;
4843
5200
  case 6:
4844
- return svid < 14 ? `I${two(svid + 1)}` : null;
5201
+ return svid < 14 ? `I${two2(svid + 1)}` : null;
4845
5202
  default:
4846
5203
  return null;
4847
5204
  }
4848
5205
  }
4849
- function svidToPrn(svid) {
4850
- if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
4851
- if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
4852
- if (svid <= 62) return null;
4853
- if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
4854
- if (svid <= 70) return null;
4855
- if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
4856
- if (svid <= 119) return null;
4857
- if (svid <= 140) return `S${two(svid - 100)}`;
4858
- if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
4859
- if (svid <= 190) return `J${two(svid - 180)}`;
4860
- if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
4861
- if (svid <= 215) return `S${two(svid - 157)}`;
4862
- if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
4863
- if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
4864
- return null;
4865
- }
4866
5206
  function carrierFreq(sys, code, fcn) {
4867
5207
  const band = code.charCodeAt(0) - 48;
4868
5208
  switch (sys) {
@@ -4925,7 +5265,6 @@ function parseSbfMeas(data) {
4925
5265
  const epochs = [];
4926
5266
  const messageCounts = {};
4927
5267
  const obsCodes = {};
4928
- let badCrc = 0;
4929
5268
  let ref = freshRefEpoch(-1);
4930
5269
  let curTime = NaN;
4931
5270
  let curMeas = [];
@@ -4956,8 +5295,8 @@ function parseSbfMeas(data) {
4956
5295
  curTime = timeMs;
4957
5296
  }
4958
5297
  };
4959
- const decodeMeas3Ranges = (i2, tow) => {
4960
- const o = i2 + 8;
5298
+ const decodeMeas3Ranges = (i, tow) => {
5299
+ const o = i + 8;
4961
5300
  const constellations = view.getUint16(o + 8, true) & 127;
4962
5301
  const misc = data[o + 10];
4963
5302
  const version = data[o + 11];
@@ -5235,8 +5574,8 @@ function parseSbfMeas(data) {
5235
5574
  }
5236
5575
  }
5237
5576
  };
5238
- const decodeMeas3CN0 = (i2, len) => {
5239
- const flags = view.getUint16(i2 + 14, true);
5577
+ const decodeMeas3CN0 = (i, len) => {
5578
+ const flags = view.getUint16(i + 14, true);
5240
5579
  if ((flags & 7) !== 0) return;
5241
5580
  const maxNibbles = (len - 16) * 2;
5242
5581
  let off = 0;
@@ -5246,7 +5585,7 @@ function parseSbfMeas(data) {
5246
5585
  for (let k = 0; k < e.slots.length; k++) {
5247
5586
  const slot = e.slots[k];
5248
5587
  if (k > 0 && !slot) break;
5249
- const nib = (data[i2 + 16 + (off >> 1)] ?? 0) >> (off & 1) * 4 & 15;
5588
+ const nib = (data[i + 16 + (off >> 1)] ?? 0) >> (off & 1) * 4 & 15;
5250
5589
  if (slot && slot.cn0 !== null) slot.cn0 += nib * 0.0625 - 0.5;
5251
5590
  off++;
5252
5591
  }
@@ -5273,14 +5612,14 @@ function parseSbfMeas(data) {
5273
5612
  if ((v & 1) === 1) value = -value;
5274
5613
  return { value, size };
5275
5614
  };
5276
- const decodeMeas3Doppler = (i2, len) => {
5277
- const flags = view.getUint16(i2 + 14, true);
5615
+ const decodeMeas3Doppler = (i, len) => {
5616
+ const flags = view.getUint16(i + 14, true);
5278
5617
  if ((flags & 7) !== 0) return;
5279
5618
  let off = 0;
5280
5619
  for (const e of curSats) {
5281
5620
  if (off + 16 >= len) break;
5282
5621
  if (!e.prn) continue;
5283
- const master = readPrRate(i2 + 16, off);
5622
+ const master = readPrRate(i + 16, off);
5284
5623
  off += master.size;
5285
5624
  if (master.value === -268435455) continue;
5286
5625
  const refPrRate = ref.sats[e.navsys][e.svid]?.prRate ?? 0;
@@ -5290,20 +5629,20 @@ function parseSbfMeas(data) {
5290
5629
  for (let k = 1; k < e.slots.length; k++) {
5291
5630
  const slot = e.slots[k];
5292
5631
  if (!slot) break;
5293
- const slave = readPrRate(i2 + 16, off);
5632
+ const slave = readPrRate(i + 16, off);
5294
5633
  off += slave.size;
5295
5634
  if (m)
5296
5635
  slot.doppler = (mD * (CLIGHT / m.freq) * 1e3 - slave.value) * 1e-3 / (CLIGHT / slot.freq);
5297
5636
  }
5298
5637
  }
5299
5638
  };
5300
- const decodeMeasEpoch = (i2, len) => {
5301
- const n1 = data[i2 + 14];
5302
- const len1 = data[i2 + 15];
5303
- const len2 = data[i2 + 16];
5304
- if ((data[i2 + 17] & 128) !== 0) return;
5305
- const end = i2 + len;
5306
- let p = i2 + 20;
5639
+ const decodeMeasEpoch = (i, len) => {
5640
+ const n1 = data[i + 14];
5641
+ const len1 = data[i + 15];
5642
+ const len2 = data[i + 16];
5643
+ if ((data[i + 17] & 128) !== 0) return;
5644
+ const end = i + len;
5645
+ let p = i + 20;
5307
5646
  for (let b1 = 0; b1 < n1 && p + 20 <= end; b1++) {
5308
5647
  const ant = data[p + 1] >> 5;
5309
5648
  let sig = data[p + 1] & 31;
@@ -5390,50 +5729,288 @@ function parseSbfMeas(data) {
5390
5729
  }
5391
5730
  }
5392
5731
  };
5393
- let i = 0;
5394
- while (i + 8 <= data.length) {
5395
- if (data[i] !== 36 || data[i + 1] !== 64) {
5396
- i++;
5397
- continue;
5398
- }
5399
- const len = view.getUint16(i + 6, true);
5400
- if (len < 8 || len % 4 !== 0) {
5401
- i++;
5402
- continue;
5403
- }
5404
- if (i + len > data.length) break;
5405
- if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
5406
- badCrc++;
5407
- i++;
5408
- continue;
5409
- }
5410
- const id = view.getUint16(i + 4, true) & 8191;
5732
+ const badCrc = scanSbfFrames(data, view, (id, i, len) => {
5411
5733
  const key = String(id);
5412
5734
  messageCounts[key] = (messageCounts[key] ?? 0) + 1;
5413
5735
  if ((id === 4027 || id === 4109 || id === 4110 || id === 4111) && len >= 16) {
5414
5736
  const tow = view.getUint32(i + 8, true);
5415
5737
  const wnc = view.getUint16(i + 12, true);
5416
5738
  if (tow !== 4294967295 && wnc !== 65535) {
5417
- ensureEpoch(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK3 + tow);
5739
+ ensureEpoch(GPS_EPOCH_MS3 + wnc * MS_PER_WEEK4 + tow);
5418
5740
  if (id === 4109) decodeMeas3Ranges(i, tow);
5419
5741
  else if (id === 4110) decodeMeas3CN0(i, len);
5420
5742
  else if (id === 4111) decodeMeas3Doppler(i, len);
5421
5743
  else decodeMeasEpoch(i, len);
5422
5744
  }
5423
5745
  }
5424
- i += len;
5425
- }
5746
+ });
5426
5747
  flush();
5427
5748
  return { epochs, messageCounts, obsCodes, badCrc };
5428
5749
  }
5429
5750
 
5751
+ // src/novatel/frame.ts
5752
+ var OEM4_HLEN = 28;
5753
+ function crc32(data, start, len) {
5754
+ let crc = 0;
5755
+ for (let i = start; i < start + len; i++) {
5756
+ crc ^= data[i];
5757
+ for (let j = 0; j < 8; j++) {
5758
+ crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
5759
+ }
5760
+ }
5761
+ return crc >>> 0;
5762
+ }
5763
+ function* oem4Frames(data, view, stats) {
5764
+ let i = 0;
5765
+ while (i + OEM4_HLEN + 4 <= data.length) {
5766
+ if (data[i] !== 170 || data[i + 1] !== 68 || data[i + 2] !== 18) {
5767
+ i++;
5768
+ continue;
5769
+ }
5770
+ const hlen = data[i + 3];
5771
+ const msgLen = view.getUint16(i + 8, true);
5772
+ const total = hlen + msgLen + 4;
5773
+ if (hlen < OEM4_HLEN || i + total > data.length) {
5774
+ i++;
5775
+ continue;
5776
+ }
5777
+ if (crc32(data, i, hlen + msgLen) !== view.getUint32(i + hlen + msgLen, true)) {
5778
+ stats.badCrc++;
5779
+ i++;
5780
+ continue;
5781
+ }
5782
+ yield {
5783
+ id: view.getUint16(i + 4, true),
5784
+ start: i,
5785
+ payload: i + hlen,
5786
+ msgLen,
5787
+ binary: (data[i + 6] >> 4 & 3) === 0,
5788
+ week: view.getUint16(i + 14, true),
5789
+ towMs: view.getUint32(i + 16, true)
5790
+ };
5791
+ i += total;
5792
+ }
5793
+ }
5794
+
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
+ // src/novatel/nav.ts
5926
+ var ID_RAWEPHEM = 41;
5927
+ var ID_GLOEPHEMERIS = 723;
5928
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5929
+ var SEC_PER_WEEK3 = 7 * 86400;
5930
+ var SEC_PER_DAY = 86400;
5931
+ function decodeRawEphem(data, view, p) {
5932
+ const prn = view.getUint32(p, true);
5933
+ if (prn < 1 || prn > 32) return null;
5934
+ const refWeek = view.getUint32(p + 4, true);
5935
+ return decodeGpsLnavFrame(data.subarray(p + 12, p + 102), {
5936
+ prn: `G${String(prn).padStart(2, "0")}`,
5937
+ refWeek
5938
+ });
5939
+ }
5940
+ function decodeGloEphemeris(view, p) {
5941
+ const slot = view.getUint16(p, true) - 37;
5942
+ if (slot < 1 || slot > 27) return null;
5943
+ const freqNum = view.getUint16(p + 2, true) - 7;
5944
+ const week = view.getUint16(p + 6, true);
5945
+ const tow = Math.floor(view.getUint32(p + 8, true) / 1e3 + 0.5);
5946
+ const toff = view.getUint32(p + 12, true);
5947
+ const health = view.getUint32(p + 24, true) < 4 ? 0 : 1;
5948
+ let tof = view.getUint32(p + 124, true) - toff;
5949
+ tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
5950
+ if (tof < tow - 43200) tof += SEC_PER_DAY;
5951
+ else if (tof > tow + 43200) tof -= SEC_PER_DAY;
5952
+ const toeGpsMs = GPS_EPOCH_MS5 + (week * SEC_PER_WEEK3 + tow) * 1e3;
5953
+ const tofGpsMs = GPS_EPOCH_MS5 + (week * SEC_PER_WEEK3 + tof) * 1e3;
5954
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS5) / 1e3;
5955
+ return {
5956
+ system: "R",
5957
+ prn: `R${String(slot).padStart(2, "0")}`,
5958
+ tocDate: getUtcDate(new Date(toeGpsMs)),
5959
+ tauN: -view.getFloat64(p + 100, true),
5960
+ // RINEX stores −τ_n
5961
+ gammaN: view.getFloat64(p + 116, true),
5962
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK3 + SEC_PER_WEEK3) % SEC_PER_WEEK3,
5963
+ x: view.getFloat64(p + 28, true) / 1e3,
5964
+ y: view.getFloat64(p + 36, true) / 1e3,
5965
+ z: view.getFloat64(p + 44, true) / 1e3,
5966
+ xDot: view.getFloat64(p + 52, true) / 1e3,
5967
+ yDot: view.getFloat64(p + 60, true) / 1e3,
5968
+ zDot: view.getFloat64(p + 68, true) / 1e3,
5969
+ xAcc: view.getFloat64(p + 76, true) / 1e3,
5970
+ yAcc: view.getFloat64(p + 84, true) / 1e3,
5971
+ zAcc: view.getFloat64(p + 92, true) / 1e3,
5972
+ health,
5973
+ freqNum
5974
+ };
5975
+ }
5976
+ function parseNovatelNav(data) {
5977
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5978
+ const stats = { badCrc: 0 };
5979
+ const ephemerides = [];
5980
+ const lastGps = /* @__PURE__ */ new Map();
5981
+ const lastGlo = /* @__PURE__ */ new Map();
5982
+ for (const frame of oem4Frames(data, view, stats)) {
5983
+ if (!frame.binary) continue;
5984
+ if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
5985
+ 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;
5990
+ }
5991
+ lastGps.set(eph.prn, eph);
5992
+ ephemerides.push(eph);
5993
+ } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
5994
+ const eph = decodeGloEphemeris(view, frame.payload);
5995
+ if (!eph) continue;
5996
+ const prev = lastGlo.get(eph.prn);
5997
+ if (prev && Math.abs(prev.tocDate.getTime() - eph.tocDate.getTime()) < 1e3 && prev.health === eph.health) {
5998
+ continue;
5999
+ }
6000
+ lastGlo.set(eph.prn, eph);
6001
+ ephemerides.push(eph);
6002
+ }
6003
+ }
6004
+ return { ephemerides, badCrc: stats.badCrc };
6005
+ }
6006
+
5430
6007
  // src/novatel/index.ts
5431
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5432
- var MS_PER_WEEK4 = 7 * 864e5;
6008
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6009
+ var MS_PER_WEEK5 = 7 * 864e5;
5433
6010
  var HDR = 28;
5434
6011
  var ID_RANGE = 43;
5435
6012
  var ID_RANGECMP = 140;
5436
- var ID_GLOEPHEMERIS = 723;
6013
+ var ID_GLOEPHEMERIS2 = 723;
5437
6014
  var C_LIGHT2 = 299792458;
5438
6015
  var ADR_ROLL = 8388608;
5439
6016
  function carrierFreq2(sys, code, gloK) {
@@ -5461,16 +6038,6 @@ function carrierFreq2(sys, code, gloK) {
5461
6038
  function exsign(v, bits) {
5462
6039
  return v & 1 << bits - 1 ? v - 2 ** bits : v;
5463
6040
  }
5464
- function crc32(data, start, len) {
5465
- let crc = 0;
5466
- for (let i = start; i < start + len; i++) {
5467
- crc ^= data[i];
5468
- for (let j = 0; j < 8; j++) {
5469
- crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
5470
- }
5471
- }
5472
- return crc >>> 0;
5473
- }
5474
6041
  var SIGNALS2 = {
5475
6042
  0: {
5476
6043
  0: ["G", "1C"],
@@ -5524,7 +6091,7 @@ function parseNovatelRange(data) {
5524
6091
  const hl = data[j + 3];
5525
6092
  const id = view.getUint16(j + 4, true);
5526
6093
  const ln = view.getUint16(j + 8, true);
5527
- if (id !== ID_GLOEPHEMERIS || hl < HDR || j + hl + ln + 4 > data.length)
6094
+ if (id !== ID_GLOEPHEMERIS2 || hl < HDR || j + hl + ln + 4 > data.length)
5528
6095
  continue;
5529
6096
  if (crc32(data, j, hl + ln) !== view.getUint32(j + hl + ln, true)) continue;
5530
6097
  const slot = view.getUint16(j + hl, true) - 37;
@@ -5608,7 +6175,7 @@ function parseNovatelRange(data) {
5608
6175
  if (!codes.includes(code)) codes.push(code);
5609
6176
  }
5610
6177
  epochs.push({
5611
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK4 + towMs,
6178
+ timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
5612
6179
  meas
5613
6180
  });
5614
6181
  }
@@ -5660,7 +6227,7 @@ function parseNovatelRange(data) {
5660
6227
  if (!codes.includes(code)) codes.push(code);
5661
6228
  }
5662
6229
  epochs.push({
5663
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK4 + towMs,
6230
+ timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + towMs,
5664
6231
  meas
5665
6232
  });
5666
6233
  }
@@ -6019,10 +6586,10 @@ function selectBest(ephs, timeMs) {
6019
6586
  }
6020
6587
  return best;
6021
6588
  }
6022
- var GPS_EPOCH_MS4 = START_GPS_TIME.getTime();
6023
- var MS_PER_WEEK5 = 7 * 864e5;
6589
+ var GPS_EPOCH_MS7 = START_GPS_TIME.getTime();
6590
+ var MS_PER_WEEK6 = 7 * 864e5;
6024
6591
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
6025
- var GAL_EPOCH_MS = GPS_EPOCH_MS4 + 1024 * MS_PER_WEEK5;
6592
+ var GAL_EPOCH_MS = GPS_EPOCH_MS7 + 1024 * MS_PER_WEEK6;
6026
6593
  function ephInfoToEphemeris(info) {
6027
6594
  const sys = info.prn.charAt(0);
6028
6595
  if (sys === "R") {
@@ -6073,11 +6640,11 @@ function ephInfoToEphemeris(info) {
6073
6640
  } else if (sys === "E") {
6074
6641
  epochMs = GAL_EPOCH_MS;
6075
6642
  } else {
6076
- epochMs = GPS_EPOCH_MS4;
6077
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS4) / MS_PER_WEEK5;
6643
+ epochMs = GPS_EPOCH_MS7;
6644
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS7) / MS_PER_WEEK6;
6078
6645
  week += 1024 * Math.round((weeksNow - week) / 1024);
6079
6646
  }
6080
- const tocDate = new Date(epochMs + week * MS_PER_WEEK5 + tocSec * 1e3);
6647
+ const tocDate = new Date(epochMs + week * MS_PER_WEEK6 + tocSec * 1e3);
6081
6648
  return {
6082
6649
  system: sys,
6083
6650
  prn: info.prn,
@@ -9030,6 +9597,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9030
9597
  MILLISECONDS_IN_WEEK,
9031
9598
  MILLISECONDS_TT_TAI,
9032
9599
  MultipathAccumulator,
9600
+ OEM4_HLEN,
9033
9601
  OMEGA_E,
9034
9602
  REFERENCE_FRAMES,
9035
9603
  RINEX_CODES,
@@ -9073,6 +9641,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9073
9641
  computeVisibility,
9074
9642
  computeVisibilityFromPositions,
9075
9643
  connectToMountpoint,
9644
+ crc32,
9076
9645
  createGzipLineSink,
9077
9646
  createStationMeta,
9078
9647
  createStreamStats,
@@ -9162,6 +9731,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9162
9731
  msmEpochToDate,
9163
9732
  navTimesFromEph,
9164
9733
  nmeaCoordToDecimal,
9734
+ oem4Frames,
9165
9735
  padL,
9166
9736
  padR,
9167
9737
  parseAntex,
@@ -9169,9 +9739,12 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
9169
9739
  parseIonex,
9170
9740
  parseNavFile,
9171
9741
  parseNmeaFile,
9742
+ parseNovatelNav,
9172
9743
  parseNovatelRange,
9173
9744
  parseRinexStream,
9745
+ parseSbfAlmanac,
9174
9746
  parseSbfMeas,
9747
+ parseSbfNav,
9175
9748
  parseSinexBiasDcb,
9176
9749
  parseSourcetable,
9177
9750
  parseSp3,