gnss-js 1.21.0 → 1.22.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
@@ -241,6 +241,7 @@ __export(src_exports, {
241
241
  parseUbxCnav: () => parseUbxCnav,
242
242
  parseUbxIonoUtc: () => parseUbxIonoUtc,
243
243
  parseUbxNav: () => parseUbxNav,
244
+ parseUbxRawNav: () => parseUbxRawNav,
244
245
  parseUbxRawx: () => parseUbxRawx,
245
246
  phiAltBOC: () => phiAltBOC,
246
247
  phiBOCc: () => phiBOCc,
@@ -5404,1345 +5405,1445 @@ function parseUbxCnav(data) {
5404
5405
  return { ephemerides, badCrc, messages };
5405
5406
  }
5406
5407
 
5407
- // src/ubx/index.ts
5408
- var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5409
- var MS_PER_WEEK2 = 7 * 864e5;
5410
- var SIGNALS = {
5411
- 0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
5412
- // GPS L1C/A, L2CL, L2CM
5413
- 1: { 0: ["S", "1C"] },
5414
- // SBAS L1C/A
5415
- 2: { 0: ["E", "1X"], 1: ["E", "1X"], 5: ["E", "7X"], 6: ["E", "7X"] },
5416
- // E1C/B, E5bI/Q
5417
- 3: { 0: ["C", "2I"], 1: ["C", "2I"], 2: ["C", "7I"], 3: ["C", "7I"] },
5418
- // B1I D1/D2, B2I D1/D2
5419
- 5: { 0: ["J", "1C"], 4: ["J", "2X"], 5: ["J", "2X"] },
5420
- // QZSS L1C/A, L2CM/CL
5421
- 6: { 0: ["R", "1C"], 2: ["R", "2C"] }
5422
- // GLONASS L1OF, L2OF
5423
- };
5424
- function prnFor(gnssId, svId) {
5425
- const two4 = (n) => String(n).padStart(2, "0");
5426
- switch (gnssId) {
5427
- case 0:
5428
- return svId >= 1 && svId <= 32 ? `G${two4(svId)}` : null;
5429
- case 1:
5430
- return svId >= 120 && svId <= 158 ? `S${two4(svId - 100)}` : null;
5431
- case 2:
5432
- return svId >= 1 && svId <= 36 ? `E${two4(svId)}` : null;
5433
- case 3:
5434
- return svId >= 1 && svId <= 63 ? `C${two4(svId)}` : null;
5435
- case 5:
5436
- return svId >= 1 && svId <= 10 ? `J${two4(svId)}` : null;
5437
- case 6:
5438
- return svId >= 1 && svId <= 32 ? `R${two4(svId)}` : null;
5439
- default:
5440
- return null;
5441
- }
5408
+ // src/navbits/bds.ts
5409
+ var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
5410
+ var SEC_PER_WEEK4 = 7 * 86400;
5411
+ var MS_PER_WEEK2 = SEC_PER_WEEK4 * 1e3;
5412
+ var HALF_WEEK2 = 302400;
5413
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
5414
+ var BDS_SUBFRAME_BYTES = 38;
5415
+ function getBitU2(b, p1, l1, p2, l2) {
5416
+ return getBitU(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
5442
5417
  }
5443
- function parseUbxRawx(data) {
5444
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5445
- const epochs = [];
5446
- const messageCounts = {};
5447
- const obsCodes = {};
5448
- const stats = { badChecksums: 0 };
5449
- for (const frame of ubxFrames(data, stats)) {
5450
- const { msgClass: cls, msgId: id } = frame;
5451
- const len = frame.payload.length;
5452
- const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
5453
- messageCounts[key] = (messageCounts[key] ?? 0) + 1;
5454
- if (cls === 2 && id === 21 && len >= 16) {
5455
- const p = frame.payloadStart;
5456
- const rcvTow = view.getFloat64(p, true);
5457
- const week = view.getUint16(p + 8, true);
5458
- const leapS = view.getInt8(p + 10);
5459
- const numMeas = data[p + 11];
5460
- const recStat = data[p + 12];
5461
- const leapValid = (recStat & 1) !== 0;
5462
- const meas = [];
5463
- for (let k = 0; k < numMeas; k++) {
5464
- const off = p + 16 + 32 * k;
5465
- if (off + 32 > p + len) break;
5466
- const gnssId = data[off + 20];
5467
- const svId = data[off + 21];
5468
- const sigId = data[off + 22];
5469
- const trkStat = data[off + 30];
5470
- const prn = prnFor(gnssId, svId);
5471
- const sig = SIGNALS[gnssId]?.[sigId];
5472
- if (!prn || !sig) continue;
5473
- const prValid = (trkStat & 1) !== 0;
5474
- const cpValid = (trkStat & 2) !== 0;
5475
- const cp = view.getFloat64(off + 8, true);
5476
- meas.push({
5477
- prn,
5478
- code: sig[1],
5479
- pr: prValid ? view.getFloat64(off, true) : null,
5480
- cp: cpValid && cp !== 0 ? cp : null,
5481
- doppler: view.getFloat32(off + 16, true),
5482
- cn0: data[off + 26],
5483
- halfCycleAmbiguous: (trkStat & 4) !== 0,
5484
- lockTimeMs: view.getUint16(off + 24, true)
5485
- });
5486
- const sys = sig[0];
5487
- const codes = obsCodes[sys] ??= [];
5488
- if (!codes.includes(sig[1])) codes.push(sig[1]);
5489
- }
5490
- epochs.push({
5491
- timeMs: GPS_EPOCH_MS4 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
5492
- leapS: leapValid ? leapS : null,
5493
- meas
5494
- });
5495
- }
5496
- }
5497
- return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
5418
+ function getBitS2(b, p1, l1, p2, l2) {
5419
+ return getBitS(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
5498
5420
  }
5499
-
5500
- // src/sbf/frame.ts
5501
- var CRC_TABLE = (() => {
5502
- const t = new Uint16Array(256);
5503
- for (let n = 0; n < 256; n++) {
5504
- let c = n << 8;
5505
- for (let k = 0; k < 8; k++)
5506
- c = c & 32768 ? (c << 1 ^ 4129) & 65535 : c << 1 & 65535;
5507
- t[n] = c;
5508
- }
5509
- return t;
5510
- })();
5511
- function crc16(data, start, end) {
5512
- let crc = 0;
5513
- for (let i = start; i < end; i++)
5514
- crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
5515
- return crc;
5421
+ function getBitU3(b, p1, l1, p2, l2, p3, l3) {
5422
+ return getBitU(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
5516
5423
  }
5517
- function scanSbfFrames(data, view, onBlock) {
5518
- let badCrc = 0;
5519
- let i = 0;
5520
- while (i + 8 <= data.length) {
5521
- if (data[i] !== 36 || data[i + 1] !== 64) {
5522
- i++;
5523
- continue;
5524
- }
5525
- const len = view.getUint16(i + 6, true);
5526
- if (len < 8 || len % 4 !== 0) {
5527
- i++;
5528
- continue;
5529
- }
5530
- if (i + len > data.length) break;
5531
- if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
5532
- badCrc++;
5533
- i++;
5534
- continue;
5535
- }
5536
- onBlock(view.getUint16(i + 4, true) & 8191, i, len);
5537
- i += len;
5538
- }
5539
- return badCrc;
5424
+ function getBitS3(b, p1, l1, p2, l2, p3, l3) {
5425
+ return getBitS(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
5540
5426
  }
5541
- var two = (n) => String(n).padStart(2, "0");
5542
- function svidToPrn(svid) {
5543
- if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
5544
- if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
5545
- if (svid <= 62) return null;
5546
- if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
5547
- if (svid <= 70) return null;
5548
- if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
5549
- if (svid <= 119) return null;
5550
- if (svid <= 140) return `S${two(svid - 100)}`;
5551
- if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
5552
- if (svid <= 190) return `J${two(svid - 180)}`;
5553
- if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
5554
- if (svid <= 215) return `S${two(svid - 157)}`;
5555
- if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
5556
- if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
5557
- return null;
5427
+ var mergeS = (hi, lo, n) => hi * 2 ** n + lo;
5428
+ function bchOk(cw) {
5429
+ for (let i = 14; i >= 4; i--) {
5430
+ if (cw & 1 << i) cw ^= 19 << i - 4;
5431
+ }
5432
+ return cw === 0;
5558
5433
  }
5559
-
5560
- // src/sbf/nav.ts
5561
- var PI2 = Math.PI;
5562
- var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5563
- var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
5564
- var SEC_PER_WEEK4 = 7 * 86400;
5565
- var MS_PER_WEEK3 = SEC_PER_WEEK4 * 1e3;
5566
- var F4_DNU = -2e10;
5567
- var U4_DNU = 4294967295;
5568
- function adjustWeek(refWeek, wn, mod) {
5569
- let offset = refWeek % mod - wn;
5570
- if (offset > mod / 2) offset -= mod;
5571
- if (offset < -(mod / 2 - 1)) offset += mod;
5572
- return refWeek - offset;
5434
+ function bdsSubframeParityOk(subframe) {
5435
+ if (subframe.length < BDS_SUBFRAME_BYTES) return false;
5436
+ if (!bchOk(getBitU(subframe, 15, 15))) return false;
5437
+ for (let w = 1; w < 10; w++) {
5438
+ const base = 30 * w;
5439
+ const cw1 = getBitU(subframe, base, 11) * 16 + getBitU(subframe, base + 22, 4);
5440
+ const cw2 = getBitU(subframe, base + 11, 11) * 16 + getBitU(subframe, base + 26, 4);
5441
+ if (!bchOk(cw1) || !bchOk(cw2)) return false;
5442
+ }
5443
+ return true;
5573
5444
  }
5574
- var gpsMs = (week, sec) => GPS_EPOCH_MS5 + week * MS_PER_WEEK3 + sec * 1e3;
5575
- var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
5576
- function decodeGpsQzsNav(view, b, sys) {
5577
- const svid = view.getUint8(b + 14);
5578
- const prn = sys === "G" ? svid : svid - 180;
5579
- const max = sys === "G" ? 32 : 10;
5580
- if (prn < 1 || prn > max) return null;
5581
- const wnc = view.getUint16(b + 12, true);
5582
- const tgdRaw = view.getFloat32(b + 28, true);
5583
- const tocs = view.getUint32(b + 32, true);
5584
- const toes = view.getUint32(b + 88, true);
5585
- const wnToc = adjustWeek(wnc, view.getUint16(b + 136, true), 1024);
5586
- const tocDate = new Date(gpsMs(wnToc, tocs));
5445
+ function buildBdsEphemeris(prn, f) {
5446
+ let week = f.week;
5447
+ if (f.toes < f.sow - HALF_WEEK2) week++;
5448
+ else if (f.toes > f.sow + HALF_WEEK2) week--;
5449
+ const tocDate = new Date(BDT_EPOCH_MS + week * MS_PER_WEEK2 + f.tocSec * 1e3);
5587
5450
  return {
5588
- system: sys,
5589
- prn: `${sys}${String(prn).padStart(2, "0")}`,
5451
+ system: "C",
5452
+ prn,
5590
5453
  toc: sowOf(tocDate.getTime()),
5591
5454
  tocDate,
5592
- af0: view.getFloat32(b + 44, true),
5593
- af1: view.getFloat32(b + 40, true),
5594
- af2: view.getFloat32(b + 36, true),
5595
- iode: view.getUint8(b + 24),
5596
- crs: view.getFloat32(b + 48, true),
5597
- deltaN: view.getFloat32(b + 52, true) * PI2,
5598
- m0: view.getFloat64(b + 56, true) * PI2,
5599
- cuc: view.getFloat32(b + 64, true),
5600
- e: view.getFloat64(b + 68, true),
5601
- cus: view.getFloat32(b + 76, true),
5602
- sqrtA: view.getFloat64(b + 80, true),
5603
- toe: toes,
5604
- cic: view.getFloat32(b + 92, true),
5605
- omega0: view.getFloat64(b + 96, true) * PI2,
5606
- cis: view.getFloat32(b + 104, true),
5607
- i0: view.getFloat64(b + 108, true) * PI2,
5608
- crc: view.getFloat32(b + 116, true),
5609
- omega: view.getFloat64(b + 120, true) * PI2,
5610
- omegaDot: view.getFloat32(b + 128, true) * PI2,
5611
- idot: view.getFloat32(b + 132, true) * PI2,
5612
- // RINEX "GPS week to go with toe": RTKLIB writes WNc for GPS and
5613
- // the week of toc for QZSS — mirrored here so records compare
5614
- // 1:1 against convbin output. wnToe is still used for `toe` above.
5615
- week: sys === "G" ? wnc : wnToc,
5616
- svHealth: view.getUint8(b + 20),
5617
- tgd: tgdRaw !== F4_DNU ? tgdRaw : 0
5455
+ af0: f.af0,
5456
+ af1: f.af1,
5457
+ af2: f.af2,
5458
+ // AODE is not in the ephemeris subframes; RTKLIB derives the RINEX
5459
+ // IODE/AODE slot from toc per the BDS ICD update schedule.
5460
+ iode: Math.floor(f.tocSec / 720) % 240,
5461
+ crs: f.crs,
5462
+ deltaN: f.deltaN,
5463
+ m0: f.m0,
5464
+ cuc: f.cuc,
5465
+ e: f.e,
5466
+ cus: f.cus,
5467
+ sqrtA: f.sqrtA,
5468
+ toe: f.toes,
5469
+ cic: f.cic,
5470
+ omega0: f.omega0,
5471
+ cis: f.cis,
5472
+ i0: f.i0,
5473
+ crc: f.crc,
5474
+ omega: f.omega,
5475
+ omegaDot: f.omegaDot,
5476
+ idot: f.idot,
5477
+ week,
5478
+ // RINEX BDS week field is the BDT week of toe
5479
+ svHealth: f.svh,
5480
+ // SatH1
5481
+ tgd: f.tgd1
5482
+ // TGD1 (B1) — RINEX slot
5618
5483
  };
5619
5484
  }
5620
- function decodeGalNav(view, b) {
5621
- const prn = view.getUint8(b + 14) - 70;
5622
- if (prn < 1 || prn > 36) return null;
5623
- const source = view.getUint8(b + 15);
5624
- if (source !== 2 && source !== 16) return null;
5625
- const wnc = view.getUint16(b + 12, true);
5626
- const toes = view.getUint32(b + 100, true);
5627
- const tocs = view.getUint32(b + 104, true);
5628
- const wnToc = adjustWeek(wnc, view.getUint16(b + 126, true), 4096);
5629
- const tocDate = new Date(gpsMs(wnToc, tocs));
5630
- const health = view.getUint16(b + 130, true);
5631
- let svh = 0;
5632
- if (health & 1) svh |= health >> 1 & 7;
5633
- if (health & 16) svh |= (health >> 5 & 7) << 6;
5634
- if (health & 256) svh |= (health >> 9 & 7) << 3;
5635
- const bgdE5a = view.getFloat32(b + 136, true);
5485
+ function decodeBdsD1Frame(subframes, opts = {}) {
5486
+ if (subframes.length < 3 * BDS_SUBFRAME_BYTES) return null;
5487
+ const b = subframes;
5488
+ let i = 8 * 38 * 0;
5489
+ const frn1 = getBitU(b, i + 15, 3);
5490
+ const sow1 = getBitU2(b, i + 18, 8, i + 30, 12);
5491
+ const svh = getBitU(b, i + 42, 1);
5492
+ const week = getBitU(b, i + 60, 13);
5493
+ const tocSec = getBitU2(b, i + 73, 9, i + 90, 8) * 8;
5494
+ const tgd1 = getBitS(b, i + 98, 10) * 0.1 * 1e-9;
5495
+ const af2 = getBitS(b, i + 214, 11) * 2 ** -66;
5496
+ const af0 = getBitS2(b, i + 225, 7, i + 240, 17) * 2 ** -33;
5497
+ const af1 = getBitS2(b, i + 257, 5, i + 270, 17) * 2 ** -50;
5498
+ i = 8 * 38 * 1;
5499
+ const frn2 = getBitU(b, i + 15, 3);
5500
+ const sow2 = getBitU2(b, i + 18, 8, i + 30, 12);
5501
+ const deltaN = getBitS2(b, i + 42, 10, i + 60, 6) * 2 ** -43 * GPS_PI;
5502
+ const cuc = getBitS2(b, i + 66, 16, i + 90, 2) * 2 ** -31;
5503
+ const m0 = getBitS2(b, i + 92, 20, i + 120, 12) * 2 ** -31 * GPS_PI;
5504
+ const e = getBitU2(b, i + 132, 10, i + 150, 22) * 2 ** -33;
5505
+ const cus = getBitS(b, i + 180, 18) * 2 ** -31;
5506
+ const crc = getBitS2(b, i + 198, 4, i + 210, 14) * 2 ** -6;
5507
+ const crs = getBitS2(b, i + 224, 8, i + 240, 10) * 2 ** -6;
5508
+ const sqrtA = getBitU2(b, i + 250, 12, i + 270, 20) * 2 ** -19;
5509
+ const toe1 = getBitU(b, i + 290, 2);
5510
+ i = 8 * 38 * 2;
5511
+ const frn3 = getBitU(b, i + 15, 3);
5512
+ const sow3 = getBitU2(b, i + 18, 8, i + 30, 12);
5513
+ const toe2 = getBitU2(b, i + 42, 10, i + 60, 5);
5514
+ const i0 = getBitS2(b, i + 65, 17, i + 90, 15) * 2 ** -31 * GPS_PI;
5515
+ const cic = getBitS2(b, i + 105, 7, i + 120, 11) * 2 ** -31;
5516
+ const omegaDot = getBitS2(b, i + 131, 11, i + 150, 13) * 2 ** -43 * GPS_PI;
5517
+ const cis = getBitS2(b, i + 163, 9, i + 180, 9) * 2 ** -31;
5518
+ const idot = getBitS2(b, i + 189, 13, i + 210, 1) * 2 ** -43 * GPS_PI;
5519
+ const omega0 = getBitS2(b, i + 211, 21, i + 240, 11) * 2 ** -31 * GPS_PI;
5520
+ const omega = getBitS2(b, i + 251, 11, i + 270, 21) * 2 ** -31 * GPS_PI;
5521
+ const toes = (toe1 * 2 ** 15 + toe2) * 8;
5522
+ if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3) return null;
5523
+ if (sow2 !== sow1 + 6 || sow3 !== sow2 + 6) return null;
5524
+ if (tocSec !== toes) return null;
5525
+ return buildBdsEphemeris(opts.prn ?? "C00", {
5526
+ week,
5527
+ sow: sow1,
5528
+ toes,
5529
+ tocSec,
5530
+ svh,
5531
+ tgd1,
5532
+ af0,
5533
+ af1,
5534
+ af2,
5535
+ crs,
5536
+ deltaN,
5537
+ m0,
5538
+ cuc,
5539
+ e,
5540
+ cus,
5541
+ sqrtA,
5542
+ cic,
5543
+ omega0,
5544
+ cis,
5545
+ i0,
5546
+ crc,
5547
+ omega,
5548
+ omegaDot,
5549
+ idot
5550
+ });
5551
+ }
5552
+ function decodeBdsD2Frame(pages, opts = {}) {
5553
+ if (pages.length < 10 * BDS_SUBFRAME_BYTES) return null;
5554
+ const b = pages;
5555
+ let i = 8 * 38 * 0;
5556
+ const pgn1 = getBitU(b, i + 42, 4);
5557
+ const sow1 = getBitU2(b, i + 18, 8, i + 30, 12);
5558
+ const svh = getBitU(b, i + 46, 1);
5559
+ const week = getBitU(b, i + 64, 13);
5560
+ const tocSec = getBitU2(b, i + 77, 5, i + 90, 12) * 8;
5561
+ const tgd1 = getBitS(b, i + 102, 10) * 0.1 * 1e-9;
5562
+ i = 8 * 38 * 2;
5563
+ const pgn3 = getBitU(b, i + 42, 4);
5564
+ const sow3 = getBitU2(b, i + 18, 8, i + 30, 12);
5565
+ const af0 = getBitS2(b, i + 100, 12, i + 120, 12) * 2 ** -33;
5566
+ const af1p3 = getBitS(b, i + 132, 4);
5567
+ i = 8 * 38 * 3;
5568
+ const pgn4 = getBitU(b, i + 42, 4);
5569
+ const sow4 = getBitU2(b, i + 18, 8, i + 30, 12);
5570
+ const af1p4 = getBitU2(b, i + 46, 6, i + 60, 12);
5571
+ const af2 = getBitS2(b, i + 72, 10, i + 90, 1) * 2 ** -66;
5572
+ const deltaN = getBitS(b, i + 96, 16) * 2 ** -43 * GPS_PI;
5573
+ const cucp4 = getBitS(b, i + 120, 14);
5574
+ i = 8 * 38 * 4;
5575
+ const pgn5 = getBitU(b, i + 42, 4);
5576
+ const sow5 = getBitU2(b, i + 18, 8, i + 30, 12);
5577
+ const cucp5 = getBitU(b, i + 46, 4);
5578
+ const m0 = getBitS3(b, i + 50, 2, i + 60, 22, i + 90, 8) * 2 ** -31 * GPS_PI;
5579
+ const cus = getBitS2(b, i + 98, 14, i + 120, 4) * 2 ** -31;
5580
+ const ep5 = getBitS(b, i + 124, 10);
5581
+ i = 8 * 38 * 5;
5582
+ const pgn6 = getBitU(b, i + 42, 4);
5583
+ const sow6 = getBitU2(b, i + 18, 8, i + 30, 12);
5584
+ const ep6 = getBitU2(b, i + 46, 6, i + 60, 16);
5585
+ const sqrtA = getBitU3(b, i + 76, 6, i + 90, 22, i + 120, 4) * 2 ** -19;
5586
+ const cicp6 = getBitS(b, i + 124, 10);
5587
+ i = 8 * 38 * 6;
5588
+ const pgn7 = getBitU(b, i + 42, 4);
5589
+ const sow7 = getBitU2(b, i + 18, 8, i + 30, 12);
5590
+ const cicp7 = getBitU2(b, i + 46, 6, i + 60, 2);
5591
+ const cis = getBitS(b, i + 62, 18) * 2 ** -31;
5592
+ const toes = getBitU2(b, i + 80, 2, i + 90, 15) * 8;
5593
+ const i0p7 = getBitS2(b, i + 105, 7, i + 120, 14);
5594
+ i = 8 * 38 * 7;
5595
+ const pgn8 = getBitU(b, i + 42, 4);
5596
+ const sow8 = getBitU2(b, i + 18, 8, i + 30, 12);
5597
+ const i0p8 = getBitU2(b, i + 46, 6, i + 60, 5);
5598
+ const crc = getBitS2(b, i + 65, 17, i + 90, 1) * 2 ** -6;
5599
+ const crs = getBitS(b, i + 91, 18) * 2 ** -6;
5600
+ const omegaDotP8 = getBitS2(b, i + 109, 3, i + 120, 16);
5601
+ i = 8 * 38 * 8;
5602
+ const pgn9 = getBitU(b, i + 42, 4);
5603
+ const sow9 = getBitU2(b, i + 18, 8, i + 30, 12);
5604
+ const omegaDotP9 = getBitU(b, i + 46, 5);
5605
+ const omega0 = getBitS3(b, i + 51, 1, i + 60, 22, i + 90, 9) * 2 ** -31 * GPS_PI;
5606
+ const omegaP9 = getBitS2(b, i + 99, 13, i + 120, 14);
5607
+ i = 8 * 38 * 9;
5608
+ const pgn10 = getBitU(b, i + 42, 4);
5609
+ const sow10 = getBitU2(b, i + 18, 8, i + 30, 12);
5610
+ const omegaP10 = getBitU(b, i + 46, 5);
5611
+ const idot = getBitS2(b, i + 51, 1, i + 60, 13) * 2 ** -43 * GPS_PI;
5612
+ if (pgn1 !== 1 || pgn3 !== 3 || pgn4 !== 4 || pgn5 !== 5 || pgn6 !== 6 || pgn7 !== 7 || pgn8 !== 8 || pgn9 !== 9 || pgn10 !== 10) {
5613
+ return null;
5614
+ }
5615
+ if (sow3 !== sow1 + 6 || sow4 !== sow3 + 3 || sow5 !== sow4 + 3 || sow6 !== sow5 + 3 || sow7 !== sow6 + 3 || sow8 !== sow7 + 3 || sow9 !== sow8 + 3 || sow10 !== sow9 + 3) {
5616
+ return null;
5617
+ }
5618
+ if (tocSec !== toes) return null;
5619
+ return buildBdsEphemeris(opts.prn ?? "C00", {
5620
+ week,
5621
+ sow: sow1,
5622
+ toes,
5623
+ tocSec,
5624
+ svh,
5625
+ tgd1,
5626
+ af0,
5627
+ af1: mergeS(af1p3, af1p4, 18) * 2 ** -50,
5628
+ af2,
5629
+ crs,
5630
+ deltaN,
5631
+ m0,
5632
+ cuc: mergeS(cucp4, cucp5, 4) * 2 ** -31,
5633
+ e: mergeS(ep5, ep6, 22) * 2 ** -33,
5634
+ cus,
5635
+ sqrtA,
5636
+ cic: mergeS(cicp6, cicp7, 8) * 2 ** -31,
5637
+ omega0,
5638
+ cis,
5639
+ i0: mergeS(i0p7, i0p8, 11) * 2 ** -31 * GPS_PI,
5640
+ crc,
5641
+ omega: mergeS(omegaP9, omegaP10, 5) * 2 ** -31 * GPS_PI,
5642
+ omegaDot: mergeS(omegaDotP8, omegaDotP9, 5) * 2 ** -43 * GPS_PI,
5643
+ idot
5644
+ });
5645
+ }
5646
+ function isBdsGeoPrn(prn) {
5647
+ return prn < 6 || prn > 58;
5648
+ }
5649
+ var BdsAssembler = class {
5650
+ sats = /* @__PURE__ */ new Map();
5651
+ /**
5652
+ * Push one 300-bit subframe (38+ bytes, bit 0 = first bit of the
5653
+ * preamble) for the satellite `prn` ("C06"). Returns the newly
5654
+ * completed ephemeris, or null.
5655
+ */
5656
+ push(prn, subframe) {
5657
+ if (subframe.length < BDS_SUBFRAME_BYTES) return null;
5658
+ const num = parseInt(prn.slice(1), 10);
5659
+ if (!Number.isFinite(num) || num < 1 || num > 63) return null;
5660
+ const id = getBitU(subframe, 15, 3);
5661
+ if (id < 1 || id > 5) return null;
5662
+ let sat = this.sats.get(prn);
5663
+ if (!sat) {
5664
+ sat = { buf: new Uint8Array(10 * BDS_SUBFRAME_BYTES) };
5665
+ this.sats.set(prn, sat);
5666
+ }
5667
+ let eph = null;
5668
+ if (!isBdsGeoPrn(num)) {
5669
+ sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (id - 1) * 38);
5670
+ if (id === 3) eph = decodeBdsD1Frame(sat.buf, { prn });
5671
+ } else {
5672
+ if (id !== 1) return null;
5673
+ const pgn = getBitU(subframe, 42, 4);
5674
+ if (pgn < 1 || pgn > 10) return null;
5675
+ sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (pgn - 1) * 38);
5676
+ if (pgn === 10) eph = decodeBdsD2Frame(sat.buf, { prn });
5677
+ }
5678
+ if (!eph) return null;
5679
+ const key = `${eph.week}:${eph.toe}`;
5680
+ if (key === sat.lastKey) return null;
5681
+ sat.lastKey = key;
5682
+ return eph;
5683
+ }
5684
+ };
5685
+
5686
+ // src/navbits/gal.ts
5687
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5688
+ var SEC_PER_WEEK5 = 7 * 86400;
5689
+ var HALF_WEEK3 = 302400;
5690
+ var GST_GPS_WEEK_OFFSET = 1024;
5691
+ function galInavPageCrcOk(page) {
5692
+ if (page.length < 28) return false;
5693
+ return crc24q(page, 196) === getBitU(page, 196, 24);
5694
+ }
5695
+ function galFnavPageCrcOk(page) {
5696
+ if (page.length < 30) return false;
5697
+ return crc24q(page, 214) === getBitU(page, 214, 24);
5698
+ }
5699
+ function gpsDate2(week, sec) {
5700
+ return new Date(GPS_EPOCH_MS4 + (week * SEC_PER_WEEK5 + sec) * 1e3);
5701
+ }
5702
+ function weekOfToe(week, tow, toe) {
5703
+ if (toe - tow > HALF_WEEK3) return week - 1;
5704
+ if (toe - tow < -HALF_WEEK3) return week + 1;
5705
+ return week;
5706
+ }
5707
+ var two = (n) => String(n).padStart(2, "0");
5708
+ function decodeGalInavWords(words) {
5709
+ if (words.length < 96) return null;
5710
+ const b = words;
5711
+ let i = 128;
5712
+ const type1 = getBitU(b, i, 6);
5713
+ i += 6;
5714
+ const iodNav1 = getBitU(b, i, 10);
5715
+ i += 10;
5716
+ const toes = getBitU(b, i, 14) * 60;
5717
+ i += 14;
5718
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5719
+ i += 32;
5720
+ const e = getBitU(b, i, 32) * 2 ** -33;
5721
+ i += 32;
5722
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
5723
+ i = 128 * 2;
5724
+ const type2 = getBitU(b, i, 6);
5725
+ i += 6;
5726
+ const iodNav2 = getBitU(b, i, 10);
5727
+ i += 10;
5728
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5729
+ i += 32;
5730
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5731
+ i += 32;
5732
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5733
+ i += 32;
5734
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
5735
+ i = 128 * 3;
5736
+ const type3 = getBitU(b, i, 6);
5737
+ i += 6;
5738
+ const iodNav3 = getBitU(b, i, 10);
5739
+ i += 10;
5740
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
5741
+ i += 24;
5742
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
5743
+ i += 16;
5744
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
5745
+ i += 16;
5746
+ const cus = getBitS(b, i, 16) * 2 ** -29;
5747
+ i += 16;
5748
+ const crc = getBitS(b, i, 16) * 2 ** -5;
5749
+ i += 16;
5750
+ const crs = getBitS(b, i, 16) * 2 ** -5;
5751
+ i = 128 * 4;
5752
+ const type4 = getBitU(b, i, 6);
5753
+ i += 6;
5754
+ const iodNav4 = getBitU(b, i, 10);
5755
+ i += 10;
5756
+ const svid = getBitU(b, i, 6);
5757
+ i += 6;
5758
+ const cic = getBitS(b, i, 16) * 2 ** -29;
5759
+ i += 16;
5760
+ const cis = getBitS(b, i, 16) * 2 ** -29;
5761
+ i += 16;
5762
+ const tocs = getBitU(b, i, 14) * 60;
5763
+ i += 14;
5764
+ const af0 = getBitS(b, i, 31) * 2 ** -34;
5765
+ i += 31;
5766
+ const af1 = getBitS(b, i, 21) * 2 ** -46;
5767
+ i += 21;
5768
+ const af2 = getBitS(b, i, 6) * 2 ** -59;
5769
+ i = 128 * 5;
5770
+ const type5 = getBitU(b, i, 6);
5771
+ i += 6 + 11 + 11 + 14 + 5;
5772
+ const bgdE5a = getBitS(b, i, 10) * 2 ** -32;
5773
+ i += 10;
5774
+ i += 10;
5775
+ const e5bHs = getBitU(b, i, 2);
5776
+ i += 2;
5777
+ const e1bHs = getBitU(b, i, 2);
5778
+ i += 2;
5779
+ const e5bDvs = getBitU(b, i, 1);
5780
+ i += 1;
5781
+ const e1bDvs = getBitU(b, i, 1);
5782
+ i += 1;
5783
+ const gstWeek = getBitU(b, i, 12);
5784
+ i += 12;
5785
+ const tow = getBitU(b, i, 20);
5786
+ if (type1 !== 1 || type2 !== 2 || type3 !== 3 || type4 !== 4 || type5 !== 5)
5787
+ return null;
5788
+ if (iodNav1 !== iodNav2 || iodNav1 !== iodNav3 || iodNav1 !== iodNav4)
5789
+ return null;
5790
+ if (svid < 1 || svid > 36) return null;
5791
+ const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
5792
+ const tocDate = gpsDate2(week, tocs);
5636
5793
  return {
5637
5794
  system: "E",
5638
- prn: `E${String(prn).padStart(2, "0")}`,
5639
- toc: sowOf(tocDate.getTime()),
5795
+ prn: `E${two(svid)}`,
5796
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
5640
5797
  tocDate,
5641
- af0: view.getFloat64(b + 116, true),
5642
- af1: view.getFloat32(b + 112, true),
5643
- af2: view.getFloat32(b + 108, true),
5644
- iode: view.getUint16(b + 128, true),
5645
- // IODnav
5646
- crs: view.getFloat32(b + 88, true),
5647
- deltaN: view.getFloat32(b + 72, true) * PI2,
5648
- m0: view.getFloat64(b + 24, true) * PI2,
5649
- cuc: view.getFloat32(b + 76, true),
5650
- e: view.getFloat64(b + 32, true),
5651
- cus: view.getFloat32(b + 80, true),
5652
- sqrtA: view.getFloat64(b + 16, true),
5798
+ af0,
5799
+ af1,
5800
+ af2,
5801
+ iode: iodNav1,
5802
+ crs,
5803
+ deltaN,
5804
+ m0,
5805
+ cuc,
5806
+ e,
5807
+ cus,
5808
+ sqrtA,
5653
5809
  toe: toes,
5654
- cic: view.getFloat32(b + 92, true),
5655
- omega0: view.getFloat64(b + 56, true) * PI2,
5656
- cis: view.getFloat32(b + 96, true),
5657
- i0: view.getFloat64(b + 40, true) * PI2,
5658
- crc: view.getFloat32(b + 84, true),
5659
- omega: view.getFloat64(b + 48, true) * PI2,
5660
- omegaDot: view.getFloat32(b + 64, true) * PI2,
5661
- idot: view.getFloat32(b + 68, true) * PI2,
5662
- week: wnc,
5663
- // RINEX GAL week is GPS-aligned and continuous
5664
- svHealth: svh,
5665
- tgd: bgdE5a !== F4_DNU ? bgdE5a : 0
5666
- // BGD E5a/E1 (RINEX slot)
5810
+ cic,
5811
+ omega0,
5812
+ cis,
5813
+ i0,
5814
+ crc,
5815
+ omega,
5816
+ omegaDot,
5817
+ idot,
5818
+ week,
5819
+ svHealth: e5bHs << 7 | e5bDvs << 6 | e1bHs << 1 | e1bDvs,
5820
+ tgd: bgdE5a
5667
5821
  };
5668
5822
  }
5669
- function decodeBdsNav(view, b) {
5670
- const prn = svidToPrn(view.getUint8(b + 14));
5671
- if (!prn || prn[0] !== "C") return null;
5672
- const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
5673
- const tgd1 = view.getFloat32(b + 24, true);
5674
- const tocs = view.getUint32(b + 32, true);
5675
- const toes = view.getUint32(b + 88, true);
5676
- const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
5677
- const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
5678
- const tocDate = new Date(BDT_EPOCH_MS + wnToc * MS_PER_WEEK3 + tocs * 1e3);
5823
+ function decodeGalFnavPages(pages) {
5824
+ if (pages.length < 124) return null;
5825
+ const b = pages;
5826
+ let i = 0;
5827
+ const type1 = getBitU(b, i, 6);
5828
+ i += 6;
5829
+ const svid = getBitU(b, i, 6);
5830
+ i += 6;
5831
+ const iodNav1 = getBitU(b, i, 10);
5832
+ i += 10;
5833
+ const tocs = getBitU(b, i, 14) * 60;
5834
+ i += 14;
5835
+ const af0 = getBitS(b, i, 31) * 2 ** -34;
5836
+ i += 31;
5837
+ const af1 = getBitS(b, i, 21) * 2 ** -46;
5838
+ i += 21;
5839
+ const af2 = getBitS(b, i, 6) * 2 ** -59;
5840
+ i += 6;
5841
+ i += 8 + 11 + 11 + 14 + 5;
5842
+ const bgdE5a = getBitS(b, i, 10) * 2 ** -32;
5843
+ i += 10;
5844
+ const e5aHs = getBitU(b, i, 2);
5845
+ i += 2;
5846
+ const gstWeek = getBitU(b, i, 12);
5847
+ i += 12;
5848
+ const tow = getBitU(b, i, 20);
5849
+ i += 20;
5850
+ const e5aDvs = getBitU(b, i, 1);
5851
+ i = 31 * 8;
5852
+ const type2 = getBitU(b, i, 6);
5853
+ i += 6;
5854
+ const iodNav2 = getBitU(b, i, 10);
5855
+ i += 10;
5856
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5857
+ i += 32;
5858
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
5859
+ i += 24;
5860
+ const e = getBitU(b, i, 32) * 2 ** -33;
5861
+ i += 32;
5862
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
5863
+ i += 32;
5864
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5865
+ i += 32;
5866
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
5867
+ i = 62 * 8;
5868
+ const type3 = getBitU(b, i, 6);
5869
+ i += 6;
5870
+ const iodNav3 = getBitU(b, i, 10);
5871
+ i += 10;
5872
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5873
+ i += 32;
5874
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5875
+ i += 32;
5876
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
5877
+ i += 16;
5878
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
5879
+ i += 16;
5880
+ const cus = getBitS(b, i, 16) * 2 ** -29;
5881
+ i += 16;
5882
+ const crc = getBitS(b, i, 16) * 2 ** -5;
5883
+ i += 16;
5884
+ const crs = getBitS(b, i, 16) * 2 ** -5;
5885
+ i += 16;
5886
+ const toes = getBitU(b, i, 14) * 60;
5887
+ i = 93 * 8;
5888
+ const type4 = getBitU(b, i, 6);
5889
+ i += 6;
5890
+ const iodNav4 = getBitU(b, i, 10);
5891
+ i += 10;
5892
+ const cic = getBitS(b, i, 16) * 2 ** -29;
5893
+ i += 16;
5894
+ const cis = getBitS(b, i, 16) * 2 ** -29;
5895
+ if (type1 !== 1 || type2 !== 2 || type3 !== 3 || type4 !== 4) return null;
5896
+ if (iodNav1 !== iodNav2 || iodNav1 !== iodNav3 || iodNav1 !== iodNav4)
5897
+ return null;
5898
+ if (svid < 1 || svid > 36) return null;
5899
+ const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
5900
+ const tocDate = gpsDate2(week, tocs);
5679
5901
  return {
5680
- system: "C",
5681
- prn,
5682
- toc: sowOf(tocDate.getTime()),
5902
+ system: "E",
5903
+ prn: `E${two(svid)}`,
5904
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
5683
5905
  tocDate,
5684
- af0: view.getFloat32(b + 44, true),
5685
- af1: view.getFloat32(b + 40, true),
5686
- af2: view.getFloat32(b + 36, true),
5687
- iode: view.getUint8(b + 21),
5688
- // AODE
5689
- crs: view.getFloat32(b + 48, true),
5690
- deltaN: view.getFloat32(b + 52, true) * PI2,
5691
- m0: view.getFloat64(b + 56, true) * PI2,
5692
- cuc: view.getFloat32(b + 64, true),
5693
- e: view.getFloat64(b + 68, true),
5694
- cus: view.getFloat32(b + 76, true),
5695
- sqrtA: view.getFloat64(b + 80, true),
5906
+ af0,
5907
+ af1,
5908
+ af2,
5909
+ iode: iodNav1,
5910
+ crs,
5911
+ deltaN,
5912
+ m0,
5913
+ cuc,
5914
+ e,
5915
+ cus,
5916
+ sqrtA,
5696
5917
  toe: toes,
5697
- cic: view.getFloat32(b + 92, true),
5698
- omega0: view.getFloat64(b + 96, true) * PI2,
5699
- cis: view.getFloat32(b + 104, true),
5700
- i0: view.getFloat64(b + 108, true) * PI2,
5701
- crc: view.getFloat32(b + 116, true),
5702
- omega: view.getFloat64(b + 120, true) * PI2,
5703
- omegaDot: view.getFloat32(b + 128, true) * PI2,
5704
- idot: view.getFloat32(b + 132, true) * PI2,
5705
- week: wnToe,
5706
- // RINEX BDS week field is the BDT week
5707
- svHealth: view.getUint8(b + 19),
5708
- // SatH1
5709
- tgd: tgd1 !== F4_DNU ? tgd1 : 0
5710
- // TGD1 B1/B3 (RINEX slot)
5918
+ cic,
5919
+ omega0,
5920
+ cis,
5921
+ i0,
5922
+ crc,
5923
+ omega,
5924
+ omegaDot,
5925
+ idot,
5926
+ week,
5927
+ svHealth: e5aHs << 4 | e5aDvs << 3,
5928
+ tgd: bgdE5a
5711
5929
  };
5712
5930
  }
5713
- function decodeGloNav(view, b) {
5714
- const prn = svidToPrn(view.getUint8(b + 14));
5715
- if (!prn || prn[0] !== "R") return null;
5716
- const wnc = view.getUint16(b + 12, true);
5717
- const toes = view.getUint32(b + 76, true);
5718
- const wnToe = adjustWeek(wnc, view.getUint16(b + 80, true), 1024);
5719
- const toeGpsMs = gpsMs(wnToe, toes);
5720
- const leapMs = getGpsLeap(new Date(toeGpsMs)) * 1e3;
5721
- const tocDate = new Date(toeGpsMs - leapMs);
5722
- const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
5723
- const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
5724
- const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS5) / 1e3 % SEC_PER_WEEK4;
5931
+ var dedupKey = (eph) => `${eph.iode}:${eph.toe}:${eph.tocDate.getTime()}`;
5932
+ var GalInavAssembler = class {
5933
+ sats = /* @__PURE__ */ new Map();
5934
+ /**
5935
+ * Push one I/NAV page pair for satellite `prn` (1-36): the 234-bit
5936
+ * even+odd concatenation, even part first with its 6 tail bits
5937
+ * removed, bit 0 = the even part's Even/Odd bit ( 17 bytes).
5938
+ * Returns the newly completed ephemeris, or null. Pages that are
5939
+ * not an even/odd nominal pair, alert pages and word types outside
5940
+ * 1-5 are ignored.
5941
+ */
5942
+ push(prn, page) {
5943
+ if (prn < 1 || prn > 36 || page.length < 17) return null;
5944
+ if (getBitU(page, 0, 1) !== 0 || getBitU(page, 114, 1) !== 1) return null;
5945
+ if (getBitU(page, 1, 1) === 1 || getBitU(page, 115, 1) === 1) return null;
5946
+ const type = getBitU(page, 2, 6);
5947
+ if (type < 1 || type > 5) return null;
5948
+ let sat = this.sats.get(prn);
5949
+ if (!sat) {
5950
+ sat = { words: new Uint8Array(96) };
5951
+ this.sats.set(prn, sat);
5952
+ }
5953
+ for (let k = 0; k < 14; k++)
5954
+ sat.words[type * 16 + k] = getBitU(page, 2 + 8 * k, 8);
5955
+ sat.words[type * 16 + 14] = getBitU(page, 116, 8);
5956
+ sat.words[type * 16 + 15] = getBitU(page, 124, 8);
5957
+ if (type !== 5) return null;
5958
+ const eph = decodeGalInavWords(sat.words);
5959
+ if (!eph || eph.prn !== `E${two(prn)}`) return null;
5960
+ const key = dedupKey(eph);
5961
+ if (key === sat.lastKey) return null;
5962
+ sat.lastKey = key;
5963
+ return eph;
5964
+ }
5965
+ };
5966
+ var GalFnavAssembler = class {
5967
+ sats = /* @__PURE__ */ new Map();
5968
+ /**
5969
+ * Push one F/NAV page for satellite `prn` (1-36): 244 bits with the
5970
+ * sync field stripped, bit 0 = first page-type bit (≥ 31 bytes).
5971
+ * Returns the newly completed ephemeris, or null. Dummy pages
5972
+ * (type 63) and page types outside 1-4 are ignored.
5973
+ */
5974
+ push(prn, page) {
5975
+ if (prn < 1 || prn > 36 || page.length < 31) return null;
5976
+ const type = getBitU(page, 0, 6);
5977
+ if (type < 1 || type > 4) return null;
5978
+ let sat = this.sats.get(prn);
5979
+ if (!sat) {
5980
+ sat = { words: new Uint8Array(124) };
5981
+ this.sats.set(prn, sat);
5982
+ }
5983
+ sat.words.set(page.subarray(0, 31), (type - 1) * 31);
5984
+ if (type !== 4) return null;
5985
+ const eph = decodeGalFnavPages(sat.words);
5986
+ if (!eph || eph.prn !== `E${two(prn)}`) return null;
5987
+ const key = dedupKey(eph);
5988
+ if (key === sat.lastKey) return null;
5989
+ sat.lastKey = key;
5990
+ return eph;
5991
+ }
5992
+ };
5993
+
5994
+ // src/navbits/glo.ts
5995
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5996
+ var SEC_PER_WEEK6 = 7 * 86400;
5997
+ var SEC_PER_DAY = 86400;
5998
+ var GLO_STRING_BYTES = 10;
5999
+ function getBitG(b, pos, len) {
6000
+ const value = getBitU(b, pos + 1, len - 1);
6001
+ return getBitU(b, pos, 1) && value !== 0 ? -value : value;
6002
+ }
6003
+ var HAMMING_MASKS = [
6004
+ [85, 85, 90, 170, 170, 170, 181, 85, 106, 216, 8],
6005
+ [102, 102, 108, 204, 204, 204, 217, 153, 179, 104, 16],
6006
+ [135, 135, 143, 15, 15, 15, 30, 30, 60, 112, 32],
6007
+ [7, 248, 15, 240, 15, 240, 31, 224, 63, 128, 64],
6008
+ [248, 0, 15, 255, 240, 0, 31, 255, 192, 0, 128],
6009
+ [0, 0, 15, 255, 255, 255, 224, 0, 0, 1, 0],
6010
+ [255, 255, 240, 0, 0, 0, 0, 0, 0, 2, 0],
6011
+ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248]
6012
+ ];
6013
+ function testGloString(buff) {
6014
+ if (buff.length < 11) return false;
6015
+ let n = 0;
6016
+ let cs = 0;
6017
+ for (const mask of HAMMING_MASKS) {
6018
+ cs = 0;
6019
+ for (let j = 0; j < 11; j++) {
6020
+ let x = buff[j] & mask[j];
6021
+ x ^= x >> 4;
6022
+ x ^= x >> 2;
6023
+ x ^= x >> 1;
6024
+ cs ^= x & 1;
6025
+ }
6026
+ if (cs) n++;
6027
+ }
6028
+ return n === 0 || n === 2 && cs === 1;
6029
+ }
6030
+ function decodeGloStrings(strings, refDate, opts = {}) {
6031
+ if (strings.length < 4 * GLO_STRING_BYTES) return null;
6032
+ const b = strings;
6033
+ let i = 1;
6034
+ const frn1 = getBitU(b, i, 4);
6035
+ i += 4 + 2;
6036
+ i += 2;
6037
+ const tkH = getBitU(b, i, 5);
6038
+ i += 5;
6039
+ const tkM = getBitU(b, i, 6);
6040
+ i += 6;
6041
+ const tkS = getBitU(b, i, 1) * 30;
6042
+ i += 1;
6043
+ const xDot = getBitG(b, i, 24) * 2 ** -20;
6044
+ i += 24;
6045
+ const xAcc = getBitG(b, i, 5) * 2 ** -30;
6046
+ i += 5;
6047
+ const x = getBitG(b, i, 27) * 2 ** -11;
6048
+ i = 80 + 1;
6049
+ const frn2 = getBitU(b, i, 4);
6050
+ i += 4;
6051
+ const bn = getBitU(b, i, 1);
6052
+ i += 1 + 2;
6053
+ i += 1;
6054
+ const tb = getBitU(b, i, 7);
6055
+ i += 7 + 5;
6056
+ const yDot = getBitG(b, i, 24) * 2 ** -20;
6057
+ i += 24;
6058
+ const yAcc = getBitG(b, i, 5) * 2 ** -30;
6059
+ i += 5;
6060
+ const y = getBitG(b, i, 27) * 2 ** -11;
6061
+ i = 160 + 1;
6062
+ const frn3 = getBitU(b, i, 4);
6063
+ i += 4;
6064
+ i += 1;
6065
+ const gammaN = getBitG(b, i, 11) * 2 ** -40;
6066
+ i += 11 + 1;
6067
+ i += 2;
6068
+ i += 1;
6069
+ const zDot = getBitG(b, i, 24) * 2 ** -20;
6070
+ i += 24;
6071
+ const zAcc = getBitG(b, i, 5) * 2 ** -30;
6072
+ i += 5;
6073
+ const z = getBitG(b, i, 27) * 2 ** -11;
6074
+ i = 240 + 1;
6075
+ const frn4 = getBitU(b, i, 4);
6076
+ i += 4;
6077
+ const tauN = getBitG(b, i, 22) * 2 ** -30;
6078
+ i += 22;
6079
+ i += 5;
6080
+ i += 5;
6081
+ i += 14;
6082
+ i += 1;
6083
+ i += 4;
6084
+ i += 3;
6085
+ i += 11;
6086
+ const slot = getBitU(b, i, 5);
6087
+ if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3 || frn4 !== 4) return null;
6088
+ if (slot < 1 || slot > 27) return null;
6089
+ const utcSec = (getUtcDate(refDate).getTime() - GPS_EPOCH_MS5) / 1e3;
6090
+ const week = Math.floor(utcSec / SEC_PER_WEEK6);
6091
+ let tow = utcSec - week * SEC_PER_WEEK6;
6092
+ const tod = tow % SEC_PER_DAY;
6093
+ tow -= tod;
6094
+ let tof = tkH * 3600 + tkM * 60 + tkS - 10800;
6095
+ if (tof < tod - 43200) tof += SEC_PER_DAY;
6096
+ else if (tof > tod + 43200) tof -= SEC_PER_DAY;
6097
+ let toe = tb * 900 - 10800;
6098
+ if (toe < tod - 43200) toe += SEC_PER_DAY;
6099
+ else if (toe > tod + 43200) toe -= SEC_PER_DAY;
5725
6100
  return {
5726
6101
  system: "R",
5727
- prn,
5728
- tocDate,
5729
- // RINEX stores −τn as the clock bias; SBF carries τn (ICD sign)
5730
- tauN: -view.getFloat32(b + 68, true),
5731
- gammaN: view.getFloat32(b + 64, true),
5732
- messageFrameTime,
5733
- x: view.getFloat64(b + 16, true),
5734
- // SBF unit is km, like RINEX
5735
- xDot: view.getFloat32(b + 40, true),
5736
- xAcc: view.getFloat32(b + 52, true),
5737
- y: view.getFloat64(b + 24, true),
5738
- yDot: view.getFloat32(b + 44, true),
5739
- yAcc: view.getFloat32(b + 56, true),
5740
- z: view.getFloat64(b + 32, true),
5741
- zDot: view.getFloat32(b + 48, true),
5742
- zAcc: view.getFloat32(b + 60, true),
6102
+ prn: `R${String(slot).padStart(2, "0")}`,
6103
+ // RINEX GLONASS epochs are UTC: build the UTC toe Date directly.
6104
+ tocDate: new Date(GPS_EPOCH_MS5 + (week * SEC_PER_WEEK6 + tow + toe) * 1e3),
6105
+ tauN: -tauN,
6106
+ // RINEX stores −τn; the SIS carries τn (ICD sign)
6107
+ gammaN,
6108
+ // v3 message frame time: seconds of the UTC week (RTKLIB tof).
6109
+ messageFrameTime: ((tow + tof) % SEC_PER_WEEK6 + SEC_PER_WEEK6) % SEC_PER_WEEK6,
6110
+ x,
6111
+ xDot,
6112
+ xAcc,
6113
+ y,
6114
+ yDot,
6115
+ yAcc,
6116
+ z,
6117
+ zDot,
6118
+ zAcc,
5743
6119
  // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
5744
- health: view.getUint8(b + 85) >> 2,
5745
- freqNum: view.getUint8(b + 15) - 8
6120
+ health: bn,
6121
+ freqNum: opts.freqNum ?? 0
5746
6122
  };
5747
6123
  }
5748
- function parseSbfNav(data) {
6124
+ var GloStringAssembler = class {
6125
+ sats = /* @__PURE__ */ new Map();
6126
+ /**
6127
+ * Push one navigation string (10+ bytes, bit 0 = string bit 85) for
6128
+ * the satellite `prn` ("R09"), received at the GPS-scale `time`.
6129
+ * Returns the newly completed ephemeris, or null. The decoded
6130
+ * string-4 slot number must match `prn`, or nothing is emitted.
6131
+ */
6132
+ push(prn, str, time, freqNum = 0) {
6133
+ if (str.length < GLO_STRING_BYTES) return null;
6134
+ const m = getBitU(str, 1, 4);
6135
+ if (m < 1) return null;
6136
+ const sec = Math.floor(time.getTime() / 1e3);
6137
+ let sat = this.sats.get(prn);
6138
+ if (!sat) {
6139
+ sat = { buf: new Uint8Array(4 * GLO_STRING_BYTES), batchSec: sec };
6140
+ this.sats.set(prn, sat);
6141
+ } else if (Math.abs(sec - sat.batchSec) > 30) {
6142
+ sat.buf.fill(0);
6143
+ sat.batchSec = sec;
6144
+ }
6145
+ if (m > 4) return null;
6146
+ sat.buf.set(str.subarray(0, GLO_STRING_BYTES), (m - 1) * 10);
6147
+ if (m !== 4) return null;
6148
+ const eph = decodeGloStrings(sat.buf, time, { freqNum });
6149
+ if (!eph || eph.prn !== prn) return null;
6150
+ const key = `${eph.tocDate.getTime()}:${eph.health}`;
6151
+ if (key === sat.lastKey) return null;
6152
+ sat.lastKey = key;
6153
+ return eph;
6154
+ }
6155
+ };
6156
+
6157
+ // src/ubx/rawnav.ts
6158
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6159
+ var SEC_PER_WEEK7 = 7 * 86400;
6160
+ var two2 = (n) => String(n).padStart(2, "0");
6161
+ function parseUbxRawNav(data, opts = {}) {
5749
6162
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5750
6163
  const ephemerides = [];
5751
- const badCrc = scanSbfFrames(data, view, (id, b, len) => {
5752
- let eph = null;
5753
- if (id === 5891 && len >= 140) eph = decodeGpsQzsNav(view, b, "G");
5754
- else if (id === 4095 && len >= 140) eph = decodeGpsQzsNav(view, b, "J");
5755
- else if (id === 4002 && len >= 149) eph = decodeGalNav(view, b);
5756
- else if (id === 4081 && len >= 140) eph = decodeBdsNav(view, b);
5757
- else if (id === 4004 && len >= 96) eph = decodeGloNav(view, b);
5758
- if (eph) ephemerides.push(eph);
5759
- });
5760
- return { ephemerides, badCrc };
5761
- }
5762
- var GAL_SQRT_A_NOMINAL = Math.sqrt(296e5);
5763
- var GAL_I_NOMINAL = 56 / 180 * PI2;
5764
- var I_REF_03 = 0.3 * PI2;
5765
- function decodeGpsGalAlm(view, b, sys) {
5766
- const wnc = view.getUint16(b + 12, true);
5767
- const toa = view.getUint32(b + 20, true);
5768
- if (toa === U4_DNU) return null;
5769
- let prn;
5770
- let weekAlm;
5771
- let i0;
5772
- let sqrtA = view.getFloat32(b + 32, true);
5773
- let health;
5774
- if (sys === "G") {
5775
- prn = view.getUint8(b + 14);
5776
- if (prn < 1 || prn > 32) return null;
5777
- weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 256);
5778
- i0 = I_REF_03 + view.getFloat32(b + 24, true) * PI2;
5779
- health = view.getUint8(b + 58);
5780
- } else {
5781
- prn = view.getUint8(b + 57) - 70;
5782
- if (prn < 1 || prn > 36) return null;
5783
- weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 4);
5784
- i0 = GAL_I_NOMINAL + view.getFloat32(b + 24, true) * PI2;
5785
- sqrtA += GAL_SQRT_A_NOMINAL;
5786
- health = view.getUint16(b + 58, true);
6164
+ const counts = { gal: 0, bds: 0, glo: 0 };
6165
+ let badParity = 0;
6166
+ const inav = new GalInavAssembler();
6167
+ const bds = new BdsAssembler();
6168
+ const glo = new GloStringAssembler();
6169
+ let refDate = null;
6170
+ for (const f of ubxFrames(data)) {
6171
+ if (f.msgClass !== 2) continue;
6172
+ if (f.msgId === 21 && f.payload.length >= 16) {
6173
+ const rcvTow = view.getFloat64(f.payloadStart, true);
6174
+ let week = view.getUint16(f.payloadStart + 8, true);
6175
+ if (week === 0 && opts.refWeek !== void 0) week = opts.refWeek;
6176
+ if (week > 0) {
6177
+ refDate = new Date(
6178
+ GPS_EPOCH_MS6 + (week * SEC_PER_WEEK7 + rcvTow) * 1e3
6179
+ );
6180
+ }
6181
+ continue;
6182
+ }
6183
+ if (f.msgId !== 19) continue;
6184
+ const p = f.payload;
6185
+ if (p.length < 8) continue;
6186
+ const gnssId = p[0];
6187
+ const svId = p[1];
6188
+ const sigId = p[2];
6189
+ const base = f.payloadStart + 8;
6190
+ if (gnssId === 2) {
6191
+ if (sigId === 3 || sigId === 8) continue;
6192
+ if (p.length < 8 + 32) continue;
6193
+ counts.gal++;
6194
+ if (svId < 1 || svId > 36) continue;
6195
+ const buff = new Uint8Array(32);
6196
+ for (let k = 0; k < 8; k++) {
6197
+ const w = view.getUint32(base + 4 * k, true);
6198
+ buff[4 * k] = w >>> 24;
6199
+ buff[4 * k + 1] = w >>> 16 & 255;
6200
+ buff[4 * k + 2] = w >>> 8 & 255;
6201
+ buff[4 * k + 3] = w & 255;
6202
+ }
6203
+ const page = new Uint8Array(30);
6204
+ for (let k = 0; k < 14; k++) page[k] = buff[k];
6205
+ setBitU(page, 112, 2, getBitU(buff, 112, 2));
6206
+ for (let k = 0; k < 15; k++) {
6207
+ setBitU(page, 114 + 8 * k, 8, getBitU(buff, 128 + 8 * k, 8));
6208
+ }
6209
+ if (getBitU(page, 0, 1) !== 0 || getBitU(page, 114, 1) !== 1) continue;
6210
+ if (getBitU(page, 1, 1) === 1 || getBitU(page, 115, 1) === 1) continue;
6211
+ if (!galInavPageCrcOk(page)) {
6212
+ badParity++;
6213
+ continue;
6214
+ }
6215
+ const eph = inav.push(svId, page);
6216
+ if (eph) ephemerides.push(eph);
6217
+ } else if (gnssId === 3) {
6218
+ if (sigId === 6 || sigId === 8) continue;
6219
+ if (p.length < 8 + 40) continue;
6220
+ counts.bds++;
6221
+ if (svId < 1 || svId > 63) continue;
6222
+ const sf = new Uint8Array(38);
6223
+ for (let k = 0; k < 10; k++) {
6224
+ const w = view.getUint32(base + 4 * k, true);
6225
+ setBitU(sf, 30 * k, 30, w & 1073741823);
6226
+ }
6227
+ if (!bdsSubframeParityOk(sf)) {
6228
+ badParity++;
6229
+ continue;
6230
+ }
6231
+ const eph = bds.push(`C${two2(svId)}`, sf);
6232
+ if (eph) ephemerides.push(eph);
6233
+ } else if (gnssId === 6) {
6234
+ if (p.length < 8 + 16) continue;
6235
+ counts.glo++;
6236
+ if (svId < 1 || svId > 32) continue;
6237
+ const str = new Uint8Array(16);
6238
+ for (let k = 0; k < 4; k++) {
6239
+ const w = view.getUint32(base + 4 * k, true);
6240
+ str[4 * k] = w >>> 24;
6241
+ str[4 * k + 1] = w >>> 16 & 255;
6242
+ str[4 * k + 2] = w >>> 8 & 255;
6243
+ str[4 * k + 3] = w & 255;
6244
+ }
6245
+ if (!testGloString(str)) {
6246
+ badParity++;
6247
+ continue;
6248
+ }
6249
+ if (!refDate) continue;
6250
+ const eph = glo.push(`R${two2(svId)}`, str, refDate, p[3] - 7);
6251
+ if (eph) ephemerides.push(eph);
6252
+ }
5787
6253
  }
5788
- return {
5789
- system: sys,
5790
- prn: `${sys}${String(prn).padStart(2, "0")}`,
5791
- weekAlm,
5792
- toaSec: toa,
5793
- sqrtA,
5794
- e: view.getFloat32(b + 16, true),
5795
- i0OrDeltaI: i0,
5796
- omega0: view.getFloat32(b + 36, true) * PI2,
5797
- omega: view.getFloat32(b + 40, true) * PI2,
5798
- m0: view.getFloat32(b + 44, true) * PI2,
5799
- omegaDot: view.getFloat32(b + 28, true) * PI2,
5800
- af0: view.getFloat32(b + 52, true),
5801
- af1: view.getFloat32(b + 48, true),
5802
- health
5803
- };
5804
- }
5805
- function decodeBdsAlm(view, b) {
5806
- const prnStr = svidToPrn(view.getUint8(b + 14));
5807
- if (!prnStr || prnStr[0] !== "C") return null;
5808
- const prn = parseInt(prnStr.slice(1), 10);
5809
- const toa = view.getUint32(b + 16, true);
5810
- if (toa === U4_DNU) return null;
5811
- const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
5812
- const geo = prn <= 5 || prn >= 59;
5813
- return {
5814
- system: "C",
5815
- prn: prnStr,
5816
- weekAlm: adjustWeek(bdsWeekRef, view.getUint8(b + 15), 256),
5817
- toaSec: toa,
5818
- sqrtA: view.getFloat32(b + 20, true),
5819
- e: view.getFloat32(b + 24, true),
5820
- i0OrDeltaI: (geo ? 0 : I_REF_03) + view.getFloat32(b + 44, true) * PI2,
5821
- omega0: view.getFloat32(b + 36, true) * PI2,
5822
- omega: view.getFloat32(b + 28, true) * PI2,
5823
- m0: view.getFloat32(b + 32, true) * PI2,
5824
- omegaDot: view.getFloat32(b + 40, true) * PI2,
5825
- af0: view.getFloat32(b + 48, true),
5826
- af1: view.getFloat32(b + 52, true),
5827
- health: view.getUint16(b + 56, true)
5828
- };
5829
- }
5830
- function decodeGloAlm(view, b) {
5831
- const prn = svidToPrn(view.getUint8(b + 14));
5832
- if (!prn || prn[0] !== "R") return null;
5833
- const wnc = view.getUint16(b + 12, true);
5834
- const toa = view.getUint32(b + 20, true);
5835
- if (toa === U4_DNU) return null;
5836
- return {
5837
- system: "R",
5838
- prn,
5839
- freqNr: view.getUint8(b + 15) - 8,
5840
- weekAlm: adjustWeek(wnc, view.getUint8(b + 52), 256),
5841
- toaSec: toa,
5842
- epsilon: view.getFloat32(b + 16, true),
5843
- deltaI: view.getFloat32(b + 24, true) * PI2,
5844
- lambda: view.getFloat32(b + 28, true) * PI2,
5845
- tLambda: view.getFloat32(b + 32, true),
5846
- omega: view.getFloat32(b + 36, true) * PI2,
5847
- deltaT: view.getFloat32(b + 40, true),
5848
- deltaTDot: view.getFloat32(b + 44, true),
5849
- tau: view.getFloat32(b + 48, true),
5850
- health: view.getUint8(b + 53),
5851
- nDay: view.getUint16(b + 54, true),
5852
- n4: view.getUint8(b + 57)
5853
- };
5854
- }
5855
- function parseSbfAlmanac(data) {
5856
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5857
- const almanacs = [];
5858
- const badCrc = scanSbfFrames(data, view, (id, b, len) => {
5859
- let alm = null;
5860
- if (id === 5892 && len >= 60) alm = decodeGpsGalAlm(view, b, "G");
5861
- else if (id === 4003 && len >= 61) alm = decodeGpsGalAlm(view, b, "E");
5862
- else if (id === 4119 && len >= 60) alm = decodeBdsAlm(view, b);
5863
- else if (id === 4005 && len >= 60) alm = decodeGloAlm(view, b);
5864
- if (alm) almanacs.push(alm);
5865
- });
5866
- return { almanacs, badCrc };
6254
+ return { ephemerides, counts, badParity };
5867
6255
  }
5868
6256
 
5869
- // src/sbf/iono.ts
5870
- var F4_DNU2 = -2e10;
5871
- function parseSbfIonoUtc(data) {
5872
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5873
- const ionoCorrections = {};
5874
- let leapSeconds = null;
5875
- const f4s = (b, n) => {
5876
- const out = [];
5877
- for (let k = 0; k < n; k++) {
5878
- const v = view.getFloat32(b + 4 * k, true);
5879
- if (v === F4_DNU2) return null;
5880
- out.push(v);
5881
- }
5882
- return out;
5883
- };
5884
- scanSbfFrames(data, view, (id, b, len) => {
5885
- if ((id === 5893 || id === 4120) && len >= 48) {
5886
- const alpha = f4s(b + 16, 4);
5887
- const beta = f4s(b + 32, 4);
5888
- if (!alpha || !beta) return;
5889
- const sys = id === 5893 ? "GPS" : "BDS";
5890
- ionoCorrections[`${sys}A`] = alpha;
5891
- ionoCorrections[`${sys}B`] = beta;
5892
- } else if (id === 4030 && len >= 28) {
5893
- const ai = f4s(b + 16, 3);
5894
- if (ai) ionoCorrections["GAL"] = ai;
5895
- } else if (id === 5894 && len >= 37) {
5896
- leapSeconds = view.getInt8(b + 33);
6257
+ // src/ubx/index.ts
6258
+ var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6259
+ var MS_PER_WEEK3 = 7 * 864e5;
6260
+ var SIGNALS = {
6261
+ 0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
6262
+ // GPS L1C/A, L2CL, L2CM
6263
+ 1: { 0: ["S", "1C"] },
6264
+ // SBAS L1C/A
6265
+ 2: { 0: ["E", "1X"], 1: ["E", "1X"], 5: ["E", "7X"], 6: ["E", "7X"] },
6266
+ // E1C/B, E5bI/Q
6267
+ 3: { 0: ["C", "2I"], 1: ["C", "2I"], 2: ["C", "7I"], 3: ["C", "7I"] },
6268
+ // B1I D1/D2, B2I D1/D2
6269
+ 5: { 0: ["J", "1C"], 4: ["J", "2X"], 5: ["J", "2X"] },
6270
+ // QZSS L1C/A, L2CM/CL
6271
+ 6: { 0: ["R", "1C"], 2: ["R", "2C"] }
6272
+ // GLONASS L1OF, L2OF
6273
+ };
6274
+ function prnFor(gnssId, svId) {
6275
+ const two5 = (n) => String(n).padStart(2, "0");
6276
+ switch (gnssId) {
6277
+ case 0:
6278
+ return svId >= 1 && svId <= 32 ? `G${two5(svId)}` : null;
6279
+ case 1:
6280
+ return svId >= 120 && svId <= 158 ? `S${two5(svId - 100)}` : null;
6281
+ case 2:
6282
+ return svId >= 1 && svId <= 36 ? `E${two5(svId)}` : null;
6283
+ case 3:
6284
+ return svId >= 1 && svId <= 63 ? `C${two5(svId)}` : null;
6285
+ case 5:
6286
+ return svId >= 1 && svId <= 10 ? `J${two5(svId)}` : null;
6287
+ case 6:
6288
+ return svId >= 1 && svId <= 32 ? `R${two5(svId)}` : null;
6289
+ default:
6290
+ return null;
6291
+ }
6292
+ }
6293
+ function parseUbxRawx(data) {
6294
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6295
+ const epochs = [];
6296
+ const messageCounts = {};
6297
+ const obsCodes = {};
6298
+ const stats = { badChecksums: 0 };
6299
+ for (const frame of ubxFrames(data, stats)) {
6300
+ const { msgClass: cls, msgId: id } = frame;
6301
+ const len = frame.payload.length;
6302
+ const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
6303
+ messageCounts[key] = (messageCounts[key] ?? 0) + 1;
6304
+ if (cls === 2 && id === 21 && len >= 16) {
6305
+ const p = frame.payloadStart;
6306
+ const rcvTow = view.getFloat64(p, true);
6307
+ const week = view.getUint16(p + 8, true);
6308
+ const leapS = view.getInt8(p + 10);
6309
+ const numMeas = data[p + 11];
6310
+ const recStat = data[p + 12];
6311
+ const leapValid = (recStat & 1) !== 0;
6312
+ const meas = [];
6313
+ for (let k = 0; k < numMeas; k++) {
6314
+ const off = p + 16 + 32 * k;
6315
+ if (off + 32 > p + len) break;
6316
+ const gnssId = data[off + 20];
6317
+ const svId = data[off + 21];
6318
+ const sigId = data[off + 22];
6319
+ const trkStat = data[off + 30];
6320
+ const prn = prnFor(gnssId, svId);
6321
+ const sig = SIGNALS[gnssId]?.[sigId];
6322
+ if (!prn || !sig) continue;
6323
+ const prValid = (trkStat & 1) !== 0;
6324
+ const cpValid = (trkStat & 2) !== 0;
6325
+ const cp = view.getFloat64(off + 8, true);
6326
+ meas.push({
6327
+ prn,
6328
+ code: sig[1],
6329
+ pr: prValid ? view.getFloat64(off, true) : null,
6330
+ cp: cpValid && cp !== 0 ? cp : null,
6331
+ doppler: view.getFloat32(off + 16, true),
6332
+ cn0: data[off + 26],
6333
+ halfCycleAmbiguous: (trkStat & 4) !== 0,
6334
+ lockTimeMs: view.getUint16(off + 24, true)
6335
+ });
6336
+ const sys = sig[0];
6337
+ const codes = obsCodes[sys] ??= [];
6338
+ if (!codes.includes(sig[1])) codes.push(sig[1]);
6339
+ }
6340
+ epochs.push({
6341
+ timeMs: GPS_EPOCH_MS7 + week * MS_PER_WEEK3 + Math.round(rcvTow * 1e3),
6342
+ leapS: leapValid ? leapS : null,
6343
+ meas
6344
+ });
5897
6345
  }
5898
- });
5899
- return { ionoCorrections, leapSeconds };
6346
+ }
6347
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
5900
6348
  }
5901
6349
 
5902
- // src/sbf/rawnav.ts
5903
- var SIGNAL_OF_BLOCK = {
5904
- 4018: "L2C",
5905
- 4019: "L5"
5906
- };
5907
- function parseSbfCnav(data) {
5908
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5909
- const ephemerides = [];
5910
- const assemblers = {
5911
- L2C: new CnavAssembler(),
5912
- L5: new CnavAssembler()
5913
- };
6350
+ // src/sbf/frame.ts
6351
+ var CRC_TABLE = (() => {
6352
+ const t = new Uint16Array(256);
6353
+ for (let n = 0; n < 256; n++) {
6354
+ let c = n << 8;
6355
+ for (let k = 0; k < 8; k++)
6356
+ c = c & 32768 ? (c << 1 ^ 4129) & 65535 : c << 1 & 65535;
6357
+ t[n] = c;
6358
+ }
6359
+ return t;
6360
+ })();
6361
+ function crc16(data, start, end) {
6362
+ let crc = 0;
6363
+ for (let i = start; i < end; i++)
6364
+ crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
6365
+ return crc;
6366
+ }
6367
+ function scanSbfFrames(data, view, onBlock) {
5914
6368
  let badCrc = 0;
5915
- let messages = 0;
5916
- scanSbfFrames(data, view, (id, b, len) => {
5917
- const signal = SIGNAL_OF_BLOCK[id];
5918
- if (!signal || len < 60) return;
5919
- messages++;
5920
- const msg = new Uint8Array(40);
5921
- for (let k = 0; k < 10; k++) {
5922
- const w = view.getUint32(b + 20 + 4 * k, true);
5923
- msg[4 * k] = w >>> 24;
5924
- msg[4 * k + 1] = w >>> 16 & 255;
5925
- msg[4 * k + 2] = w >>> 8 & 255;
5926
- msg[4 * k + 3] = w & 255;
6369
+ let i = 0;
6370
+ while (i + 8 <= data.length) {
6371
+ if (data[i] !== 36 || data[i + 1] !== 64) {
6372
+ i++;
6373
+ continue;
5927
6374
  }
5928
- if (!cnavCrcOk(msg)) {
6375
+ const len = view.getUint16(i + 6, true);
6376
+ if (len < 8 || len % 4 !== 0) {
6377
+ i++;
6378
+ continue;
6379
+ }
6380
+ if (i + len > data.length) break;
6381
+ if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
5929
6382
  badCrc++;
5930
- return;
6383
+ i++;
6384
+ continue;
5931
6385
  }
5932
- const eph = assemblers[signal].push(msg);
5933
- if (eph) ephemerides.push({ ...eph, signal });
5934
- });
5935
- return { ephemerides, badCrc, messages };
5936
- }
5937
-
5938
- // src/navbits/gal.ts
5939
- var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
5940
- var SEC_PER_WEEK5 = 7 * 86400;
5941
- var HALF_WEEK2 = 302400;
5942
- var GST_GPS_WEEK_OFFSET = 1024;
5943
- function galInavPageCrcOk(page) {
5944
- if (page.length < 28) return false;
5945
- return crc24q(page, 196) === getBitU(page, 196, 24);
5946
- }
5947
- function galFnavPageCrcOk(page) {
5948
- if (page.length < 30) return false;
5949
- return crc24q(page, 214) === getBitU(page, 214, 24);
5950
- }
5951
- function gpsDate2(week, sec) {
5952
- return new Date(GPS_EPOCH_MS6 + (week * SEC_PER_WEEK5 + sec) * 1e3);
6386
+ onBlock(view.getUint16(i + 4, true) & 8191, i, len);
6387
+ i += len;
6388
+ }
6389
+ return badCrc;
5953
6390
  }
5954
- function weekOfToe(week, tow, toe) {
5955
- if (toe - tow > HALF_WEEK2) return week - 1;
5956
- if (toe - tow < -HALF_WEEK2) return week + 1;
5957
- return week;
6391
+ var two3 = (n) => String(n).padStart(2, "0");
6392
+ function svidToPrn(svid) {
6393
+ if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two3(svid)}` : null;
6394
+ if (svid <= 61) return svid - 37 <= 27 ? `R${two3(svid - 37)}` : null;
6395
+ if (svid <= 62) return null;
6396
+ if (svid <= 68) return svid - 38 <= 27 ? `R${two3(svid - 38)}` : null;
6397
+ if (svid <= 70) return null;
6398
+ if (svid <= 106) return svid - 70 <= 36 ? `E${two3(svid - 70)}` : null;
6399
+ if (svid <= 119) return null;
6400
+ if (svid <= 140) return `S${two3(svid - 100)}`;
6401
+ if (svid <= 180) return svid - 140 <= 50 ? `C${two3(svid - 140)}` : null;
6402
+ if (svid <= 190) return `J${two3(svid - 180)}`;
6403
+ if (svid <= 197) return svid - 190 <= 14 ? `I${two3(svid - 190)}` : null;
6404
+ if (svid <= 215) return `S${two3(svid - 157)}`;
6405
+ if (svid <= 222) return svid - 208 <= 14 ? `I${two3(svid - 208)}` : null;
6406
+ if (svid <= 245) return svid - 182 <= 50 ? `C${two3(svid - 182)}` : null;
6407
+ return null;
5958
6408
  }
5959
- var two2 = (n) => String(n).padStart(2, "0");
5960
- function decodeGalInavWords(words) {
5961
- if (words.length < 96) return null;
5962
- const b = words;
5963
- let i = 128;
5964
- const type1 = getBitU(b, i, 6);
5965
- i += 6;
5966
- const iodNav1 = getBitU(b, i, 10);
5967
- i += 10;
5968
- const toes = getBitU(b, i, 14) * 60;
5969
- i += 14;
5970
- const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5971
- i += 32;
5972
- const e = getBitU(b, i, 32) * 2 ** -33;
5973
- i += 32;
5974
- const sqrtA = getBitU(b, i, 32) * 2 ** -19;
5975
- i = 128 * 2;
5976
- const type2 = getBitU(b, i, 6);
5977
- i += 6;
5978
- const iodNav2 = getBitU(b, i, 10);
5979
- i += 10;
5980
- const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5981
- i += 32;
5982
- const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5983
- i += 32;
5984
- const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
5985
- i += 32;
5986
- const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
5987
- i = 128 * 3;
5988
- const type3 = getBitU(b, i, 6);
5989
- i += 6;
5990
- const iodNav3 = getBitU(b, i, 10);
5991
- i += 10;
5992
- const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
5993
- i += 24;
5994
- const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
5995
- i += 16;
5996
- const cuc = getBitS(b, i, 16) * 2 ** -29;
5997
- i += 16;
5998
- const cus = getBitS(b, i, 16) * 2 ** -29;
5999
- i += 16;
6000
- const crc = getBitS(b, i, 16) * 2 ** -5;
6001
- i += 16;
6002
- const crs = getBitS(b, i, 16) * 2 ** -5;
6003
- i = 128 * 4;
6004
- const type4 = getBitU(b, i, 6);
6005
- i += 6;
6006
- const iodNav4 = getBitU(b, i, 10);
6007
- i += 10;
6008
- const svid = getBitU(b, i, 6);
6009
- i += 6;
6010
- const cic = getBitS(b, i, 16) * 2 ** -29;
6011
- i += 16;
6012
- const cis = getBitS(b, i, 16) * 2 ** -29;
6013
- i += 16;
6014
- const tocs = getBitU(b, i, 14) * 60;
6015
- i += 14;
6016
- const af0 = getBitS(b, i, 31) * 2 ** -34;
6017
- i += 31;
6018
- const af1 = getBitS(b, i, 21) * 2 ** -46;
6019
- i += 21;
6020
- const af2 = getBitS(b, i, 6) * 2 ** -59;
6021
- i = 128 * 5;
6022
- const type5 = getBitU(b, i, 6);
6023
- i += 6 + 11 + 11 + 14 + 5;
6024
- const bgdE5a = getBitS(b, i, 10) * 2 ** -32;
6025
- i += 10;
6026
- i += 10;
6027
- const e5bHs = getBitU(b, i, 2);
6028
- i += 2;
6029
- const e1bHs = getBitU(b, i, 2);
6030
- i += 2;
6031
- const e5bDvs = getBitU(b, i, 1);
6032
- i += 1;
6033
- const e1bDvs = getBitU(b, i, 1);
6034
- i += 1;
6035
- const gstWeek = getBitU(b, i, 12);
6036
- i += 12;
6037
- const tow = getBitU(b, i, 20);
6038
- if (type1 !== 1 || type2 !== 2 || type3 !== 3 || type4 !== 4 || type5 !== 5)
6039
- return null;
6040
- if (iodNav1 !== iodNav2 || iodNav1 !== iodNav3 || iodNav1 !== iodNav4)
6041
- return null;
6042
- if (svid < 1 || svid > 36) return null;
6043
- const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
6044
- const tocDate = gpsDate2(week, tocs);
6409
+
6410
+ // src/sbf/nav.ts
6411
+ var PI2 = Math.PI;
6412
+ var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
6413
+ var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
6414
+ var SEC_PER_WEEK8 = 7 * 86400;
6415
+ var MS_PER_WEEK4 = SEC_PER_WEEK8 * 1e3;
6416
+ var F4_DNU = -2e10;
6417
+ var U4_DNU = 4294967295;
6418
+ function adjustWeek(refWeek, wn, mod) {
6419
+ let offset = refWeek % mod - wn;
6420
+ if (offset > mod / 2) offset -= mod;
6421
+ if (offset < -(mod / 2 - 1)) offset += mod;
6422
+ return refWeek - offset;
6423
+ }
6424
+ var gpsMs = (week, sec) => GPS_EPOCH_MS8 + week * MS_PER_WEEK4 + sec * 1e3;
6425
+ var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK8;
6426
+ function decodeGpsQzsNav(view, b, sys) {
6427
+ const svid = view.getUint8(b + 14);
6428
+ const prn = sys === "G" ? svid : svid - 180;
6429
+ const max = sys === "G" ? 32 : 10;
6430
+ if (prn < 1 || prn > max) return null;
6431
+ const wnc = view.getUint16(b + 12, true);
6432
+ const tgdRaw = view.getFloat32(b + 28, true);
6433
+ const tocs = view.getUint32(b + 32, true);
6434
+ const toes = view.getUint32(b + 88, true);
6435
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 136, true), 1024);
6436
+ const tocDate = new Date(gpsMs(wnToc, tocs));
6045
6437
  return {
6046
- system: "E",
6047
- prn: `E${two2(svid)}`,
6048
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
6438
+ system: sys,
6439
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
6440
+ toc: sowOf2(tocDate.getTime()),
6049
6441
  tocDate,
6050
- af0,
6051
- af1,
6052
- af2,
6053
- iode: iodNav1,
6054
- crs,
6055
- deltaN,
6056
- m0,
6057
- cuc,
6058
- e,
6059
- cus,
6060
- sqrtA,
6442
+ af0: view.getFloat32(b + 44, true),
6443
+ af1: view.getFloat32(b + 40, true),
6444
+ af2: view.getFloat32(b + 36, true),
6445
+ iode: view.getUint8(b + 24),
6446
+ crs: view.getFloat32(b + 48, true),
6447
+ deltaN: view.getFloat32(b + 52, true) * PI2,
6448
+ m0: view.getFloat64(b + 56, true) * PI2,
6449
+ cuc: view.getFloat32(b + 64, true),
6450
+ e: view.getFloat64(b + 68, true),
6451
+ cus: view.getFloat32(b + 76, true),
6452
+ sqrtA: view.getFloat64(b + 80, true),
6061
6453
  toe: toes,
6062
- cic,
6063
- omega0,
6064
- cis,
6065
- i0,
6066
- crc,
6067
- omega,
6068
- omegaDot,
6069
- idot,
6070
- week,
6071
- svHealth: e5bHs << 7 | e5bDvs << 6 | e1bHs << 1 | e1bDvs,
6072
- tgd: bgdE5a
6454
+ cic: view.getFloat32(b + 92, true),
6455
+ omega0: view.getFloat64(b + 96, true) * PI2,
6456
+ cis: view.getFloat32(b + 104, true),
6457
+ i0: view.getFloat64(b + 108, true) * PI2,
6458
+ crc: view.getFloat32(b + 116, true),
6459
+ omega: view.getFloat64(b + 120, true) * PI2,
6460
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
6461
+ idot: view.getFloat32(b + 132, true) * PI2,
6462
+ // RINEX "GPS week to go with toe": RTKLIB writes WNc for GPS and
6463
+ // the week of toc for QZSS mirrored here so records compare
6464
+ // 1:1 against convbin output. wnToe is still used for `toe` above.
6465
+ week: sys === "G" ? wnc : wnToc,
6466
+ svHealth: view.getUint8(b + 20),
6467
+ tgd: tgdRaw !== F4_DNU ? tgdRaw : 0
6073
6468
  };
6074
6469
  }
6075
- function decodeGalFnavPages(pages) {
6076
- if (pages.length < 124) return null;
6077
- const b = pages;
6078
- let i = 0;
6079
- const type1 = getBitU(b, i, 6);
6080
- i += 6;
6081
- const svid = getBitU(b, i, 6);
6082
- i += 6;
6083
- const iodNav1 = getBitU(b, i, 10);
6084
- i += 10;
6085
- const tocs = getBitU(b, i, 14) * 60;
6086
- i += 14;
6087
- const af0 = getBitS(b, i, 31) * 2 ** -34;
6088
- i += 31;
6089
- const af1 = getBitS(b, i, 21) * 2 ** -46;
6090
- i += 21;
6091
- const af2 = getBitS(b, i, 6) * 2 ** -59;
6092
- i += 6;
6093
- i += 8 + 11 + 11 + 14 + 5;
6094
- const bgdE5a = getBitS(b, i, 10) * 2 ** -32;
6095
- i += 10;
6096
- const e5aHs = getBitU(b, i, 2);
6097
- i += 2;
6098
- const gstWeek = getBitU(b, i, 12);
6099
- i += 12;
6100
- const tow = getBitU(b, i, 20);
6101
- i += 20;
6102
- const e5aDvs = getBitU(b, i, 1);
6103
- i = 31 * 8;
6104
- const type2 = getBitU(b, i, 6);
6105
- i += 6;
6106
- const iodNav2 = getBitU(b, i, 10);
6107
- i += 10;
6108
- const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
6109
- i += 32;
6110
- const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
6111
- i += 24;
6112
- const e = getBitU(b, i, 32) * 2 ** -33;
6113
- i += 32;
6114
- const sqrtA = getBitU(b, i, 32) * 2 ** -19;
6115
- i += 32;
6116
- const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
6117
- i += 32;
6118
- const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
6119
- i = 62 * 8;
6120
- const type3 = getBitU(b, i, 6);
6121
- i += 6;
6122
- const iodNav3 = getBitU(b, i, 10);
6123
- i += 10;
6124
- const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
6125
- i += 32;
6126
- const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
6127
- i += 32;
6128
- const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
6129
- i += 16;
6130
- const cuc = getBitS(b, i, 16) * 2 ** -29;
6131
- i += 16;
6132
- const cus = getBitS(b, i, 16) * 2 ** -29;
6133
- i += 16;
6134
- const crc = getBitS(b, i, 16) * 2 ** -5;
6135
- i += 16;
6136
- const crs = getBitS(b, i, 16) * 2 ** -5;
6137
- i += 16;
6138
- const toes = getBitU(b, i, 14) * 60;
6139
- i = 93 * 8;
6140
- const type4 = getBitU(b, i, 6);
6141
- i += 6;
6142
- const iodNav4 = getBitU(b, i, 10);
6143
- i += 10;
6144
- const cic = getBitS(b, i, 16) * 2 ** -29;
6145
- i += 16;
6146
- const cis = getBitS(b, i, 16) * 2 ** -29;
6147
- if (type1 !== 1 || type2 !== 2 || type3 !== 3 || type4 !== 4) return null;
6148
- if (iodNav1 !== iodNav2 || iodNav1 !== iodNav3 || iodNav1 !== iodNav4)
6149
- return null;
6150
- if (svid < 1 || svid > 36) return null;
6151
- const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
6152
- const tocDate = gpsDate2(week, tocs);
6470
+ function decodeGalNav(view, b) {
6471
+ const prn = view.getUint8(b + 14) - 70;
6472
+ if (prn < 1 || prn > 36) return null;
6473
+ const source = view.getUint8(b + 15);
6474
+ if (source !== 2 && source !== 16) return null;
6475
+ const wnc = view.getUint16(b + 12, true);
6476
+ const toes = view.getUint32(b + 100, true);
6477
+ const tocs = view.getUint32(b + 104, true);
6478
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 126, true), 4096);
6479
+ const tocDate = new Date(gpsMs(wnToc, tocs));
6480
+ const health = view.getUint16(b + 130, true);
6481
+ let svh = 0;
6482
+ if (health & 1) svh |= health >> 1 & 7;
6483
+ if (health & 16) svh |= (health >> 5 & 7) << 6;
6484
+ if (health & 256) svh |= (health >> 9 & 7) << 3;
6485
+ const bgdE5a = view.getFloat32(b + 136, true);
6153
6486
  return {
6154
6487
  system: "E",
6155
- prn: `E${two2(svid)}`,
6156
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
6488
+ prn: `E${String(prn).padStart(2, "0")}`,
6489
+ toc: sowOf2(tocDate.getTime()),
6157
6490
  tocDate,
6158
- af0,
6159
- af1,
6160
- af2,
6161
- iode: iodNav1,
6162
- crs,
6163
- deltaN,
6164
- m0,
6165
- cuc,
6166
- e,
6167
- cus,
6168
- sqrtA,
6491
+ af0: view.getFloat64(b + 116, true),
6492
+ af1: view.getFloat32(b + 112, true),
6493
+ af2: view.getFloat32(b + 108, true),
6494
+ iode: view.getUint16(b + 128, true),
6495
+ // IODnav
6496
+ crs: view.getFloat32(b + 88, true),
6497
+ deltaN: view.getFloat32(b + 72, true) * PI2,
6498
+ m0: view.getFloat64(b + 24, true) * PI2,
6499
+ cuc: view.getFloat32(b + 76, true),
6500
+ e: view.getFloat64(b + 32, true),
6501
+ cus: view.getFloat32(b + 80, true),
6502
+ sqrtA: view.getFloat64(b + 16, true),
6169
6503
  toe: toes,
6170
- cic,
6171
- omega0,
6172
- cis,
6173
- i0,
6174
- crc,
6175
- omega,
6176
- omegaDot,
6177
- idot,
6178
- week,
6179
- svHealth: e5aHs << 4 | e5aDvs << 3,
6180
- tgd: bgdE5a
6504
+ cic: view.getFloat32(b + 92, true),
6505
+ omega0: view.getFloat64(b + 56, true) * PI2,
6506
+ cis: view.getFloat32(b + 96, true),
6507
+ i0: view.getFloat64(b + 40, true) * PI2,
6508
+ crc: view.getFloat32(b + 84, true),
6509
+ omega: view.getFloat64(b + 48, true) * PI2,
6510
+ omegaDot: view.getFloat32(b + 64, true) * PI2,
6511
+ idot: view.getFloat32(b + 68, true) * PI2,
6512
+ week: wnc,
6513
+ // RINEX GAL week is GPS-aligned and continuous
6514
+ svHealth: svh,
6515
+ tgd: bgdE5a !== F4_DNU ? bgdE5a : 0
6516
+ // BGD E5a/E1 (RINEX slot)
6517
+ };
6518
+ }
6519
+ function decodeBdsNav(view, b) {
6520
+ const prn = svidToPrn(view.getUint8(b + 14));
6521
+ if (!prn || prn[0] !== "C") return null;
6522
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
6523
+ const tgd1 = view.getFloat32(b + 24, true);
6524
+ const tocs = view.getUint32(b + 32, true);
6525
+ const toes = view.getUint32(b + 88, true);
6526
+ const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
6527
+ const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
6528
+ const tocDate = new Date(BDT_EPOCH_MS2 + wnToc * MS_PER_WEEK4 + tocs * 1e3);
6529
+ return {
6530
+ system: "C",
6531
+ prn,
6532
+ toc: sowOf2(tocDate.getTime()),
6533
+ tocDate,
6534
+ af0: view.getFloat32(b + 44, true),
6535
+ af1: view.getFloat32(b + 40, true),
6536
+ af2: view.getFloat32(b + 36, true),
6537
+ iode: view.getUint8(b + 21),
6538
+ // AODE
6539
+ crs: view.getFloat32(b + 48, true),
6540
+ deltaN: view.getFloat32(b + 52, true) * PI2,
6541
+ m0: view.getFloat64(b + 56, true) * PI2,
6542
+ cuc: view.getFloat32(b + 64, true),
6543
+ e: view.getFloat64(b + 68, true),
6544
+ cus: view.getFloat32(b + 76, true),
6545
+ sqrtA: view.getFloat64(b + 80, true),
6546
+ toe: toes,
6547
+ cic: view.getFloat32(b + 92, true),
6548
+ omega0: view.getFloat64(b + 96, true) * PI2,
6549
+ cis: view.getFloat32(b + 104, true),
6550
+ i0: view.getFloat64(b + 108, true) * PI2,
6551
+ crc: view.getFloat32(b + 116, true),
6552
+ omega: view.getFloat64(b + 120, true) * PI2,
6553
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
6554
+ idot: view.getFloat32(b + 132, true) * PI2,
6555
+ week: wnToe,
6556
+ // RINEX BDS week field is the BDT week
6557
+ svHealth: view.getUint8(b + 19),
6558
+ // SatH1
6559
+ tgd: tgd1 !== F4_DNU ? tgd1 : 0
6560
+ // TGD1 B1/B3 (RINEX slot)
6561
+ };
6562
+ }
6563
+ function decodeGloNav(view, b) {
6564
+ const prn = svidToPrn(view.getUint8(b + 14));
6565
+ if (!prn || prn[0] !== "R") return null;
6566
+ const wnc = view.getUint16(b + 12, true);
6567
+ const toes = view.getUint32(b + 76, true);
6568
+ const wnToe = adjustWeek(wnc, view.getUint16(b + 80, true), 1024);
6569
+ const toeGpsMs = gpsMs(wnToe, toes);
6570
+ const leapMs = getGpsLeap(new Date(toeGpsMs)) * 1e3;
6571
+ const tocDate = new Date(toeGpsMs - leapMs);
6572
+ const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
6573
+ const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
6574
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS8) / 1e3 % SEC_PER_WEEK8;
6575
+ return {
6576
+ system: "R",
6577
+ prn,
6578
+ tocDate,
6579
+ // RINEX stores −τn as the clock bias; SBF carries τn (ICD sign)
6580
+ tauN: -view.getFloat32(b + 68, true),
6581
+ gammaN: view.getFloat32(b + 64, true),
6582
+ messageFrameTime,
6583
+ x: view.getFloat64(b + 16, true),
6584
+ // SBF unit is km, like RINEX
6585
+ xDot: view.getFloat32(b + 40, true),
6586
+ xAcc: view.getFloat32(b + 52, true),
6587
+ y: view.getFloat64(b + 24, true),
6588
+ yDot: view.getFloat32(b + 44, true),
6589
+ yAcc: view.getFloat32(b + 56, true),
6590
+ z: view.getFloat64(b + 32, true),
6591
+ zDot: view.getFloat32(b + 48, true),
6592
+ zAcc: view.getFloat32(b + 60, true),
6593
+ // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
6594
+ health: view.getUint8(b + 85) >> 2,
6595
+ freqNum: view.getUint8(b + 15) - 8
6181
6596
  };
6182
6597
  }
6183
- var dedupKey = (eph) => `${eph.iode}:${eph.toe}:${eph.tocDate.getTime()}`;
6184
- var GalInavAssembler = class {
6185
- sats = /* @__PURE__ */ new Map();
6186
- /**
6187
- * Push one I/NAV page pair for satellite `prn` (1-36): the 234-bit
6188
- * even+odd concatenation, even part first with its 6 tail bits
6189
- * removed, bit 0 = the even part's Even/Odd bit (≥ 17 bytes).
6190
- * Returns the newly completed ephemeris, or null. Pages that are
6191
- * not an even/odd nominal pair, alert pages and word types outside
6192
- * 1-5 are ignored.
6193
- */
6194
- push(prn, page) {
6195
- if (prn < 1 || prn > 36 || page.length < 17) return null;
6196
- if (getBitU(page, 0, 1) !== 0 || getBitU(page, 114, 1) !== 1) return null;
6197
- if (getBitU(page, 1, 1) === 1 || getBitU(page, 115, 1) === 1) return null;
6198
- const type = getBitU(page, 2, 6);
6199
- if (type < 1 || type > 5) return null;
6200
- let sat = this.sats.get(prn);
6201
- if (!sat) {
6202
- sat = { words: new Uint8Array(96) };
6203
- this.sats.set(prn, sat);
6204
- }
6205
- for (let k = 0; k < 14; k++)
6206
- sat.words[type * 16 + k] = getBitU(page, 2 + 8 * k, 8);
6207
- sat.words[type * 16 + 14] = getBitU(page, 116, 8);
6208
- sat.words[type * 16 + 15] = getBitU(page, 124, 8);
6209
- if (type !== 5) return null;
6210
- const eph = decodeGalInavWords(sat.words);
6211
- if (!eph || eph.prn !== `E${two2(prn)}`) return null;
6212
- const key = dedupKey(eph);
6213
- if (key === sat.lastKey) return null;
6214
- sat.lastKey = key;
6215
- return eph;
6216
- }
6217
- };
6218
- var GalFnavAssembler = class {
6219
- sats = /* @__PURE__ */ new Map();
6220
- /**
6221
- * Push one F/NAV page for satellite `prn` (1-36): 244 bits with the
6222
- * sync field stripped, bit 0 = first page-type bit (≥ 31 bytes).
6223
- * Returns the newly completed ephemeris, or null. Dummy pages
6224
- * (type 63) and page types outside 1-4 are ignored.
6225
- */
6226
- push(prn, page) {
6227
- if (prn < 1 || prn > 36 || page.length < 31) return null;
6228
- const type = getBitU(page, 0, 6);
6229
- if (type < 1 || type > 4) return null;
6230
- let sat = this.sats.get(prn);
6231
- if (!sat) {
6232
- sat = { words: new Uint8Array(124) };
6233
- this.sats.set(prn, sat);
6234
- }
6235
- sat.words.set(page.subarray(0, 31), (type - 1) * 31);
6236
- if (type !== 4) return null;
6237
- const eph = decodeGalFnavPages(sat.words);
6238
- if (!eph || eph.prn !== `E${two2(prn)}`) return null;
6239
- const key = dedupKey(eph);
6240
- if (key === sat.lastKey) return null;
6241
- sat.lastKey = key;
6242
- return eph;
6243
- }
6244
- };
6245
-
6246
- // src/sbf/rawnav-gal.ts
6247
- var INAV_SOURCES = [17, 21, 22];
6248
- var FNAV_SOURCES = [20, 22];
6249
- function parseSbfGalNav(data) {
6598
+ function parseSbfNav(data) {
6250
6599
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6251
6600
  const ephemerides = [];
6252
- const inav = new GalInavAssembler();
6253
- const fnav = new GalFnavAssembler();
6254
- let badCrc = 0;
6255
- let messages = 0;
6256
- scanSbfFrames(data, view, (id, b, len) => {
6257
- if (id !== 4022 && id !== 4023 || len < 52) return;
6258
- messages++;
6259
- const prnStr = svidToPrn(data[b + 14]);
6260
- if (!prnStr || prnStr[0] !== "E") return;
6261
- const prn = parseInt(prnStr.slice(1), 10);
6262
- const source = data[b + 17] & 31;
6263
- const page = new Uint8Array(32);
6264
- for (let k = 0; k < 8; k++) {
6265
- const w = view.getUint32(b + 20 + 4 * k, true);
6266
- page[4 * k] = w >>> 24;
6267
- page[4 * k + 1] = w >>> 16 & 255;
6268
- page[4 * k + 2] = w >>> 8 & 255;
6269
- page[4 * k + 3] = w & 255;
6270
- }
6271
- let eph;
6272
- let src;
6273
- if (id === 4023) {
6274
- src = "inav";
6275
- if (!INAV_SOURCES.includes(source)) return;
6276
- if (getBitU2(page, 0) !== 0 || getBitU2(page, 114) !== 1) return;
6277
- if (!galInavPageCrcOk(page)) {
6278
- badCrc++;
6279
- return;
6280
- }
6281
- eph = inav.push(prn, page);
6282
- } else {
6283
- src = "fnav";
6284
- if (!FNAV_SOURCES.includes(source)) return;
6285
- if (isFnavDummy(page)) return;
6286
- if (!galFnavPageCrcOk(page)) {
6287
- badCrc++;
6288
- return;
6289
- }
6290
- eph = fnav.push(prn, page);
6291
- }
6292
- if (eph) ephemerides.push({ ...eph, source: src });
6601
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
6602
+ let eph = null;
6603
+ if (id === 5891 && len >= 140) eph = decodeGpsQzsNav(view, b, "G");
6604
+ else if (id === 4095 && len >= 140) eph = decodeGpsQzsNav(view, b, "J");
6605
+ else if (id === 4002 && len >= 149) eph = decodeGalNav(view, b);
6606
+ else if (id === 4081 && len >= 140) eph = decodeBdsNav(view, b);
6607
+ else if (id === 4004 && len >= 96) eph = decodeGloNav(view, b);
6608
+ if (eph) ephemerides.push(eph);
6293
6609
  });
6294
- return { ephemerides, badCrc, messages };
6295
- }
6296
- function getBitU2(buff, pos) {
6297
- return buff[pos >> 3] >> 7 - (pos & 7) & 1;
6298
- }
6299
- function isFnavDummy(page) {
6300
- return page[0] >> 2 === 63;
6301
- }
6302
-
6303
- // src/navbits/bds.ts
6304
- var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
6305
- var SEC_PER_WEEK6 = 7 * 86400;
6306
- var MS_PER_WEEK4 = SEC_PER_WEEK6 * 1e3;
6307
- var HALF_WEEK3 = 302400;
6308
- var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK6;
6309
- var BDS_SUBFRAME_BYTES = 38;
6310
- function getBitU22(b, p1, l1, p2, l2) {
6311
- return getBitU(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
6312
- }
6313
- function getBitS2(b, p1, l1, p2, l2) {
6314
- return getBitS(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
6315
- }
6316
- function getBitU3(b, p1, l1, p2, l2, p3, l3) {
6317
- return getBitU(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
6318
- }
6319
- function getBitS3(b, p1, l1, p2, l2, p3, l3) {
6320
- return getBitS(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
6610
+ return { ephemerides, badCrc };
6321
6611
  }
6322
- var mergeS = (hi, lo, n) => hi * 2 ** n + lo;
6323
- function bchOk(cw) {
6324
- for (let i = 14; i >= 4; i--) {
6325
- if (cw & 1 << i) cw ^= 19 << i - 4;
6612
+ var GAL_SQRT_A_NOMINAL = Math.sqrt(296e5);
6613
+ var GAL_I_NOMINAL = 56 / 180 * PI2;
6614
+ var I_REF_03 = 0.3 * PI2;
6615
+ function decodeGpsGalAlm(view, b, sys) {
6616
+ const wnc = view.getUint16(b + 12, true);
6617
+ const toa = view.getUint32(b + 20, true);
6618
+ if (toa === U4_DNU) return null;
6619
+ let prn;
6620
+ let weekAlm;
6621
+ let i0;
6622
+ let sqrtA = view.getFloat32(b + 32, true);
6623
+ let health;
6624
+ if (sys === "G") {
6625
+ prn = view.getUint8(b + 14);
6626
+ if (prn < 1 || prn > 32) return null;
6627
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 256);
6628
+ i0 = I_REF_03 + view.getFloat32(b + 24, true) * PI2;
6629
+ health = view.getUint8(b + 58);
6630
+ } else {
6631
+ prn = view.getUint8(b + 57) - 70;
6632
+ if (prn < 1 || prn > 36) return null;
6633
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 4);
6634
+ i0 = GAL_I_NOMINAL + view.getFloat32(b + 24, true) * PI2;
6635
+ sqrtA += GAL_SQRT_A_NOMINAL;
6636
+ health = view.getUint16(b + 58, true);
6326
6637
  }
6327
- return cw === 0;
6638
+ return {
6639
+ system: sys,
6640
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
6641
+ weekAlm,
6642
+ toaSec: toa,
6643
+ sqrtA,
6644
+ e: view.getFloat32(b + 16, true),
6645
+ i0OrDeltaI: i0,
6646
+ omega0: view.getFloat32(b + 36, true) * PI2,
6647
+ omega: view.getFloat32(b + 40, true) * PI2,
6648
+ m0: view.getFloat32(b + 44, true) * PI2,
6649
+ omegaDot: view.getFloat32(b + 28, true) * PI2,
6650
+ af0: view.getFloat32(b + 52, true),
6651
+ af1: view.getFloat32(b + 48, true),
6652
+ health
6653
+ };
6328
6654
  }
6329
- function bdsSubframeParityOk(subframe) {
6330
- if (subframe.length < BDS_SUBFRAME_BYTES) return false;
6331
- if (!bchOk(getBitU(subframe, 15, 15))) return false;
6332
- for (let w = 1; w < 10; w++) {
6333
- const base = 30 * w;
6334
- const cw1 = getBitU(subframe, base, 11) * 16 + getBitU(subframe, base + 22, 4);
6335
- const cw2 = getBitU(subframe, base + 11, 11) * 16 + getBitU(subframe, base + 26, 4);
6336
- if (!bchOk(cw1) || !bchOk(cw2)) return false;
6337
- }
6338
- return true;
6655
+ function decodeBdsAlm(view, b) {
6656
+ const prnStr = svidToPrn(view.getUint8(b + 14));
6657
+ if (!prnStr || prnStr[0] !== "C") return null;
6658
+ const prn = parseInt(prnStr.slice(1), 10);
6659
+ const toa = view.getUint32(b + 16, true);
6660
+ if (toa === U4_DNU) return null;
6661
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
6662
+ const geo = prn <= 5 || prn >= 59;
6663
+ return {
6664
+ system: "C",
6665
+ prn: prnStr,
6666
+ weekAlm: adjustWeek(bdsWeekRef, view.getUint8(b + 15), 256),
6667
+ toaSec: toa,
6668
+ sqrtA: view.getFloat32(b + 20, true),
6669
+ e: view.getFloat32(b + 24, true),
6670
+ i0OrDeltaI: (geo ? 0 : I_REF_03) + view.getFloat32(b + 44, true) * PI2,
6671
+ omega0: view.getFloat32(b + 36, true) * PI2,
6672
+ omega: view.getFloat32(b + 28, true) * PI2,
6673
+ m0: view.getFloat32(b + 32, true) * PI2,
6674
+ omegaDot: view.getFloat32(b + 40, true) * PI2,
6675
+ af0: view.getFloat32(b + 48, true),
6676
+ af1: view.getFloat32(b + 52, true),
6677
+ health: view.getUint16(b + 56, true)
6678
+ };
6339
6679
  }
6340
- function buildBdsEphemeris(prn, f) {
6341
- let week = f.week;
6342
- if (f.toes < f.sow - HALF_WEEK3) week++;
6343
- else if (f.toes > f.sow + HALF_WEEK3) week--;
6344
- const tocDate = new Date(BDT_EPOCH_MS2 + week * MS_PER_WEEK4 + f.tocSec * 1e3);
6680
+ function decodeGloAlm(view, b) {
6681
+ const prn = svidToPrn(view.getUint8(b + 14));
6682
+ if (!prn || prn[0] !== "R") return null;
6683
+ const wnc = view.getUint16(b + 12, true);
6684
+ const toa = view.getUint32(b + 20, true);
6685
+ if (toa === U4_DNU) return null;
6345
6686
  return {
6346
- system: "C",
6687
+ system: "R",
6347
6688
  prn,
6348
- toc: sowOf2(tocDate.getTime()),
6349
- tocDate,
6350
- af0: f.af0,
6351
- af1: f.af1,
6352
- af2: f.af2,
6353
- // AODE is not in the ephemeris subframes; RTKLIB derives the RINEX
6354
- // IODE/AODE slot from toc per the BDS ICD update schedule.
6355
- iode: Math.floor(f.tocSec / 720) % 240,
6356
- crs: f.crs,
6357
- deltaN: f.deltaN,
6358
- m0: f.m0,
6359
- cuc: f.cuc,
6360
- e: f.e,
6361
- cus: f.cus,
6362
- sqrtA: f.sqrtA,
6363
- toe: f.toes,
6364
- cic: f.cic,
6365
- omega0: f.omega0,
6366
- cis: f.cis,
6367
- i0: f.i0,
6368
- crc: f.crc,
6369
- omega: f.omega,
6370
- omegaDot: f.omegaDot,
6371
- idot: f.idot,
6372
- week,
6373
- // RINEX BDS week field is the BDT week of toe
6374
- svHealth: f.svh,
6375
- // SatH1
6376
- tgd: f.tgd1
6377
- // TGD1 (B1) — RINEX slot
6689
+ freqNr: view.getUint8(b + 15) - 8,
6690
+ weekAlm: adjustWeek(wnc, view.getUint8(b + 52), 256),
6691
+ toaSec: toa,
6692
+ epsilon: view.getFloat32(b + 16, true),
6693
+ deltaI: view.getFloat32(b + 24, true) * PI2,
6694
+ lambda: view.getFloat32(b + 28, true) * PI2,
6695
+ tLambda: view.getFloat32(b + 32, true),
6696
+ omega: view.getFloat32(b + 36, true) * PI2,
6697
+ deltaT: view.getFloat32(b + 40, true),
6698
+ deltaTDot: view.getFloat32(b + 44, true),
6699
+ tau: view.getFloat32(b + 48, true),
6700
+ health: view.getUint8(b + 53),
6701
+ nDay: view.getUint16(b + 54, true),
6702
+ n4: view.getUint8(b + 57)
6378
6703
  };
6379
6704
  }
6380
- function decodeBdsD1Frame(subframes, opts = {}) {
6381
- if (subframes.length < 3 * BDS_SUBFRAME_BYTES) return null;
6382
- const b = subframes;
6383
- let i = 8 * 38 * 0;
6384
- const frn1 = getBitU(b, i + 15, 3);
6385
- const sow1 = getBitU22(b, i + 18, 8, i + 30, 12);
6386
- const svh = getBitU(b, i + 42, 1);
6387
- const week = getBitU(b, i + 60, 13);
6388
- const tocSec = getBitU22(b, i + 73, 9, i + 90, 8) * 8;
6389
- const tgd1 = getBitS(b, i + 98, 10) * 0.1 * 1e-9;
6390
- const af2 = getBitS(b, i + 214, 11) * 2 ** -66;
6391
- const af0 = getBitS2(b, i + 225, 7, i + 240, 17) * 2 ** -33;
6392
- const af1 = getBitS2(b, i + 257, 5, i + 270, 17) * 2 ** -50;
6393
- i = 8 * 38 * 1;
6394
- const frn2 = getBitU(b, i + 15, 3);
6395
- const sow2 = getBitU22(b, i + 18, 8, i + 30, 12);
6396
- const deltaN = getBitS2(b, i + 42, 10, i + 60, 6) * 2 ** -43 * GPS_PI;
6397
- const cuc = getBitS2(b, i + 66, 16, i + 90, 2) * 2 ** -31;
6398
- const m0 = getBitS2(b, i + 92, 20, i + 120, 12) * 2 ** -31 * GPS_PI;
6399
- const e = getBitU22(b, i + 132, 10, i + 150, 22) * 2 ** -33;
6400
- const cus = getBitS(b, i + 180, 18) * 2 ** -31;
6401
- const crc = getBitS2(b, i + 198, 4, i + 210, 14) * 2 ** -6;
6402
- const crs = getBitS2(b, i + 224, 8, i + 240, 10) * 2 ** -6;
6403
- const sqrtA = getBitU22(b, i + 250, 12, i + 270, 20) * 2 ** -19;
6404
- const toe1 = getBitU(b, i + 290, 2);
6405
- i = 8 * 38 * 2;
6406
- const frn3 = getBitU(b, i + 15, 3);
6407
- const sow3 = getBitU22(b, i + 18, 8, i + 30, 12);
6408
- const toe2 = getBitU22(b, i + 42, 10, i + 60, 5);
6409
- const i0 = getBitS2(b, i + 65, 17, i + 90, 15) * 2 ** -31 * GPS_PI;
6410
- const cic = getBitS2(b, i + 105, 7, i + 120, 11) * 2 ** -31;
6411
- const omegaDot = getBitS2(b, i + 131, 11, i + 150, 13) * 2 ** -43 * GPS_PI;
6412
- const cis = getBitS2(b, i + 163, 9, i + 180, 9) * 2 ** -31;
6413
- const idot = getBitS2(b, i + 189, 13, i + 210, 1) * 2 ** -43 * GPS_PI;
6414
- const omega0 = getBitS2(b, i + 211, 21, i + 240, 11) * 2 ** -31 * GPS_PI;
6415
- const omega = getBitS2(b, i + 251, 11, i + 270, 21) * 2 ** -31 * GPS_PI;
6416
- const toes = (toe1 * 2 ** 15 + toe2) * 8;
6417
- if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3) return null;
6418
- if (sow2 !== sow1 + 6 || sow3 !== sow2 + 6) return null;
6419
- if (tocSec !== toes) return null;
6420
- return buildBdsEphemeris(opts.prn ?? "C00", {
6421
- week,
6422
- sow: sow1,
6423
- toes,
6424
- tocSec,
6425
- svh,
6426
- tgd1,
6427
- af0,
6428
- af1,
6429
- af2,
6430
- crs,
6431
- deltaN,
6432
- m0,
6433
- cuc,
6434
- e,
6435
- cus,
6436
- sqrtA,
6437
- cic,
6438
- omega0,
6439
- cis,
6440
- i0,
6441
- crc,
6442
- omega,
6443
- omegaDot,
6444
- idot
6705
+ function parseSbfAlmanac(data) {
6706
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6707
+ const almanacs = [];
6708
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
6709
+ let alm = null;
6710
+ if (id === 5892 && len >= 60) alm = decodeGpsGalAlm(view, b, "G");
6711
+ else if (id === 4003 && len >= 61) alm = decodeGpsGalAlm(view, b, "E");
6712
+ else if (id === 4119 && len >= 60) alm = decodeBdsAlm(view, b);
6713
+ else if (id === 4005 && len >= 60) alm = decodeGloAlm(view, b);
6714
+ if (alm) almanacs.push(alm);
6445
6715
  });
6716
+ return { almanacs, badCrc };
6446
6717
  }
6447
- function decodeBdsD2Frame(pages, opts = {}) {
6448
- if (pages.length < 10 * BDS_SUBFRAME_BYTES) return null;
6449
- const b = pages;
6450
- let i = 8 * 38 * 0;
6451
- const pgn1 = getBitU(b, i + 42, 4);
6452
- const sow1 = getBitU22(b, i + 18, 8, i + 30, 12);
6453
- const svh = getBitU(b, i + 46, 1);
6454
- const week = getBitU(b, i + 64, 13);
6455
- const tocSec = getBitU22(b, i + 77, 5, i + 90, 12) * 8;
6456
- const tgd1 = getBitS(b, i + 102, 10) * 0.1 * 1e-9;
6457
- i = 8 * 38 * 2;
6458
- const pgn3 = getBitU(b, i + 42, 4);
6459
- const sow3 = getBitU22(b, i + 18, 8, i + 30, 12);
6460
- const af0 = getBitS2(b, i + 100, 12, i + 120, 12) * 2 ** -33;
6461
- const af1p3 = getBitS(b, i + 132, 4);
6462
- i = 8 * 38 * 3;
6463
- const pgn4 = getBitU(b, i + 42, 4);
6464
- const sow4 = getBitU22(b, i + 18, 8, i + 30, 12);
6465
- const af1p4 = getBitU22(b, i + 46, 6, i + 60, 12);
6466
- const af2 = getBitS2(b, i + 72, 10, i + 90, 1) * 2 ** -66;
6467
- const deltaN = getBitS(b, i + 96, 16) * 2 ** -43 * GPS_PI;
6468
- const cucp4 = getBitS(b, i + 120, 14);
6469
- i = 8 * 38 * 4;
6470
- const pgn5 = getBitU(b, i + 42, 4);
6471
- const sow5 = getBitU22(b, i + 18, 8, i + 30, 12);
6472
- const cucp5 = getBitU(b, i + 46, 4);
6473
- const m0 = getBitS3(b, i + 50, 2, i + 60, 22, i + 90, 8) * 2 ** -31 * GPS_PI;
6474
- const cus = getBitS2(b, i + 98, 14, i + 120, 4) * 2 ** -31;
6475
- const ep5 = getBitS(b, i + 124, 10);
6476
- i = 8 * 38 * 5;
6477
- const pgn6 = getBitU(b, i + 42, 4);
6478
- const sow6 = getBitU22(b, i + 18, 8, i + 30, 12);
6479
- const ep6 = getBitU22(b, i + 46, 6, i + 60, 16);
6480
- const sqrtA = getBitU3(b, i + 76, 6, i + 90, 22, i + 120, 4) * 2 ** -19;
6481
- const cicp6 = getBitS(b, i + 124, 10);
6482
- i = 8 * 38 * 6;
6483
- const pgn7 = getBitU(b, i + 42, 4);
6484
- const sow7 = getBitU22(b, i + 18, 8, i + 30, 12);
6485
- const cicp7 = getBitU22(b, i + 46, 6, i + 60, 2);
6486
- const cis = getBitS(b, i + 62, 18) * 2 ** -31;
6487
- const toes = getBitU22(b, i + 80, 2, i + 90, 15) * 8;
6488
- const i0p7 = getBitS2(b, i + 105, 7, i + 120, 14);
6489
- i = 8 * 38 * 7;
6490
- const pgn8 = getBitU(b, i + 42, 4);
6491
- const sow8 = getBitU22(b, i + 18, 8, i + 30, 12);
6492
- const i0p8 = getBitU22(b, i + 46, 6, i + 60, 5);
6493
- const crc = getBitS2(b, i + 65, 17, i + 90, 1) * 2 ** -6;
6494
- const crs = getBitS(b, i + 91, 18) * 2 ** -6;
6495
- const omegaDotP8 = getBitS2(b, i + 109, 3, i + 120, 16);
6496
- i = 8 * 38 * 8;
6497
- const pgn9 = getBitU(b, i + 42, 4);
6498
- const sow9 = getBitU22(b, i + 18, 8, i + 30, 12);
6499
- const omegaDotP9 = getBitU(b, i + 46, 5);
6500
- const omega0 = getBitS3(b, i + 51, 1, i + 60, 22, i + 90, 9) * 2 ** -31 * GPS_PI;
6501
- const omegaP9 = getBitS2(b, i + 99, 13, i + 120, 14);
6502
- i = 8 * 38 * 9;
6503
- const pgn10 = getBitU(b, i + 42, 4);
6504
- const sow10 = getBitU22(b, i + 18, 8, i + 30, 12);
6505
- const omegaP10 = getBitU(b, i + 46, 5);
6506
- const idot = getBitS2(b, i + 51, 1, i + 60, 13) * 2 ** -43 * GPS_PI;
6507
- if (pgn1 !== 1 || pgn3 !== 3 || pgn4 !== 4 || pgn5 !== 5 || pgn6 !== 6 || pgn7 !== 7 || pgn8 !== 8 || pgn9 !== 9 || pgn10 !== 10) {
6508
- return null;
6509
- }
6510
- if (sow3 !== sow1 + 6 || sow4 !== sow3 + 3 || sow5 !== sow4 + 3 || sow6 !== sow5 + 3 || sow7 !== sow6 + 3 || sow8 !== sow7 + 3 || sow9 !== sow8 + 3 || sow10 !== sow9 + 3) {
6511
- return null;
6512
- }
6513
- if (tocSec !== toes) return null;
6514
- return buildBdsEphemeris(opts.prn ?? "C00", {
6515
- week,
6516
- sow: sow1,
6517
- toes,
6518
- tocSec,
6519
- svh,
6520
- tgd1,
6521
- af0,
6522
- af1: mergeS(af1p3, af1p4, 18) * 2 ** -50,
6523
- af2,
6524
- crs,
6525
- deltaN,
6526
- m0,
6527
- cuc: mergeS(cucp4, cucp5, 4) * 2 ** -31,
6528
- e: mergeS(ep5, ep6, 22) * 2 ** -33,
6529
- cus,
6530
- sqrtA,
6531
- cic: mergeS(cicp6, cicp7, 8) * 2 ** -31,
6532
- omega0,
6533
- cis,
6534
- i0: mergeS(i0p7, i0p8, 11) * 2 ** -31 * GPS_PI,
6535
- crc,
6536
- omega: mergeS(omegaP9, omegaP10, 5) * 2 ** -31 * GPS_PI,
6537
- omegaDot: mergeS(omegaDotP8, omegaDotP9, 5) * 2 ** -43 * GPS_PI,
6538
- idot
6718
+
6719
+ // src/sbf/iono.ts
6720
+ var F4_DNU2 = -2e10;
6721
+ function parseSbfIonoUtc(data) {
6722
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6723
+ const ionoCorrections = {};
6724
+ let leapSeconds = null;
6725
+ const f4s = (b, n) => {
6726
+ const out = [];
6727
+ for (let k = 0; k < n; k++) {
6728
+ const v = view.getFloat32(b + 4 * k, true);
6729
+ if (v === F4_DNU2) return null;
6730
+ out.push(v);
6731
+ }
6732
+ return out;
6733
+ };
6734
+ scanSbfFrames(data, view, (id, b, len) => {
6735
+ if ((id === 5893 || id === 4120) && len >= 48) {
6736
+ const alpha = f4s(b + 16, 4);
6737
+ const beta = f4s(b + 32, 4);
6738
+ if (!alpha || !beta) return;
6739
+ const sys = id === 5893 ? "GPS" : "BDS";
6740
+ ionoCorrections[`${sys}A`] = alpha;
6741
+ ionoCorrections[`${sys}B`] = beta;
6742
+ } else if (id === 4030 && len >= 28) {
6743
+ const ai = f4s(b + 16, 3);
6744
+ if (ai) ionoCorrections["GAL"] = ai;
6745
+ } else if (id === 5894 && len >= 37) {
6746
+ leapSeconds = view.getInt8(b + 33);
6747
+ }
6748
+ });
6749
+ return { ionoCorrections, leapSeconds };
6750
+ }
6751
+
6752
+ // src/sbf/rawnav.ts
6753
+ var SIGNAL_OF_BLOCK = {
6754
+ 4018: "L2C",
6755
+ 4019: "L5"
6756
+ };
6757
+ function parseSbfCnav(data) {
6758
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6759
+ const ephemerides = [];
6760
+ const assemblers = {
6761
+ L2C: new CnavAssembler(),
6762
+ L5: new CnavAssembler()
6763
+ };
6764
+ let badCrc = 0;
6765
+ let messages = 0;
6766
+ scanSbfFrames(data, view, (id, b, len) => {
6767
+ const signal = SIGNAL_OF_BLOCK[id];
6768
+ if (!signal || len < 60) return;
6769
+ messages++;
6770
+ const msg = new Uint8Array(40);
6771
+ for (let k = 0; k < 10; k++) {
6772
+ const w = view.getUint32(b + 20 + 4 * k, true);
6773
+ msg[4 * k] = w >>> 24;
6774
+ msg[4 * k + 1] = w >>> 16 & 255;
6775
+ msg[4 * k + 2] = w >>> 8 & 255;
6776
+ msg[4 * k + 3] = w & 255;
6777
+ }
6778
+ if (!cnavCrcOk(msg)) {
6779
+ badCrc++;
6780
+ return;
6781
+ }
6782
+ const eph = assemblers[signal].push(msg);
6783
+ if (eph) ephemerides.push({ ...eph, signal });
6539
6784
  });
6785
+ return { ephemerides, badCrc, messages };
6540
6786
  }
6541
- function isBdsGeoPrn(prn) {
6542
- return prn < 6 || prn > 58;
6543
- }
6544
- var BdsAssembler = class {
6545
- sats = /* @__PURE__ */ new Map();
6546
- /**
6547
- * Push one 300-bit subframe (38+ bytes, bit 0 = first bit of the
6548
- * preamble) for the satellite `prn` ("C06"). Returns the newly
6549
- * completed ephemeris, or null.
6550
- */
6551
- push(prn, subframe) {
6552
- if (subframe.length < BDS_SUBFRAME_BYTES) return null;
6553
- const num = parseInt(prn.slice(1), 10);
6554
- if (!Number.isFinite(num) || num < 1 || num > 63) return null;
6555
- const id = getBitU(subframe, 15, 3);
6556
- if (id < 1 || id > 5) return null;
6557
- let sat = this.sats.get(prn);
6558
- if (!sat) {
6559
- sat = { buf: new Uint8Array(10 * BDS_SUBFRAME_BYTES) };
6560
- this.sats.set(prn, sat);
6787
+
6788
+ // src/sbf/rawnav-gal.ts
6789
+ var INAV_SOURCES = [17, 21, 22];
6790
+ var FNAV_SOURCES = [20, 22];
6791
+ function parseSbfGalNav(data) {
6792
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6793
+ const ephemerides = [];
6794
+ const inav = new GalInavAssembler();
6795
+ const fnav = new GalFnavAssembler();
6796
+ let badCrc = 0;
6797
+ let messages = 0;
6798
+ scanSbfFrames(data, view, (id, b, len) => {
6799
+ if (id !== 4022 && id !== 4023 || len < 52) return;
6800
+ messages++;
6801
+ const prnStr = svidToPrn(data[b + 14]);
6802
+ if (!prnStr || prnStr[0] !== "E") return;
6803
+ const prn = parseInt(prnStr.slice(1), 10);
6804
+ const source = data[b + 17] & 31;
6805
+ const page = new Uint8Array(32);
6806
+ for (let k = 0; k < 8; k++) {
6807
+ const w = view.getUint32(b + 20 + 4 * k, true);
6808
+ page[4 * k] = w >>> 24;
6809
+ page[4 * k + 1] = w >>> 16 & 255;
6810
+ page[4 * k + 2] = w >>> 8 & 255;
6811
+ page[4 * k + 3] = w & 255;
6561
6812
  }
6562
- let eph = null;
6563
- if (!isBdsGeoPrn(num)) {
6564
- sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (id - 1) * 38);
6565
- if (id === 3) eph = decodeBdsD1Frame(sat.buf, { prn });
6813
+ let eph;
6814
+ let src;
6815
+ if (id === 4023) {
6816
+ src = "inav";
6817
+ if (!INAV_SOURCES.includes(source)) return;
6818
+ if (getBitU22(page, 0) !== 0 || getBitU22(page, 114) !== 1) return;
6819
+ if (!galInavPageCrcOk(page)) {
6820
+ badCrc++;
6821
+ return;
6822
+ }
6823
+ eph = inav.push(prn, page);
6566
6824
  } else {
6567
- if (id !== 1) return null;
6568
- const pgn = getBitU(subframe, 42, 4);
6569
- if (pgn < 1 || pgn > 10) return null;
6570
- sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (pgn - 1) * 38);
6571
- if (pgn === 10) eph = decodeBdsD2Frame(sat.buf, { prn });
6825
+ src = "fnav";
6826
+ if (!FNAV_SOURCES.includes(source)) return;
6827
+ if (isFnavDummy(page)) return;
6828
+ if (!galFnavPageCrcOk(page)) {
6829
+ badCrc++;
6830
+ return;
6831
+ }
6832
+ eph = fnav.push(prn, page);
6572
6833
  }
6573
- if (!eph) return null;
6574
- const key = `${eph.week}:${eph.toe}`;
6575
- if (key === sat.lastKey) return null;
6576
- sat.lastKey = key;
6577
- return eph;
6578
- }
6579
- };
6580
-
6581
- // src/navbits/glo.ts
6582
- var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6583
- var SEC_PER_WEEK7 = 7 * 86400;
6584
- var SEC_PER_DAY = 86400;
6585
- var GLO_STRING_BYTES = 10;
6586
- function getBitG(b, pos, len) {
6587
- const value = getBitU(b, pos + 1, len - 1);
6588
- return getBitU(b, pos, 1) && value !== 0 ? -value : value;
6834
+ if (eph) ephemerides.push({ ...eph, source: src });
6835
+ });
6836
+ return { ephemerides, badCrc, messages };
6589
6837
  }
6590
- var HAMMING_MASKS = [
6591
- [85, 85, 90, 170, 170, 170, 181, 85, 106, 216, 8],
6592
- [102, 102, 108, 204, 204, 204, 217, 153, 179, 104, 16],
6593
- [135, 135, 143, 15, 15, 15, 30, 30, 60, 112, 32],
6594
- [7, 248, 15, 240, 15, 240, 31, 224, 63, 128, 64],
6595
- [248, 0, 15, 255, 240, 0, 31, 255, 192, 0, 128],
6596
- [0, 0, 15, 255, 255, 255, 224, 0, 0, 1, 0],
6597
- [255, 255, 240, 0, 0, 0, 0, 0, 0, 2, 0],
6598
- [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248]
6599
- ];
6600
- function testGloString(buff) {
6601
- if (buff.length < 11) return false;
6602
- let n = 0;
6603
- let cs = 0;
6604
- for (const mask of HAMMING_MASKS) {
6605
- cs = 0;
6606
- for (let j = 0; j < 11; j++) {
6607
- let x = buff[j] & mask[j];
6608
- x ^= x >> 4;
6609
- x ^= x >> 2;
6610
- x ^= x >> 1;
6611
- cs ^= x & 1;
6612
- }
6613
- if (cs) n++;
6614
- }
6615
- return n === 0 || n === 2 && cs === 1;
6838
+ function getBitU22(buff, pos) {
6839
+ return buff[pos >> 3] >> 7 - (pos & 7) & 1;
6616
6840
  }
6617
- function decodeGloStrings(strings, refDate, opts = {}) {
6618
- if (strings.length < 4 * GLO_STRING_BYTES) return null;
6619
- const b = strings;
6620
- let i = 1;
6621
- const frn1 = getBitU(b, i, 4);
6622
- i += 4 + 2;
6623
- i += 2;
6624
- const tkH = getBitU(b, i, 5);
6625
- i += 5;
6626
- const tkM = getBitU(b, i, 6);
6627
- i += 6;
6628
- const tkS = getBitU(b, i, 1) * 30;
6629
- i += 1;
6630
- const xDot = getBitG(b, i, 24) * 2 ** -20;
6631
- i += 24;
6632
- const xAcc = getBitG(b, i, 5) * 2 ** -30;
6633
- i += 5;
6634
- const x = getBitG(b, i, 27) * 2 ** -11;
6635
- i = 80 + 1;
6636
- const frn2 = getBitU(b, i, 4);
6637
- i += 4;
6638
- const bn = getBitU(b, i, 1);
6639
- i += 1 + 2;
6640
- i += 1;
6641
- const tb = getBitU(b, i, 7);
6642
- i += 7 + 5;
6643
- const yDot = getBitG(b, i, 24) * 2 ** -20;
6644
- i += 24;
6645
- const yAcc = getBitG(b, i, 5) * 2 ** -30;
6646
- i += 5;
6647
- const y = getBitG(b, i, 27) * 2 ** -11;
6648
- i = 160 + 1;
6649
- const frn3 = getBitU(b, i, 4);
6650
- i += 4;
6651
- i += 1;
6652
- const gammaN = getBitG(b, i, 11) * 2 ** -40;
6653
- i += 11 + 1;
6654
- i += 2;
6655
- i += 1;
6656
- const zDot = getBitG(b, i, 24) * 2 ** -20;
6657
- i += 24;
6658
- const zAcc = getBitG(b, i, 5) * 2 ** -30;
6659
- i += 5;
6660
- const z = getBitG(b, i, 27) * 2 ** -11;
6661
- i = 240 + 1;
6662
- const frn4 = getBitU(b, i, 4);
6663
- i += 4;
6664
- const tauN = getBitG(b, i, 22) * 2 ** -30;
6665
- i += 22;
6666
- i += 5;
6667
- i += 5;
6668
- i += 14;
6669
- i += 1;
6670
- i += 4;
6671
- i += 3;
6672
- i += 11;
6673
- const slot = getBitU(b, i, 5);
6674
- if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3 || frn4 !== 4) return null;
6675
- if (slot < 1 || slot > 27) return null;
6676
- const utcSec = (getUtcDate(refDate).getTime() - GPS_EPOCH_MS7) / 1e3;
6677
- const week = Math.floor(utcSec / SEC_PER_WEEK7);
6678
- let tow = utcSec - week * SEC_PER_WEEK7;
6679
- const tod = tow % SEC_PER_DAY;
6680
- tow -= tod;
6681
- let tof = tkH * 3600 + tkM * 60 + tkS - 10800;
6682
- if (tof < tod - 43200) tof += SEC_PER_DAY;
6683
- else if (tof > tod + 43200) tof -= SEC_PER_DAY;
6684
- let toe = tb * 900 - 10800;
6685
- if (toe < tod - 43200) toe += SEC_PER_DAY;
6686
- else if (toe > tod + 43200) toe -= SEC_PER_DAY;
6687
- return {
6688
- system: "R",
6689
- prn: `R${String(slot).padStart(2, "0")}`,
6690
- // RINEX GLONASS epochs are UTC: build the UTC toe Date directly.
6691
- tocDate: new Date(GPS_EPOCH_MS7 + (week * SEC_PER_WEEK7 + tow + toe) * 1e3),
6692
- tauN: -tauN,
6693
- // RINEX stores −τn; the SIS carries τn (ICD sign)
6694
- gammaN,
6695
- // v3 message frame time: seconds of the UTC week (RTKLIB tof).
6696
- messageFrameTime: ((tow + tof) % SEC_PER_WEEK7 + SEC_PER_WEEK7) % SEC_PER_WEEK7,
6697
- x,
6698
- xDot,
6699
- xAcc,
6700
- y,
6701
- yDot,
6702
- yAcc,
6703
- z,
6704
- zDot,
6705
- zAcc,
6706
- // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
6707
- health: bn,
6708
- freqNum: opts.freqNum ?? 0
6709
- };
6841
+ function isFnavDummy(page) {
6842
+ return page[0] >> 2 === 63;
6710
6843
  }
6711
- var GloStringAssembler = class {
6712
- sats = /* @__PURE__ */ new Map();
6713
- /**
6714
- * Push one navigation string (10+ bytes, bit 0 = string bit 85) for
6715
- * the satellite `prn` ("R09"), received at the GPS-scale `time`.
6716
- * Returns the newly completed ephemeris, or null. The decoded
6717
- * string-4 slot number must match `prn`, or nothing is emitted.
6718
- */
6719
- push(prn, str, time, freqNum = 0) {
6720
- if (str.length < GLO_STRING_BYTES) return null;
6721
- const m = getBitU(str, 1, 4);
6722
- if (m < 1) return null;
6723
- const sec = Math.floor(time.getTime() / 1e3);
6724
- let sat = this.sats.get(prn);
6725
- if (!sat) {
6726
- sat = { buf: new Uint8Array(4 * GLO_STRING_BYTES), batchSec: sec };
6727
- this.sats.set(prn, sat);
6728
- } else if (Math.abs(sec - sat.batchSec) > 30) {
6729
- sat.buf.fill(0);
6730
- sat.batchSec = sec;
6731
- }
6732
- if (m > 4) return null;
6733
- sat.buf.set(str.subarray(0, GLO_STRING_BYTES), (m - 1) * 10);
6734
- if (m !== 4) return null;
6735
- const eph = decodeGloStrings(sat.buf, time, { freqNum });
6736
- if (!eph || eph.prn !== prn) return null;
6737
- const key = `${eph.tocDate.getTime()}:${eph.health}`;
6738
- if (key === sat.lastKey) return null;
6739
- sat.lastKey = key;
6740
- return eph;
6741
- }
6742
- };
6743
6844
 
6744
6845
  // src/sbf/rawnav-bds.ts
6745
- var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
6846
+ var GPS_EPOCH_MS9 = Date.UTC(1980, 0, 6);
6746
6847
  var MS_PER_WEEK5 = 7 * 86400 * 1e3;
6747
6848
  var TOW_DNU = 4294967295;
6748
6849
  var WNC_DNU = 65535;
@@ -6812,7 +6913,7 @@ function parseSbfGloNav(data) {
6812
6913
  const eph = assembler.push(
6813
6914
  prn,
6814
6915
  str,
6815
- new Date(GPS_EPOCH_MS8 + wnc * MS_PER_WEEK5 + towMs),
6916
+ new Date(GPS_EPOCH_MS9 + wnc * MS_PER_WEEK5 + towMs),
6816
6917
  view.getUint8(b + 18) - 8
6817
6918
  // FreqNr, offset by 8
6818
6919
  );
@@ -7346,7 +7447,7 @@ function parseSbfHas(data) {
7346
7447
 
7347
7448
  // src/sbf/index.ts
7348
7449
  var CLIGHT = 299792458;
7349
- var GPS_EPOCH_MS9 = Date.UTC(1980, 0, 6);
7450
+ var GPS_EPOCH_MS10 = Date.UTC(1980, 0, 6);
7350
7451
  var MS_PER_WEEK6 = 7 * 864e5;
7351
7452
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
7352
7453
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -7446,23 +7547,23 @@ var MEAS2_SIG = [
7446
7547
  ["J", "1E"],
7447
7548
  ["J", "5P"]
7448
7549
  ];
7449
- var two3 = (n) => String(n).padStart(2, "0");
7550
+ var two4 = (n) => String(n).padStart(2, "0");
7450
7551
  function m3Prn(navsys, svid) {
7451
7552
  switch (navsys) {
7452
7553
  case 0:
7453
- return svid < 32 ? `G${two3(svid + 1)}` : null;
7554
+ return svid < 32 ? `G${two4(svid + 1)}` : null;
7454
7555
  case 1:
7455
- return svid < 27 ? `R${two3(svid + 1)}` : null;
7556
+ return svid < 27 ? `R${two4(svid + 1)}` : null;
7456
7557
  case 2:
7457
- return svid < 36 ? `E${two3(svid + 1)}` : null;
7558
+ return svid < 36 ? `E${two4(svid + 1)}` : null;
7458
7559
  case 3:
7459
- return svid < 50 ? `C${two3(svid + 1)}` : null;
7560
+ return svid < 50 ? `C${two4(svid + 1)}` : null;
7460
7561
  case 4:
7461
- return svid <= 38 ? `S${two3(svid + 20)}` : null;
7562
+ return svid <= 38 ? `S${two4(svid + 20)}` : null;
7462
7563
  case 5:
7463
- return svid < 10 ? `J${two3(svid + 1)}` : null;
7564
+ return svid < 10 ? `J${two4(svid + 1)}` : null;
7464
7565
  case 6:
7465
- return svid < 14 ? `I${two3(svid + 1)}` : null;
7566
+ return svid < 14 ? `I${two4(svid + 1)}` : null;
7466
7567
  default:
7467
7568
  return null;
7468
7569
  }
@@ -8000,7 +8101,7 @@ function parseSbfMeas(data) {
8000
8101
  const tow = view.getUint32(i + 8, true);
8001
8102
  const wnc = view.getUint16(i + 12, true);
8002
8103
  if (tow !== 4294967295 && wnc !== 65535) {
8003
- ensureEpoch(GPS_EPOCH_MS9 + wnc * MS_PER_WEEK6 + tow);
8104
+ ensureEpoch(GPS_EPOCH_MS10 + wnc * MS_PER_WEEK6 + tow);
8004
8105
  if (id === 4109) decodeMeas3Ranges(i, tow);
8005
8106
  else if (id === 4110) decodeMeas3CN0(i, len);
8006
8107
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -8065,15 +8166,15 @@ var ID_GALEPHEMERIS = 1122;
8065
8166
  var ID_QZSSEPHEMERIS = 1336;
8066
8167
  var ID_GPSEPHEM = 7;
8067
8168
  var ID_BDSEPHEMERIS = 1696;
8068
- var GPS_EPOCH_MS10 = Date.UTC(1980, 0, 6);
8169
+ var GPS_EPOCH_MS11 = Date.UTC(1980, 0, 6);
8069
8170
  var BDT_EPOCH_MS3 = Date.UTC(2006, 0, 1);
8070
- var SEC_PER_WEEK8 = 7 * 86400;
8171
+ var SEC_PER_WEEK9 = 7 * 86400;
8071
8172
  var SEC_PER_DAY2 = 86400;
8072
- var MS_PER_WEEK7 = SEC_PER_WEEK8 * 1e3;
8073
- var gpsMs2 = (week, sec) => GPS_EPOCH_MS10 + week * MS_PER_WEEK7 + sec * 1e3;
8074
- var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK8;
8173
+ var MS_PER_WEEK7 = SEC_PER_WEEK9 * 1e3;
8174
+ var gpsMs2 = (week, sec) => GPS_EPOCH_MS11 + week * MS_PER_WEEK7 + sec * 1e3;
8175
+ var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK9;
8075
8176
  function nearWeekMs(refMs, sow) {
8076
- const refWeek = Math.floor((refMs - GPS_EPOCH_MS10) / MS_PER_WEEK7);
8177
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS11) / MS_PER_WEEK7);
8077
8178
  let ms = gpsMs2(refWeek, sow);
8078
8179
  if (ms < refMs - MS_PER_WEEK7 / 2) ms += MS_PER_WEEK7;
8079
8180
  else if (ms > refMs + MS_PER_WEEK7 / 2) ms -= MS_PER_WEEK7;
@@ -8100,9 +8201,9 @@ function decodeGloEphemeris(view, p) {
8100
8201
  tof += Math.floor(tow / SEC_PER_DAY2) * SEC_PER_DAY2;
8101
8202
  if (tof < tow - 43200) tof += SEC_PER_DAY2;
8102
8203
  else if (tof > tow + 43200) tof -= SEC_PER_DAY2;
8103
- const toeGpsMs = GPS_EPOCH_MS10 + (week * SEC_PER_WEEK8 + tow) * 1e3;
8104
- const tofGpsMs = GPS_EPOCH_MS10 + (week * SEC_PER_WEEK8 + tof) * 1e3;
8105
- const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS10) / 1e3;
8204
+ const toeGpsMs = GPS_EPOCH_MS11 + (week * SEC_PER_WEEK9 + tow) * 1e3;
8205
+ const tofGpsMs = GPS_EPOCH_MS11 + (week * SEC_PER_WEEK9 + tof) * 1e3;
8206
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS11) / 1e3;
8106
8207
  return {
8107
8208
  system: "R",
8108
8209
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -8110,7 +8211,7 @@ function decodeGloEphemeris(view, p) {
8110
8211
  tauN: -view.getFloat64(p + 100, true),
8111
8212
  // RINEX stores −τ_n
8112
8213
  gammaN: view.getFloat64(p + 116, true),
8113
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK8 + SEC_PER_WEEK8) % SEC_PER_WEEK8,
8214
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK9 + SEC_PER_WEEK9) % SEC_PER_WEEK9,
8114
8215
  x: view.getFloat64(p + 28, true) / 1e3,
8115
8216
  y: view.getFloat64(p + 36, true) / 1e3,
8116
8217
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -8167,7 +8268,7 @@ function decodeGalEphemeris(view, p, headerMs) {
8167
8268
  omega: view.getFloat64(p + 60, true),
8168
8269
  omegaDot: view.getFloat64(p + 140, true),
8169
8270
  idot: view.getFloat64(p + 124, true),
8170
- week: Math.floor((toeMs - GPS_EPOCH_MS10) / MS_PER_WEEK7),
8271
+ week: Math.floor((toeMs - GPS_EPOCH_MS11) / MS_PER_WEEK7),
8171
8272
  // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
8172
8273
  // 3-5, E5b in 6-8) — same packing as RTKLIB.
8173
8274
  svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
@@ -8339,7 +8440,7 @@ function parseNovatelNav(data) {
8339
8440
  }
8340
8441
 
8341
8442
  // src/novatel/index.ts
8342
- var GPS_EPOCH_MS11 = Date.UTC(1980, 0, 6);
8443
+ var GPS_EPOCH_MS12 = Date.UTC(1980, 0, 6);
8343
8444
  var MS_PER_WEEK8 = 7 * 864e5;
8344
8445
  var HDR = 28;
8345
8446
  var ID_RANGE = 43;
@@ -8509,7 +8610,7 @@ function parseNovatelRange(data) {
8509
8610
  if (!codes.includes(code)) codes.push(code);
8510
8611
  }
8511
8612
  epochs.push({
8512
- timeMs: GPS_EPOCH_MS11 + week * MS_PER_WEEK8 + towMs,
8613
+ timeMs: GPS_EPOCH_MS12 + week * MS_PER_WEEK8 + towMs,
8513
8614
  meas
8514
8615
  });
8515
8616
  }
@@ -8561,7 +8662,7 @@ function parseNovatelRange(data) {
8561
8662
  if (!codes.includes(code)) codes.push(code);
8562
8663
  }
8563
8664
  epochs.push({
8564
- timeMs: GPS_EPOCH_MS11 + week * MS_PER_WEEK8 + towMs,
8665
+ timeMs: GPS_EPOCH_MS12 + week * MS_PER_WEEK8 + towMs,
8565
8666
  meas
8566
8667
  });
8567
8668
  }
@@ -8575,7 +8676,7 @@ function parseNovatelRange(data) {
8575
8676
  var GM_GPS = 3986005e8;
8576
8677
  var GM_GAL = 3986004418e5;
8577
8678
  var GM_BDS = 3986004418e5;
8578
- var GPS_EPOCH_MS12 = Date.UTC(1980, 0, 6);
8679
+ var GPS_EPOCH_MS13 = Date.UTC(1980, 0, 6);
8579
8680
  var BDT_EPOCH_MS4 = Date.UTC(2006, 0, 1);
8580
8681
  var WEEK_MS = 6048e5;
8581
8682
  var BDT_MINUS_GPST_MS = 14e3;
@@ -8584,7 +8685,7 @@ function almanacEpochMs(alm) {
8584
8685
  if (alm.system === "C") {
8585
8686
  return BDT_EPOCH_MS4 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3 + BDT_MINUS_GPST_MS;
8586
8687
  }
8587
- return GPS_EPOCH_MS12 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
8688
+ return GPS_EPOCH_MS13 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
8588
8689
  }
8589
8690
  function almanacSatPosition(alm, timeMs) {
8590
8691
  const GM = alm.system === "E" ? GM_GAL : alm.system === "C" ? GM_BDS : GM_GPS;
@@ -9094,10 +9195,10 @@ function selectBest(ephs, timeMs) {
9094
9195
  }
9095
9196
  return best;
9096
9197
  }
9097
- var GPS_EPOCH_MS13 = START_GPS_TIME.getTime();
9198
+ var GPS_EPOCH_MS14 = START_GPS_TIME.getTime();
9098
9199
  var MS_PER_WEEK9 = 7 * 864e5;
9099
9200
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
9100
- var GAL_EPOCH_MS = GPS_EPOCH_MS13 + 1024 * MS_PER_WEEK9;
9201
+ var GAL_EPOCH_MS = GPS_EPOCH_MS14 + 1024 * MS_PER_WEEK9;
9101
9202
  function ephInfoToEphemeris(info) {
9102
9203
  const sys = info.prn.charAt(0);
9103
9204
  if (sys === "R") {
@@ -9148,8 +9249,8 @@ function ephInfoToEphemeris(info) {
9148
9249
  } else if (sys === "E") {
9149
9250
  epochMs = GAL_EPOCH_MS;
9150
9251
  } else {
9151
- epochMs = GPS_EPOCH_MS13;
9152
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS13) / MS_PER_WEEK9;
9252
+ epochMs = GPS_EPOCH_MS14;
9253
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS14) / MS_PER_WEEK9;
9153
9254
  week += 1024 * Math.round((weeksNow - week) / 1024);
9154
9255
  }
9155
9256
  const tocDate = new Date(epochMs + week * MS_PER_WEEK9 + tocSec * 1e3);
@@ -12270,6 +12371,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12270
12371
  parseUbxCnav,
12271
12372
  parseUbxIonoUtc,
12272
12373
  parseUbxNav,
12374
+ parseUbxRawNav,
12273
12375
  parseUbxRawx,
12274
12376
  phiAltBOC,
12275
12377
  phiBOCc,