gnss-js 1.21.0 → 1.23.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
@@ -82,6 +82,7 @@ __export(src_exports, {
82
82
  RINEX_CODES: () => RINEX_CODES,
83
83
  RTCM3_MESSAGE_NAMES: () => RTCM3_MESSAGE_NAMES,
84
84
  Rtcm3Decoder: () => Rtcm3Decoder,
85
+ RtkFloatEngine: () => RtkFloatEngine,
85
86
  SECONDS_IN_DAY: () => SECONDS_IN_DAY,
86
87
  SECONDS_IN_HOUR: () => SECONDS_IN_HOUR,
87
88
  SECONDS_IN_MINUTE: () => SECONDS_IN_MINUTE,
@@ -241,6 +242,7 @@ __export(src_exports, {
241
242
  parseUbxCnav: () => parseUbxCnav,
242
243
  parseUbxIonoUtc: () => parseUbxIonoUtc,
243
244
  parseUbxNav: () => parseUbxNav,
245
+ parseUbxRawNav: () => parseUbxRawNav,
244
246
  parseUbxRawx: () => parseUbxRawx,
245
247
  phiAltBOC: () => phiAltBOC,
246
248
  phiBOCc: () => phiBOCc,
@@ -259,11 +261,13 @@ __export(src_exports, {
259
261
  selectEphemeris: () => selectEphemeris,
260
262
  setGloFreqNumber: () => setGloFreqNumber,
261
263
  setRtcm3DebugHandler: () => setRtcm3DebugHandler,
264
+ solveDgnss: () => solveDgnss,
262
265
  solveSpp: () => solveSpp,
263
266
  sp3Position: () => sp3Position,
264
267
  stationHeaderLines: () => stationHeaderLines,
265
268
  systemCmp: () => systemCmp,
266
269
  systemName: () => systemName,
270
+ toRtkEpoch: () => toRtkEpoch,
267
271
  transformFrame: () => transformFrame,
268
272
  ubxFrames: () => ubxFrames,
269
273
  updateStationMeta: () => updateStationMeta,
@@ -5404,541 +5408,288 @@ function parseUbxCnav(data) {
5404
5408
  return { ephemerides, badCrc, messages };
5405
5409
  }
5406
5410
 
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
- }
5411
+ // src/navbits/bds.ts
5412
+ var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
5413
+ var SEC_PER_WEEK4 = 7 * 86400;
5414
+ var MS_PER_WEEK2 = SEC_PER_WEEK4 * 1e3;
5415
+ var HALF_WEEK2 = 302400;
5416
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
5417
+ var BDS_SUBFRAME_BYTES = 38;
5418
+ function getBitU2(b, p1, l1, p2, l2) {
5419
+ return getBitU(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
5442
5420
  }
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 };
5421
+ function getBitS2(b, p1, l1, p2, l2) {
5422
+ return getBitS(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
5498
5423
  }
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;
5424
+ function getBitU3(b, p1, l1, p2, l2, p3, l3) {
5425
+ return getBitU(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
5516
5426
  }
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;
5427
+ function getBitS3(b, p1, l1, p2, l2, p3, l3) {
5428
+ return getBitS(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
5540
5429
  }
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;
5430
+ var mergeS = (hi, lo, n) => hi * 2 ** n + lo;
5431
+ function bchOk(cw) {
5432
+ for (let i = 14; i >= 4; i--) {
5433
+ if (cw & 1 << i) cw ^= 19 << i - 4;
5434
+ }
5435
+ return cw === 0;
5558
5436
  }
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;
5437
+ function bdsSubframeParityOk(subframe) {
5438
+ if (subframe.length < BDS_SUBFRAME_BYTES) return false;
5439
+ if (!bchOk(getBitU(subframe, 15, 15))) return false;
5440
+ for (let w = 1; w < 10; w++) {
5441
+ const base = 30 * w;
5442
+ const cw1 = getBitU(subframe, base, 11) * 16 + getBitU(subframe, base + 22, 4);
5443
+ const cw2 = getBitU(subframe, base + 11, 11) * 16 + getBitU(subframe, base + 26, 4);
5444
+ if (!bchOk(cw1) || !bchOk(cw2)) return false;
5445
+ }
5446
+ return true;
5573
5447
  }
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));
5448
+ function buildBdsEphemeris(prn, f) {
5449
+ let week = f.week;
5450
+ if (f.toes < f.sow - HALF_WEEK2) week++;
5451
+ else if (f.toes > f.sow + HALF_WEEK2) week--;
5452
+ const tocDate = new Date(BDT_EPOCH_MS + week * MS_PER_WEEK2 + f.tocSec * 1e3);
5587
5453
  return {
5588
- system: sys,
5589
- prn: `${sys}${String(prn).padStart(2, "0")}`,
5454
+ system: "C",
5455
+ prn,
5590
5456
  toc: sowOf(tocDate.getTime()),
5591
5457
  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
5458
+ af0: f.af0,
5459
+ af1: f.af1,
5460
+ af2: f.af2,
5461
+ // AODE is not in the ephemeris subframes; RTKLIB derives the RINEX
5462
+ // IODE/AODE slot from toc per the BDS ICD update schedule.
5463
+ iode: Math.floor(f.tocSec / 720) % 240,
5464
+ crs: f.crs,
5465
+ deltaN: f.deltaN,
5466
+ m0: f.m0,
5467
+ cuc: f.cuc,
5468
+ e: f.e,
5469
+ cus: f.cus,
5470
+ sqrtA: f.sqrtA,
5471
+ toe: f.toes,
5472
+ cic: f.cic,
5473
+ omega0: f.omega0,
5474
+ cis: f.cis,
5475
+ i0: f.i0,
5476
+ crc: f.crc,
5477
+ omega: f.omega,
5478
+ omegaDot: f.omegaDot,
5479
+ idot: f.idot,
5480
+ week,
5481
+ // RINEX BDS week field is the BDT week of toe
5482
+ svHealth: f.svh,
5483
+ // SatH1
5484
+ tgd: f.tgd1
5485
+ // TGD1 (B1) — RINEX slot
5618
5486
  };
5619
5487
  }
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);
5636
- return {
5637
- system: "E",
5638
- prn: `E${String(prn).padStart(2, "0")}`,
5639
- toc: sowOf(tocDate.getTime()),
5640
- 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),
5653
- 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)
5667
- };
5668
- }
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);
5679
- return {
5680
- system: "C",
5681
- prn,
5682
- toc: sowOf(tocDate.getTime()),
5683
- 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),
5696
- 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)
5711
- };
5712
- }
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;
5725
- return {
5726
- 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),
5743
- // 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
5746
- };
5747
- }
5748
- function parseSbfNav(data) {
5749
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5750
- 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);
5488
+ function decodeBdsD1Frame(subframes, opts = {}) {
5489
+ if (subframes.length < 3 * BDS_SUBFRAME_BYTES) return null;
5490
+ const b = subframes;
5491
+ let i = 8 * 38 * 0;
5492
+ const frn1 = getBitU(b, i + 15, 3);
5493
+ const sow1 = getBitU2(b, i + 18, 8, i + 30, 12);
5494
+ const svh = getBitU(b, i + 42, 1);
5495
+ const week = getBitU(b, i + 60, 13);
5496
+ const tocSec = getBitU2(b, i + 73, 9, i + 90, 8) * 8;
5497
+ const tgd1 = getBitS(b, i + 98, 10) * 0.1 * 1e-9;
5498
+ const af2 = getBitS(b, i + 214, 11) * 2 ** -66;
5499
+ const af0 = getBitS2(b, i + 225, 7, i + 240, 17) * 2 ** -33;
5500
+ const af1 = getBitS2(b, i + 257, 5, i + 270, 17) * 2 ** -50;
5501
+ i = 8 * 38 * 1;
5502
+ const frn2 = getBitU(b, i + 15, 3);
5503
+ const sow2 = getBitU2(b, i + 18, 8, i + 30, 12);
5504
+ const deltaN = getBitS2(b, i + 42, 10, i + 60, 6) * 2 ** -43 * GPS_PI;
5505
+ const cuc = getBitS2(b, i + 66, 16, i + 90, 2) * 2 ** -31;
5506
+ const m0 = getBitS2(b, i + 92, 20, i + 120, 12) * 2 ** -31 * GPS_PI;
5507
+ const e = getBitU2(b, i + 132, 10, i + 150, 22) * 2 ** -33;
5508
+ const cus = getBitS(b, i + 180, 18) * 2 ** -31;
5509
+ const crc = getBitS2(b, i + 198, 4, i + 210, 14) * 2 ** -6;
5510
+ const crs = getBitS2(b, i + 224, 8, i + 240, 10) * 2 ** -6;
5511
+ const sqrtA = getBitU2(b, i + 250, 12, i + 270, 20) * 2 ** -19;
5512
+ const toe1 = getBitU(b, i + 290, 2);
5513
+ i = 8 * 38 * 2;
5514
+ const frn3 = getBitU(b, i + 15, 3);
5515
+ const sow3 = getBitU2(b, i + 18, 8, i + 30, 12);
5516
+ const toe2 = getBitU2(b, i + 42, 10, i + 60, 5);
5517
+ const i0 = getBitS2(b, i + 65, 17, i + 90, 15) * 2 ** -31 * GPS_PI;
5518
+ const cic = getBitS2(b, i + 105, 7, i + 120, 11) * 2 ** -31;
5519
+ const omegaDot = getBitS2(b, i + 131, 11, i + 150, 13) * 2 ** -43 * GPS_PI;
5520
+ const cis = getBitS2(b, i + 163, 9, i + 180, 9) * 2 ** -31;
5521
+ const idot = getBitS2(b, i + 189, 13, i + 210, 1) * 2 ** -43 * GPS_PI;
5522
+ const omega0 = getBitS2(b, i + 211, 21, i + 240, 11) * 2 ** -31 * GPS_PI;
5523
+ const omega = getBitS2(b, i + 251, 11, i + 270, 21) * 2 ** -31 * GPS_PI;
5524
+ const toes = (toe1 * 2 ** 15 + toe2) * 8;
5525
+ if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3) return null;
5526
+ if (sow2 !== sow1 + 6 || sow3 !== sow2 + 6) return null;
5527
+ if (tocSec !== toes) return null;
5528
+ return buildBdsEphemeris(opts.prn ?? "C00", {
5529
+ week,
5530
+ sow: sow1,
5531
+ toes,
5532
+ tocSec,
5533
+ svh,
5534
+ tgd1,
5535
+ af0,
5536
+ af1,
5537
+ af2,
5538
+ crs,
5539
+ deltaN,
5540
+ m0,
5541
+ cuc,
5542
+ e,
5543
+ cus,
5544
+ sqrtA,
5545
+ cic,
5546
+ omega0,
5547
+ cis,
5548
+ i0,
5549
+ crc,
5550
+ omega,
5551
+ omegaDot,
5552
+ idot
5759
5553
  });
5760
- return { ephemerides, badCrc };
5761
5554
  }
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);
5555
+ function decodeBdsD2Frame(pages, opts = {}) {
5556
+ if (pages.length < 10 * BDS_SUBFRAME_BYTES) return null;
5557
+ const b = pages;
5558
+ let i = 8 * 38 * 0;
5559
+ const pgn1 = getBitU(b, i + 42, 4);
5560
+ const sow1 = getBitU2(b, i + 18, 8, i + 30, 12);
5561
+ const svh = getBitU(b, i + 46, 1);
5562
+ const week = getBitU(b, i + 64, 13);
5563
+ const tocSec = getBitU2(b, i + 77, 5, i + 90, 12) * 8;
5564
+ const tgd1 = getBitS(b, i + 102, 10) * 0.1 * 1e-9;
5565
+ i = 8 * 38 * 2;
5566
+ const pgn3 = getBitU(b, i + 42, 4);
5567
+ const sow3 = getBitU2(b, i + 18, 8, i + 30, 12);
5568
+ const af0 = getBitS2(b, i + 100, 12, i + 120, 12) * 2 ** -33;
5569
+ const af1p3 = getBitS(b, i + 132, 4);
5570
+ i = 8 * 38 * 3;
5571
+ const pgn4 = getBitU(b, i + 42, 4);
5572
+ const sow4 = getBitU2(b, i + 18, 8, i + 30, 12);
5573
+ const af1p4 = getBitU2(b, i + 46, 6, i + 60, 12);
5574
+ const af2 = getBitS2(b, i + 72, 10, i + 90, 1) * 2 ** -66;
5575
+ const deltaN = getBitS(b, i + 96, 16) * 2 ** -43 * GPS_PI;
5576
+ const cucp4 = getBitS(b, i + 120, 14);
5577
+ i = 8 * 38 * 4;
5578
+ const pgn5 = getBitU(b, i + 42, 4);
5579
+ const sow5 = getBitU2(b, i + 18, 8, i + 30, 12);
5580
+ const cucp5 = getBitU(b, i + 46, 4);
5581
+ const m0 = getBitS3(b, i + 50, 2, i + 60, 22, i + 90, 8) * 2 ** -31 * GPS_PI;
5582
+ const cus = getBitS2(b, i + 98, 14, i + 120, 4) * 2 ** -31;
5583
+ const ep5 = getBitS(b, i + 124, 10);
5584
+ i = 8 * 38 * 5;
5585
+ const pgn6 = getBitU(b, i + 42, 4);
5586
+ const sow6 = getBitU2(b, i + 18, 8, i + 30, 12);
5587
+ const ep6 = getBitU2(b, i + 46, 6, i + 60, 16);
5588
+ const sqrtA = getBitU3(b, i + 76, 6, i + 90, 22, i + 120, 4) * 2 ** -19;
5589
+ const cicp6 = getBitS(b, i + 124, 10);
5590
+ i = 8 * 38 * 6;
5591
+ const pgn7 = getBitU(b, i + 42, 4);
5592
+ const sow7 = getBitU2(b, i + 18, 8, i + 30, 12);
5593
+ const cicp7 = getBitU2(b, i + 46, 6, i + 60, 2);
5594
+ const cis = getBitS(b, i + 62, 18) * 2 ** -31;
5595
+ const toes = getBitU2(b, i + 80, 2, i + 90, 15) * 8;
5596
+ const i0p7 = getBitS2(b, i + 105, 7, i + 120, 14);
5597
+ i = 8 * 38 * 7;
5598
+ const pgn8 = getBitU(b, i + 42, 4);
5599
+ const sow8 = getBitU2(b, i + 18, 8, i + 30, 12);
5600
+ const i0p8 = getBitU2(b, i + 46, 6, i + 60, 5);
5601
+ const crc = getBitS2(b, i + 65, 17, i + 90, 1) * 2 ** -6;
5602
+ const crs = getBitS(b, i + 91, 18) * 2 ** -6;
5603
+ const omegaDotP8 = getBitS2(b, i + 109, 3, i + 120, 16);
5604
+ i = 8 * 38 * 8;
5605
+ const pgn9 = getBitU(b, i + 42, 4);
5606
+ const sow9 = getBitU2(b, i + 18, 8, i + 30, 12);
5607
+ const omegaDotP9 = getBitU(b, i + 46, 5);
5608
+ const omega0 = getBitS3(b, i + 51, 1, i + 60, 22, i + 90, 9) * 2 ** -31 * GPS_PI;
5609
+ const omegaP9 = getBitS2(b, i + 99, 13, i + 120, 14);
5610
+ i = 8 * 38 * 9;
5611
+ const pgn10 = getBitU(b, i + 42, 4);
5612
+ const sow10 = getBitU2(b, i + 18, 8, i + 30, 12);
5613
+ const omegaP10 = getBitU(b, i + 46, 5);
5614
+ const idot = getBitS2(b, i + 51, 1, i + 60, 13) * 2 ** -43 * GPS_PI;
5615
+ if (pgn1 !== 1 || pgn3 !== 3 || pgn4 !== 4 || pgn5 !== 5 || pgn6 !== 6 || pgn7 !== 7 || pgn8 !== 8 || pgn9 !== 9 || pgn10 !== 10) {
5616
+ return null;
5787
5617
  }
5788
- return {
5789
- system: sys,
5790
- prn: `${sys}${String(prn).padStart(2, "0")}`,
5791
- weekAlm,
5792
- toaSec: toa,
5618
+ 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) {
5619
+ return null;
5620
+ }
5621
+ if (tocSec !== toes) return null;
5622
+ return buildBdsEphemeris(opts.prn ?? "C00", {
5623
+ week,
5624
+ sow: sow1,
5625
+ toes,
5626
+ tocSec,
5627
+ svh,
5628
+ tgd1,
5629
+ af0,
5630
+ af1: mergeS(af1p3, af1p4, 18) * 2 ** -50,
5631
+ af2,
5632
+ crs,
5633
+ deltaN,
5634
+ m0,
5635
+ cuc: mergeS(cucp4, cucp5, 4) * 2 ** -31,
5636
+ e: mergeS(ep5, ep6, 22) * 2 ** -33,
5637
+ cus,
5793
5638
  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 };
5867
- }
5868
-
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);
5897
- }
5639
+ cic: mergeS(cicp6, cicp7, 8) * 2 ** -31,
5640
+ omega0,
5641
+ cis,
5642
+ i0: mergeS(i0p7, i0p8, 11) * 2 ** -31 * GPS_PI,
5643
+ crc,
5644
+ omega: mergeS(omegaP9, omegaP10, 5) * 2 ** -31 * GPS_PI,
5645
+ omegaDot: mergeS(omegaDotP8, omegaDotP9, 5) * 2 ** -43 * GPS_PI,
5646
+ idot
5898
5647
  });
5899
- return { ionoCorrections, leapSeconds };
5900
5648
  }
5901
-
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
- };
5914
- 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;
5649
+ function isBdsGeoPrn(prn) {
5650
+ return prn < 6 || prn > 58;
5651
+ }
5652
+ var BdsAssembler = class {
5653
+ sats = /* @__PURE__ */ new Map();
5654
+ /**
5655
+ * Push one 300-bit subframe (38+ bytes, bit 0 = first bit of the
5656
+ * preamble) for the satellite `prn` ("C06"). Returns the newly
5657
+ * completed ephemeris, or null.
5658
+ */
5659
+ push(prn, subframe) {
5660
+ if (subframe.length < BDS_SUBFRAME_BYTES) return null;
5661
+ const num = parseInt(prn.slice(1), 10);
5662
+ if (!Number.isFinite(num) || num < 1 || num > 63) return null;
5663
+ const id = getBitU(subframe, 15, 3);
5664
+ if (id < 1 || id > 5) return null;
5665
+ let sat = this.sats.get(prn);
5666
+ if (!sat) {
5667
+ sat = { buf: new Uint8Array(10 * BDS_SUBFRAME_BYTES) };
5668
+ this.sats.set(prn, sat);
5927
5669
  }
5928
- if (!cnavCrcOk(msg)) {
5929
- badCrc++;
5930
- return;
5670
+ let eph = null;
5671
+ if (!isBdsGeoPrn(num)) {
5672
+ sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (id - 1) * 38);
5673
+ if (id === 3) eph = decodeBdsD1Frame(sat.buf, { prn });
5674
+ } else {
5675
+ if (id !== 1) return null;
5676
+ const pgn = getBitU(subframe, 42, 4);
5677
+ if (pgn < 1 || pgn > 10) return null;
5678
+ sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (pgn - 1) * 38);
5679
+ if (pgn === 10) eph = decodeBdsD2Frame(sat.buf, { prn });
5931
5680
  }
5932
- const eph = assemblers[signal].push(msg);
5933
- if (eph) ephemerides.push({ ...eph, signal });
5934
- });
5935
- return { ephemerides, badCrc, messages };
5936
- }
5681
+ if (!eph) return null;
5682
+ const key = `${eph.week}:${eph.toe}`;
5683
+ if (key === sat.lastKey) return null;
5684
+ sat.lastKey = key;
5685
+ return eph;
5686
+ }
5687
+ };
5937
5688
 
5938
5689
  // src/navbits/gal.ts
5939
- var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
5690
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5940
5691
  var SEC_PER_WEEK5 = 7 * 86400;
5941
- var HALF_WEEK2 = 302400;
5692
+ var HALF_WEEK3 = 302400;
5942
5693
  var GST_GPS_WEEK_OFFSET = 1024;
5943
5694
  function galInavPageCrcOk(page) {
5944
5695
  if (page.length < 28) return false;
@@ -5949,14 +5700,14 @@ function galFnavPageCrcOk(page) {
5949
5700
  return crc24q(page, 214) === getBitU(page, 214, 24);
5950
5701
  }
5951
5702
  function gpsDate2(week, sec) {
5952
- return new Date(GPS_EPOCH_MS6 + (week * SEC_PER_WEEK5 + sec) * 1e3);
5703
+ return new Date(GPS_EPOCH_MS4 + (week * SEC_PER_WEEK5 + sec) * 1e3);
5953
5704
  }
5954
5705
  function weekOfToe(week, tow, toe) {
5955
- if (toe - tow > HALF_WEEK2) return week - 1;
5956
- if (toe - tow < -HALF_WEEK2) return week + 1;
5706
+ if (toe - tow > HALF_WEEK3) return week - 1;
5707
+ if (toe - tow < -HALF_WEEK3) return week + 1;
5957
5708
  return week;
5958
5709
  }
5959
- var two2 = (n) => String(n).padStart(2, "0");
5710
+ var two = (n) => String(n).padStart(2, "0");
5960
5711
  function decodeGalInavWords(words) {
5961
5712
  if (words.length < 96) return null;
5962
5713
  const b = words;
@@ -6044,7 +5795,7 @@ function decodeGalInavWords(words) {
6044
5795
  const tocDate = gpsDate2(week, tocs);
6045
5796
  return {
6046
5797
  system: "E",
6047
- prn: `E${two2(svid)}`,
5798
+ prn: `E${two(svid)}`,
6048
5799
  toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
6049
5800
  tocDate,
6050
5801
  af0,
@@ -6151,598 +5902,951 @@ function decodeGalFnavPages(pages) {
6151
5902
  const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
6152
5903
  const tocDate = gpsDate2(week, tocs);
6153
5904
  return {
6154
- system: "E",
6155
- prn: `E${two2(svid)}`,
6156
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
6157
- tocDate,
6158
- af0,
6159
- af1,
6160
- af2,
6161
- iode: iodNav1,
6162
- crs,
6163
- deltaN,
6164
- m0,
6165
- cuc,
6166
- e,
6167
- cus,
6168
- sqrtA,
6169
- 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
5905
+ system: "E",
5906
+ prn: `E${two(svid)}`,
5907
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
5908
+ tocDate,
5909
+ af0,
5910
+ af1,
5911
+ af2,
5912
+ iode: iodNav1,
5913
+ crs,
5914
+ deltaN,
5915
+ m0,
5916
+ cuc,
5917
+ e,
5918
+ cus,
5919
+ sqrtA,
5920
+ toe: toes,
5921
+ cic,
5922
+ omega0,
5923
+ cis,
5924
+ i0,
5925
+ crc,
5926
+ omega,
5927
+ omegaDot,
5928
+ idot,
5929
+ week,
5930
+ svHealth: e5aHs << 4 | e5aDvs << 3,
5931
+ tgd: bgdE5a
5932
+ };
5933
+ }
5934
+ var dedupKey = (eph) => `${eph.iode}:${eph.toe}:${eph.tocDate.getTime()}`;
5935
+ var GalInavAssembler = class {
5936
+ sats = /* @__PURE__ */ new Map();
5937
+ /**
5938
+ * Push one I/NAV page pair for satellite `prn` (1-36): the 234-bit
5939
+ * even+odd concatenation, even part first with its 6 tail bits
5940
+ * removed, bit 0 = the even part's Even/Odd bit (≥ 17 bytes).
5941
+ * Returns the newly completed ephemeris, or null. Pages that are
5942
+ * not an even/odd nominal pair, alert pages and word types outside
5943
+ * 1-5 are ignored.
5944
+ */
5945
+ push(prn, page) {
5946
+ if (prn < 1 || prn > 36 || page.length < 17) return null;
5947
+ if (getBitU(page, 0, 1) !== 0 || getBitU(page, 114, 1) !== 1) return null;
5948
+ if (getBitU(page, 1, 1) === 1 || getBitU(page, 115, 1) === 1) return null;
5949
+ const type = getBitU(page, 2, 6);
5950
+ if (type < 1 || type > 5) return null;
5951
+ let sat = this.sats.get(prn);
5952
+ if (!sat) {
5953
+ sat = { words: new Uint8Array(96) };
5954
+ this.sats.set(prn, sat);
5955
+ }
5956
+ for (let k = 0; k < 14; k++)
5957
+ sat.words[type * 16 + k] = getBitU(page, 2 + 8 * k, 8);
5958
+ sat.words[type * 16 + 14] = getBitU(page, 116, 8);
5959
+ sat.words[type * 16 + 15] = getBitU(page, 124, 8);
5960
+ if (type !== 5) return null;
5961
+ const eph = decodeGalInavWords(sat.words);
5962
+ if (!eph || eph.prn !== `E${two(prn)}`) return null;
5963
+ const key = dedupKey(eph);
5964
+ if (key === sat.lastKey) return null;
5965
+ sat.lastKey = key;
5966
+ return eph;
5967
+ }
5968
+ };
5969
+ var GalFnavAssembler = class {
5970
+ sats = /* @__PURE__ */ new Map();
5971
+ /**
5972
+ * Push one F/NAV page for satellite `prn` (1-36): 244 bits with the
5973
+ * sync field stripped, bit 0 = first page-type bit (≥ 31 bytes).
5974
+ * Returns the newly completed ephemeris, or null. Dummy pages
5975
+ * (type 63) and page types outside 1-4 are ignored.
5976
+ */
5977
+ push(prn, page) {
5978
+ if (prn < 1 || prn > 36 || page.length < 31) return null;
5979
+ const type = getBitU(page, 0, 6);
5980
+ if (type < 1 || type > 4) return null;
5981
+ let sat = this.sats.get(prn);
5982
+ if (!sat) {
5983
+ sat = { words: new Uint8Array(124) };
5984
+ this.sats.set(prn, sat);
5985
+ }
5986
+ sat.words.set(page.subarray(0, 31), (type - 1) * 31);
5987
+ if (type !== 4) return null;
5988
+ const eph = decodeGalFnavPages(sat.words);
5989
+ if (!eph || eph.prn !== `E${two(prn)}`) return null;
5990
+ const key = dedupKey(eph);
5991
+ if (key === sat.lastKey) return null;
5992
+ sat.lastKey = key;
5993
+ return eph;
5994
+ }
5995
+ };
5996
+
5997
+ // src/navbits/glo.ts
5998
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5999
+ var SEC_PER_WEEK6 = 7 * 86400;
6000
+ var SEC_PER_DAY = 86400;
6001
+ var GLO_STRING_BYTES = 10;
6002
+ function getBitG(b, pos, len) {
6003
+ const value = getBitU(b, pos + 1, len - 1);
6004
+ return getBitU(b, pos, 1) && value !== 0 ? -value : value;
6005
+ }
6006
+ var HAMMING_MASKS = [
6007
+ [85, 85, 90, 170, 170, 170, 181, 85, 106, 216, 8],
6008
+ [102, 102, 108, 204, 204, 204, 217, 153, 179, 104, 16],
6009
+ [135, 135, 143, 15, 15, 15, 30, 30, 60, 112, 32],
6010
+ [7, 248, 15, 240, 15, 240, 31, 224, 63, 128, 64],
6011
+ [248, 0, 15, 255, 240, 0, 31, 255, 192, 0, 128],
6012
+ [0, 0, 15, 255, 255, 255, 224, 0, 0, 1, 0],
6013
+ [255, 255, 240, 0, 0, 0, 0, 0, 0, 2, 0],
6014
+ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248]
6015
+ ];
6016
+ function testGloString(buff) {
6017
+ if (buff.length < 11) return false;
6018
+ let n = 0;
6019
+ let cs = 0;
6020
+ for (const mask of HAMMING_MASKS) {
6021
+ cs = 0;
6022
+ for (let j = 0; j < 11; j++) {
6023
+ let x = buff[j] & mask[j];
6024
+ x ^= x >> 4;
6025
+ x ^= x >> 2;
6026
+ x ^= x >> 1;
6027
+ cs ^= x & 1;
6028
+ }
6029
+ if (cs) n++;
6030
+ }
6031
+ return n === 0 || n === 2 && cs === 1;
6032
+ }
6033
+ function decodeGloStrings(strings, refDate, opts = {}) {
6034
+ if (strings.length < 4 * GLO_STRING_BYTES) return null;
6035
+ const b = strings;
6036
+ let i = 1;
6037
+ const frn1 = getBitU(b, i, 4);
6038
+ i += 4 + 2;
6039
+ i += 2;
6040
+ const tkH = getBitU(b, i, 5);
6041
+ i += 5;
6042
+ const tkM = getBitU(b, i, 6);
6043
+ i += 6;
6044
+ const tkS = getBitU(b, i, 1) * 30;
6045
+ i += 1;
6046
+ const xDot = getBitG(b, i, 24) * 2 ** -20;
6047
+ i += 24;
6048
+ const xAcc = getBitG(b, i, 5) * 2 ** -30;
6049
+ i += 5;
6050
+ const x = getBitG(b, i, 27) * 2 ** -11;
6051
+ i = 80 + 1;
6052
+ const frn2 = getBitU(b, i, 4);
6053
+ i += 4;
6054
+ const bn = getBitU(b, i, 1);
6055
+ i += 1 + 2;
6056
+ i += 1;
6057
+ const tb = getBitU(b, i, 7);
6058
+ i += 7 + 5;
6059
+ const yDot = getBitG(b, i, 24) * 2 ** -20;
6060
+ i += 24;
6061
+ const yAcc = getBitG(b, i, 5) * 2 ** -30;
6062
+ i += 5;
6063
+ const y = getBitG(b, i, 27) * 2 ** -11;
6064
+ i = 160 + 1;
6065
+ const frn3 = getBitU(b, i, 4);
6066
+ i += 4;
6067
+ i += 1;
6068
+ const gammaN = getBitG(b, i, 11) * 2 ** -40;
6069
+ i += 11 + 1;
6070
+ i += 2;
6071
+ i += 1;
6072
+ const zDot = getBitG(b, i, 24) * 2 ** -20;
6073
+ i += 24;
6074
+ const zAcc = getBitG(b, i, 5) * 2 ** -30;
6075
+ i += 5;
6076
+ const z = getBitG(b, i, 27) * 2 ** -11;
6077
+ i = 240 + 1;
6078
+ const frn4 = getBitU(b, i, 4);
6079
+ i += 4;
6080
+ const tauN = getBitG(b, i, 22) * 2 ** -30;
6081
+ i += 22;
6082
+ i += 5;
6083
+ i += 5;
6084
+ i += 14;
6085
+ i += 1;
6086
+ i += 4;
6087
+ i += 3;
6088
+ i += 11;
6089
+ const slot = getBitU(b, i, 5);
6090
+ if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3 || frn4 !== 4) return null;
6091
+ if (slot < 1 || slot > 27) return null;
6092
+ const utcSec = (getUtcDate(refDate).getTime() - GPS_EPOCH_MS5) / 1e3;
6093
+ const week = Math.floor(utcSec / SEC_PER_WEEK6);
6094
+ let tow = utcSec - week * SEC_PER_WEEK6;
6095
+ const tod = tow % SEC_PER_DAY;
6096
+ tow -= tod;
6097
+ let tof = tkH * 3600 + tkM * 60 + tkS - 10800;
6098
+ if (tof < tod - 43200) tof += SEC_PER_DAY;
6099
+ else if (tof > tod + 43200) tof -= SEC_PER_DAY;
6100
+ let toe = tb * 900 - 10800;
6101
+ if (toe < tod - 43200) toe += SEC_PER_DAY;
6102
+ else if (toe > tod + 43200) toe -= SEC_PER_DAY;
6103
+ return {
6104
+ system: "R",
6105
+ prn: `R${String(slot).padStart(2, "0")}`,
6106
+ // RINEX GLONASS epochs are UTC: build the UTC toe Date directly.
6107
+ tocDate: new Date(GPS_EPOCH_MS5 + (week * SEC_PER_WEEK6 + tow + toe) * 1e3),
6108
+ tauN: -tauN,
6109
+ // RINEX stores −τn; the SIS carries τn (ICD sign)
6110
+ gammaN,
6111
+ // v3 message frame time: seconds of the UTC week (RTKLIB tof).
6112
+ messageFrameTime: ((tow + tof) % SEC_PER_WEEK6 + SEC_PER_WEEK6) % SEC_PER_WEEK6,
6113
+ x,
6114
+ xDot,
6115
+ xAcc,
6116
+ y,
6117
+ yDot,
6118
+ yAcc,
6119
+ z,
6120
+ zDot,
6121
+ zAcc,
6122
+ // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
6123
+ health: bn,
6124
+ freqNum: opts.freqNum ?? 0
6181
6125
  };
6182
6126
  }
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 {
6127
+ var GloStringAssembler = class {
6219
6128
  sats = /* @__PURE__ */ new Map();
6220
6129
  /**
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.
6130
+ * Push one navigation string (10+ bytes, bit 0 = string bit 85) for
6131
+ * the satellite `prn` ("R09"), received at the GPS-scale `time`.
6132
+ * Returns the newly completed ephemeris, or null. The decoded
6133
+ * string-4 slot number must match `prn`, or nothing is emitted.
6225
6134
  */
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;
6135
+ push(prn, str, time, freqNum = 0) {
6136
+ if (str.length < GLO_STRING_BYTES) return null;
6137
+ const m = getBitU(str, 1, 4);
6138
+ if (m < 1) return null;
6139
+ const sec = Math.floor(time.getTime() / 1e3);
6230
6140
  let sat = this.sats.get(prn);
6231
6141
  if (!sat) {
6232
- sat = { words: new Uint8Array(124) };
6142
+ sat = { buf: new Uint8Array(4 * GLO_STRING_BYTES), batchSec: sec };
6233
6143
  this.sats.set(prn, sat);
6144
+ } else if (Math.abs(sec - sat.batchSec) > 30) {
6145
+ sat.buf.fill(0);
6146
+ sat.batchSec = sec;
6234
6147
  }
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);
6148
+ if (m > 4) return null;
6149
+ sat.buf.set(str.subarray(0, GLO_STRING_BYTES), (m - 1) * 10);
6150
+ if (m !== 4) return null;
6151
+ const eph = decodeGloStrings(sat.buf, time, { freqNum });
6152
+ if (!eph || eph.prn !== prn) return null;
6153
+ const key = `${eph.tocDate.getTime()}:${eph.health}`;
6240
6154
  if (key === sat.lastKey) return null;
6241
6155
  sat.lastKey = key;
6242
6156
  return eph;
6243
6157
  }
6244
6158
  };
6245
6159
 
6246
- // src/sbf/rawnav-gal.ts
6247
- var INAV_SOURCES = [17, 21, 22];
6248
- var FNAV_SOURCES = [20, 22];
6249
- function parseSbfGalNav(data) {
6160
+ // src/ubx/rawnav.ts
6161
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6162
+ var SEC_PER_WEEK7 = 7 * 86400;
6163
+ var two2 = (n) => String(n).padStart(2, "0");
6164
+ function parseUbxRawNav(data, opts = {}) {
6250
6165
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6251
6166
  const ephemerides = [];
6167
+ const counts = { gal: 0, bds: 0, glo: 0 };
6168
+ let badParity = 0;
6252
6169
  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;
6170
+ const bds = new BdsAssembler();
6171
+ const glo = new GloStringAssembler();
6172
+ let refDate = null;
6173
+ for (const f of ubxFrames(data)) {
6174
+ if (f.msgClass !== 2) continue;
6175
+ if (f.msgId === 21 && f.payload.length >= 16) {
6176
+ const rcvTow = view.getFloat64(f.payloadStart, true);
6177
+ let week = view.getUint16(f.payloadStart + 8, true);
6178
+ if (week === 0 && opts.refWeek !== void 0) week = opts.refWeek;
6179
+ if (week > 0) {
6180
+ refDate = new Date(
6181
+ GPS_EPOCH_MS6 + (week * SEC_PER_WEEK7 + rcvTow) * 1e3
6182
+ );
6183
+ }
6184
+ continue;
6270
6185
  }
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;
6186
+ if (f.msgId !== 19) continue;
6187
+ const p = f.payload;
6188
+ if (p.length < 8) continue;
6189
+ const gnssId = p[0];
6190
+ const svId = p[1];
6191
+ const sigId = p[2];
6192
+ const base = f.payloadStart + 8;
6193
+ if (gnssId === 2) {
6194
+ if (sigId === 3 || sigId === 8) continue;
6195
+ if (p.length < 8 + 32) continue;
6196
+ counts.gal++;
6197
+ if (svId < 1 || svId > 36) continue;
6198
+ const buff = new Uint8Array(32);
6199
+ for (let k = 0; k < 8; k++) {
6200
+ const w = view.getUint32(base + 4 * k, true);
6201
+ buff[4 * k] = w >>> 24;
6202
+ buff[4 * k + 1] = w >>> 16 & 255;
6203
+ buff[4 * k + 2] = w >>> 8 & 255;
6204
+ buff[4 * k + 3] = w & 255;
6205
+ }
6206
+ const page = new Uint8Array(30);
6207
+ for (let k = 0; k < 14; k++) page[k] = buff[k];
6208
+ setBitU(page, 112, 2, getBitU(buff, 112, 2));
6209
+ for (let k = 0; k < 15; k++) {
6210
+ setBitU(page, 114 + 8 * k, 8, getBitU(buff, 128 + 8 * k, 8));
6211
+ }
6212
+ if (getBitU(page, 0, 1) !== 0 || getBitU(page, 114, 1) !== 1) continue;
6213
+ if (getBitU(page, 1, 1) === 1 || getBitU(page, 115, 1) === 1) continue;
6277
6214
  if (!galInavPageCrcOk(page)) {
6278
- badCrc++;
6279
- return;
6215
+ badParity++;
6216
+ continue;
6280
6217
  }
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;
6218
+ const eph = inav.push(svId, page);
6219
+ if (eph) ephemerides.push(eph);
6220
+ } else if (gnssId === 3) {
6221
+ if (sigId === 6 || sigId === 8) continue;
6222
+ if (p.length < 8 + 40) continue;
6223
+ counts.bds++;
6224
+ if (svId < 1 || svId > 63) continue;
6225
+ const sf = new Uint8Array(38);
6226
+ for (let k = 0; k < 10; k++) {
6227
+ const w = view.getUint32(base + 4 * k, true);
6228
+ setBitU(sf, 30 * k, 30, w & 1073741823);
6229
+ }
6230
+ if (!bdsSubframeParityOk(sf)) {
6231
+ badParity++;
6232
+ continue;
6233
+ }
6234
+ const eph = bds.push(`C${two2(svId)}`, sf);
6235
+ if (eph) ephemerides.push(eph);
6236
+ } else if (gnssId === 6) {
6237
+ if (p.length < 8 + 16) continue;
6238
+ counts.glo++;
6239
+ if (svId < 1 || svId > 32) continue;
6240
+ const str = new Uint8Array(16);
6241
+ for (let k = 0; k < 4; k++) {
6242
+ const w = view.getUint32(base + 4 * k, true);
6243
+ str[4 * k] = w >>> 24;
6244
+ str[4 * k + 1] = w >>> 16 & 255;
6245
+ str[4 * k + 2] = w >>> 8 & 255;
6246
+ str[4 * k + 3] = w & 255;
6247
+ }
6248
+ if (!testGloString(str)) {
6249
+ badParity++;
6250
+ continue;
6251
+ }
6252
+ if (!refDate) continue;
6253
+ const eph = glo.push(`R${two2(svId)}`, str, refDate, p[3] - 7);
6254
+ if (eph) ephemerides.push(eph);
6255
+ }
6256
+ }
6257
+ return { ephemerides, counts, badParity };
6258
+ }
6259
+
6260
+ // src/ubx/index.ts
6261
+ var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6262
+ var MS_PER_WEEK3 = 7 * 864e5;
6263
+ var SIGNALS = {
6264
+ 0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
6265
+ // GPS L1C/A, L2CL, L2CM
6266
+ 1: { 0: ["S", "1C"] },
6267
+ // SBAS L1C/A
6268
+ 2: { 0: ["E", "1X"], 1: ["E", "1X"], 5: ["E", "7X"], 6: ["E", "7X"] },
6269
+ // E1C/B, E5bI/Q
6270
+ 3: { 0: ["C", "2I"], 1: ["C", "2I"], 2: ["C", "7I"], 3: ["C", "7I"] },
6271
+ // B1I D1/D2, B2I D1/D2
6272
+ 5: { 0: ["J", "1C"], 4: ["J", "2X"], 5: ["J", "2X"] },
6273
+ // QZSS L1C/A, L2CM/CL
6274
+ 6: { 0: ["R", "1C"], 2: ["R", "2C"] }
6275
+ // GLONASS L1OF, L2OF
6276
+ };
6277
+ function prnFor(gnssId, svId) {
6278
+ const two5 = (n) => String(n).padStart(2, "0");
6279
+ switch (gnssId) {
6280
+ case 0:
6281
+ return svId >= 1 && svId <= 32 ? `G${two5(svId)}` : null;
6282
+ case 1:
6283
+ return svId >= 120 && svId <= 158 ? `S${two5(svId - 100)}` : null;
6284
+ case 2:
6285
+ return svId >= 1 && svId <= 36 ? `E${two5(svId)}` : null;
6286
+ case 3:
6287
+ return svId >= 1 && svId <= 63 ? `C${two5(svId)}` : null;
6288
+ case 5:
6289
+ return svId >= 1 && svId <= 10 ? `J${two5(svId)}` : null;
6290
+ case 6:
6291
+ return svId >= 1 && svId <= 32 ? `R${two5(svId)}` : null;
6292
+ default:
6293
+ return null;
6294
+ }
6295
+ }
6296
+ function parseUbxRawx(data) {
6297
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6298
+ const epochs = [];
6299
+ const messageCounts = {};
6300
+ const obsCodes = {};
6301
+ const stats = { badChecksums: 0 };
6302
+ for (const frame of ubxFrames(data, stats)) {
6303
+ const { msgClass: cls, msgId: id } = frame;
6304
+ const len = frame.payload.length;
6305
+ const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
6306
+ messageCounts[key] = (messageCounts[key] ?? 0) + 1;
6307
+ if (cls === 2 && id === 21 && len >= 16) {
6308
+ const p = frame.payloadStart;
6309
+ const rcvTow = view.getFloat64(p, true);
6310
+ const week = view.getUint16(p + 8, true);
6311
+ const leapS = view.getInt8(p + 10);
6312
+ const numMeas = data[p + 11];
6313
+ const recStat = data[p + 12];
6314
+ const leapValid = (recStat & 1) !== 0;
6315
+ const meas = [];
6316
+ for (let k = 0; k < numMeas; k++) {
6317
+ const off = p + 16 + 32 * k;
6318
+ if (off + 32 > p + len) break;
6319
+ const gnssId = data[off + 20];
6320
+ const svId = data[off + 21];
6321
+ const sigId = data[off + 22];
6322
+ const trkStat = data[off + 30];
6323
+ const prn = prnFor(gnssId, svId);
6324
+ const sig = SIGNALS[gnssId]?.[sigId];
6325
+ if (!prn || !sig) continue;
6326
+ const prValid = (trkStat & 1) !== 0;
6327
+ const cpValid = (trkStat & 2) !== 0;
6328
+ const cp = view.getFloat64(off + 8, true);
6329
+ meas.push({
6330
+ prn,
6331
+ code: sig[1],
6332
+ pr: prValid ? view.getFloat64(off, true) : null,
6333
+ cp: cpValid && cp !== 0 ? cp : null,
6334
+ doppler: view.getFloat32(off + 16, true),
6335
+ cn0: data[off + 26],
6336
+ halfCycleAmbiguous: (trkStat & 4) !== 0,
6337
+ lockTimeMs: view.getUint16(off + 24, true)
6338
+ });
6339
+ const sys = sig[0];
6340
+ const codes = obsCodes[sys] ??= [];
6341
+ if (!codes.includes(sig[1])) codes.push(sig[1]);
6289
6342
  }
6290
- eph = fnav.push(prn, page);
6343
+ epochs.push({
6344
+ timeMs: GPS_EPOCH_MS7 + week * MS_PER_WEEK3 + Math.round(rcvTow * 1e3),
6345
+ leapS: leapValid ? leapS : null,
6346
+ meas
6347
+ });
6291
6348
  }
6292
- if (eph) ephemerides.push({ ...eph, source: src });
6293
- });
6294
- return { ephemerides, badCrc, messages };
6349
+ }
6350
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
6295
6351
  }
6296
- function getBitU2(buff, pos) {
6297
- return buff[pos >> 3] >> 7 - (pos & 7) & 1;
6352
+
6353
+ // src/sbf/frame.ts
6354
+ var CRC_TABLE = (() => {
6355
+ const t = new Uint16Array(256);
6356
+ for (let n = 0; n < 256; n++) {
6357
+ let c = n << 8;
6358
+ for (let k = 0; k < 8; k++)
6359
+ c = c & 32768 ? (c << 1 ^ 4129) & 65535 : c << 1 & 65535;
6360
+ t[n] = c;
6361
+ }
6362
+ return t;
6363
+ })();
6364
+ function crc16(data, start, end) {
6365
+ let crc = 0;
6366
+ for (let i = start; i < end; i++)
6367
+ crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
6368
+ return crc;
6298
6369
  }
6299
- function isFnavDummy(page) {
6300
- return page[0] >> 2 === 63;
6370
+ function scanSbfFrames(data, view, onBlock) {
6371
+ let badCrc = 0;
6372
+ let i = 0;
6373
+ while (i + 8 <= data.length) {
6374
+ if (data[i] !== 36 || data[i + 1] !== 64) {
6375
+ i++;
6376
+ continue;
6377
+ }
6378
+ const len = view.getUint16(i + 6, true);
6379
+ if (len < 8 || len % 4 !== 0) {
6380
+ i++;
6381
+ continue;
6382
+ }
6383
+ if (i + len > data.length) break;
6384
+ if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
6385
+ badCrc++;
6386
+ i++;
6387
+ continue;
6388
+ }
6389
+ onBlock(view.getUint16(i + 4, true) & 8191, i, len);
6390
+ i += len;
6391
+ }
6392
+ return badCrc;
6393
+ }
6394
+ var two3 = (n) => String(n).padStart(2, "0");
6395
+ function svidToPrn(svid) {
6396
+ if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two3(svid)}` : null;
6397
+ if (svid <= 61) return svid - 37 <= 27 ? `R${two3(svid - 37)}` : null;
6398
+ if (svid <= 62) return null;
6399
+ if (svid <= 68) return svid - 38 <= 27 ? `R${two3(svid - 38)}` : null;
6400
+ if (svid <= 70) return null;
6401
+ if (svid <= 106) return svid - 70 <= 36 ? `E${two3(svid - 70)}` : null;
6402
+ if (svid <= 119) return null;
6403
+ if (svid <= 140) return `S${two3(svid - 100)}`;
6404
+ if (svid <= 180) return svid - 140 <= 50 ? `C${two3(svid - 140)}` : null;
6405
+ if (svid <= 190) return `J${two3(svid - 180)}`;
6406
+ if (svid <= 197) return svid - 190 <= 14 ? `I${two3(svid - 190)}` : null;
6407
+ if (svid <= 215) return `S${two3(svid - 157)}`;
6408
+ if (svid <= 222) return svid - 208 <= 14 ? `I${two3(svid - 208)}` : null;
6409
+ if (svid <= 245) return svid - 182 <= 50 ? `C${two3(svid - 182)}` : null;
6410
+ return null;
6301
6411
  }
6302
6412
 
6303
- // src/navbits/bds.ts
6413
+ // src/sbf/nav.ts
6414
+ var PI2 = Math.PI;
6415
+ var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
6304
6416
  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);
6417
+ var SEC_PER_WEEK8 = 7 * 86400;
6418
+ var MS_PER_WEEK4 = SEC_PER_WEEK8 * 1e3;
6419
+ var F4_DNU = -2e10;
6420
+ var U4_DNU = 4294967295;
6421
+ function adjustWeek(refWeek, wn, mod) {
6422
+ let offset = refWeek % mod - wn;
6423
+ if (offset > mod / 2) offset -= mod;
6424
+ if (offset < -(mod / 2 - 1)) offset += mod;
6425
+ return refWeek - offset;
6318
6426
  }
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);
6427
+ var gpsMs = (week, sec) => GPS_EPOCH_MS8 + week * MS_PER_WEEK4 + sec * 1e3;
6428
+ var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK8;
6429
+ function decodeGpsQzsNav(view, b, sys) {
6430
+ const svid = view.getUint8(b + 14);
6431
+ const prn = sys === "G" ? svid : svid - 180;
6432
+ const max = sys === "G" ? 32 : 10;
6433
+ if (prn < 1 || prn > max) return null;
6434
+ const wnc = view.getUint16(b + 12, true);
6435
+ const tgdRaw = view.getFloat32(b + 28, true);
6436
+ const tocs = view.getUint32(b + 32, true);
6437
+ const toes = view.getUint32(b + 88, true);
6438
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 136, true), 1024);
6439
+ const tocDate = new Date(gpsMs(wnToc, tocs));
6440
+ return {
6441
+ system: sys,
6442
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
6443
+ toc: sowOf2(tocDate.getTime()),
6444
+ tocDate,
6445
+ af0: view.getFloat32(b + 44, true),
6446
+ af1: view.getFloat32(b + 40, true),
6447
+ af2: view.getFloat32(b + 36, true),
6448
+ iode: view.getUint8(b + 24),
6449
+ crs: view.getFloat32(b + 48, true),
6450
+ deltaN: view.getFloat32(b + 52, true) * PI2,
6451
+ m0: view.getFloat64(b + 56, true) * PI2,
6452
+ cuc: view.getFloat32(b + 64, true),
6453
+ e: view.getFloat64(b + 68, true),
6454
+ cus: view.getFloat32(b + 76, true),
6455
+ sqrtA: view.getFloat64(b + 80, true),
6456
+ toe: toes,
6457
+ cic: view.getFloat32(b + 92, true),
6458
+ omega0: view.getFloat64(b + 96, true) * PI2,
6459
+ cis: view.getFloat32(b + 104, true),
6460
+ i0: view.getFloat64(b + 108, true) * PI2,
6461
+ crc: view.getFloat32(b + 116, true),
6462
+ omega: view.getFloat64(b + 120, true) * PI2,
6463
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
6464
+ idot: view.getFloat32(b + 132, true) * PI2,
6465
+ // RINEX "GPS week to go with toe": RTKLIB writes WNc for GPS and
6466
+ // the week of toc for QZSS — mirrored here so records compare
6467
+ // 1:1 against convbin output. wnToe is still used for `toe` above.
6468
+ week: sys === "G" ? wnc : wnToc,
6469
+ svHealth: view.getUint8(b + 20),
6470
+ tgd: tgdRaw !== F4_DNU ? tgdRaw : 0
6471
+ };
6321
6472
  }
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;
6326
- }
6327
- return cw === 0;
6473
+ function decodeGalNav(view, b) {
6474
+ const prn = view.getUint8(b + 14) - 70;
6475
+ if (prn < 1 || prn > 36) return null;
6476
+ const source = view.getUint8(b + 15);
6477
+ if (source !== 2 && source !== 16) return null;
6478
+ const wnc = view.getUint16(b + 12, true);
6479
+ const toes = view.getUint32(b + 100, true);
6480
+ const tocs = view.getUint32(b + 104, true);
6481
+ const wnToc = adjustWeek(wnc, view.getUint16(b + 126, true), 4096);
6482
+ const tocDate = new Date(gpsMs(wnToc, tocs));
6483
+ const health = view.getUint16(b + 130, true);
6484
+ let svh = 0;
6485
+ if (health & 1) svh |= health >> 1 & 7;
6486
+ if (health & 16) svh |= (health >> 5 & 7) << 6;
6487
+ if (health & 256) svh |= (health >> 9 & 7) << 3;
6488
+ const bgdE5a = view.getFloat32(b + 136, true);
6489
+ return {
6490
+ system: "E",
6491
+ prn: `E${String(prn).padStart(2, "0")}`,
6492
+ toc: sowOf2(tocDate.getTime()),
6493
+ tocDate,
6494
+ af0: view.getFloat64(b + 116, true),
6495
+ af1: view.getFloat32(b + 112, true),
6496
+ af2: view.getFloat32(b + 108, true),
6497
+ iode: view.getUint16(b + 128, true),
6498
+ // IODnav
6499
+ crs: view.getFloat32(b + 88, true),
6500
+ deltaN: view.getFloat32(b + 72, true) * PI2,
6501
+ m0: view.getFloat64(b + 24, true) * PI2,
6502
+ cuc: view.getFloat32(b + 76, true),
6503
+ e: view.getFloat64(b + 32, true),
6504
+ cus: view.getFloat32(b + 80, true),
6505
+ sqrtA: view.getFloat64(b + 16, true),
6506
+ toe: toes,
6507
+ cic: view.getFloat32(b + 92, true),
6508
+ omega0: view.getFloat64(b + 56, true) * PI2,
6509
+ cis: view.getFloat32(b + 96, true),
6510
+ i0: view.getFloat64(b + 40, true) * PI2,
6511
+ crc: view.getFloat32(b + 84, true),
6512
+ omega: view.getFloat64(b + 48, true) * PI2,
6513
+ omegaDot: view.getFloat32(b + 64, true) * PI2,
6514
+ idot: view.getFloat32(b + 68, true) * PI2,
6515
+ week: wnc,
6516
+ // RINEX GAL week is GPS-aligned and continuous
6517
+ svHealth: svh,
6518
+ tgd: bgdE5a !== F4_DNU ? bgdE5a : 0
6519
+ // BGD E5a/E1 (RINEX slot)
6520
+ };
6328
6521
  }
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;
6522
+ function decodeBdsNav(view, b) {
6523
+ const prn = svidToPrn(view.getUint8(b + 14));
6524
+ if (!prn || prn[0] !== "C") return null;
6525
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
6526
+ const tgd1 = view.getFloat32(b + 24, true);
6527
+ const tocs = view.getUint32(b + 32, true);
6528
+ const toes = view.getUint32(b + 88, true);
6529
+ const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
6530
+ const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
6531
+ const tocDate = new Date(BDT_EPOCH_MS2 + wnToc * MS_PER_WEEK4 + tocs * 1e3);
6532
+ return {
6533
+ system: "C",
6534
+ prn,
6535
+ toc: sowOf2(tocDate.getTime()),
6536
+ tocDate,
6537
+ af0: view.getFloat32(b + 44, true),
6538
+ af1: view.getFloat32(b + 40, true),
6539
+ af2: view.getFloat32(b + 36, true),
6540
+ iode: view.getUint8(b + 21),
6541
+ // AODE
6542
+ crs: view.getFloat32(b + 48, true),
6543
+ deltaN: view.getFloat32(b + 52, true) * PI2,
6544
+ m0: view.getFloat64(b + 56, true) * PI2,
6545
+ cuc: view.getFloat32(b + 64, true),
6546
+ e: view.getFloat64(b + 68, true),
6547
+ cus: view.getFloat32(b + 76, true),
6548
+ sqrtA: view.getFloat64(b + 80, true),
6549
+ toe: toes,
6550
+ cic: view.getFloat32(b + 92, true),
6551
+ omega0: view.getFloat64(b + 96, true) * PI2,
6552
+ cis: view.getFloat32(b + 104, true),
6553
+ i0: view.getFloat64(b + 108, true) * PI2,
6554
+ crc: view.getFloat32(b + 116, true),
6555
+ omega: view.getFloat64(b + 120, true) * PI2,
6556
+ omegaDot: view.getFloat32(b + 128, true) * PI2,
6557
+ idot: view.getFloat32(b + 132, true) * PI2,
6558
+ week: wnToe,
6559
+ // RINEX BDS week field is the BDT week
6560
+ svHealth: view.getUint8(b + 19),
6561
+ // SatH1
6562
+ tgd: tgd1 !== F4_DNU ? tgd1 : 0
6563
+ // TGD1 B1/B3 (RINEX slot)
6564
+ };
6339
6565
  }
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);
6566
+ function decodeGloNav(view, b) {
6567
+ const prn = svidToPrn(view.getUint8(b + 14));
6568
+ if (!prn || prn[0] !== "R") return null;
6569
+ const wnc = view.getUint16(b + 12, true);
6570
+ const toes = view.getUint32(b + 76, true);
6571
+ const wnToe = adjustWeek(wnc, view.getUint16(b + 80, true), 1024);
6572
+ const toeGpsMs = gpsMs(wnToe, toes);
6573
+ const leapMs = getGpsLeap(new Date(toeGpsMs)) * 1e3;
6574
+ const tocDate = new Date(toeGpsMs - leapMs);
6575
+ const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
6576
+ const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
6577
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS8) / 1e3 % SEC_PER_WEEK8;
6345
6578
  return {
6346
- system: "C",
6579
+ system: "R",
6347
6580
  prn,
6348
- toc: sowOf2(tocDate.getTime()),
6349
6581
  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
6582
+ // RINEX stores −τn as the clock bias; SBF carries τn (ICD sign)
6583
+ tauN: -view.getFloat32(b + 68, true),
6584
+ gammaN: view.getFloat32(b + 64, true),
6585
+ messageFrameTime,
6586
+ x: view.getFloat64(b + 16, true),
6587
+ // SBF unit is km, like RINEX
6588
+ xDot: view.getFloat32(b + 40, true),
6589
+ xAcc: view.getFloat32(b + 52, true),
6590
+ y: view.getFloat64(b + 24, true),
6591
+ yDot: view.getFloat32(b + 44, true),
6592
+ yAcc: view.getFloat32(b + 56, true),
6593
+ z: view.getFloat64(b + 32, true),
6594
+ zDot: view.getFloat32(b + 48, true),
6595
+ zAcc: view.getFloat32(b + 60, true),
6596
+ // MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
6597
+ health: view.getUint8(b + 85) >> 2,
6598
+ freqNum: view.getUint8(b + 15) - 8
6378
6599
  };
6379
6600
  }
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
6445
- });
6446
- }
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
6601
+ function parseSbfNav(data) {
6602
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6603
+ const ephemerides = [];
6604
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
6605
+ let eph = null;
6606
+ if (id === 5891 && len >= 140) eph = decodeGpsQzsNav(view, b, "G");
6607
+ else if (id === 4095 && len >= 140) eph = decodeGpsQzsNav(view, b, "J");
6608
+ else if (id === 4002 && len >= 149) eph = decodeGalNav(view, b);
6609
+ else if (id === 4081 && len >= 140) eph = decodeBdsNav(view, b);
6610
+ else if (id === 4004 && len >= 96) eph = decodeGloNav(view, b);
6611
+ if (eph) ephemerides.push(eph);
6539
6612
  });
6613
+ return { ephemerides, badCrc };
6540
6614
  }
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);
6561
- }
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 });
6566
- } 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 });
6572
- }
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;
6615
+ var GAL_SQRT_A_NOMINAL = Math.sqrt(296e5);
6616
+ var GAL_I_NOMINAL = 56 / 180 * PI2;
6617
+ var I_REF_03 = 0.3 * PI2;
6618
+ function decodeGpsGalAlm(view, b, sys) {
6619
+ const wnc = view.getUint16(b + 12, true);
6620
+ const toa = view.getUint32(b + 20, true);
6621
+ if (toa === U4_DNU) return null;
6622
+ let prn;
6623
+ let weekAlm;
6624
+ let i0;
6625
+ let sqrtA = view.getFloat32(b + 32, true);
6626
+ let health;
6627
+ if (sys === "G") {
6628
+ prn = view.getUint8(b + 14);
6629
+ if (prn < 1 || prn > 32) return null;
6630
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 256);
6631
+ i0 = I_REF_03 + view.getFloat32(b + 24, true) * PI2;
6632
+ health = view.getUint8(b + 58);
6633
+ } else {
6634
+ prn = view.getUint8(b + 57) - 70;
6635
+ if (prn < 1 || prn > 36) return null;
6636
+ weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 4);
6637
+ i0 = GAL_I_NOMINAL + view.getFloat32(b + 24, true) * PI2;
6638
+ sqrtA += GAL_SQRT_A_NOMINAL;
6639
+ health = view.getUint16(b + 58, true);
6578
6640
  }
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;
6641
+ return {
6642
+ system: sys,
6643
+ prn: `${sys}${String(prn).padStart(2, "0")}`,
6644
+ weekAlm,
6645
+ toaSec: toa,
6646
+ sqrtA,
6647
+ e: view.getFloat32(b + 16, true),
6648
+ i0OrDeltaI: i0,
6649
+ omega0: view.getFloat32(b + 36, true) * PI2,
6650
+ omega: view.getFloat32(b + 40, true) * PI2,
6651
+ m0: view.getFloat32(b + 44, true) * PI2,
6652
+ omegaDot: view.getFloat32(b + 28, true) * PI2,
6653
+ af0: view.getFloat32(b + 52, true),
6654
+ af1: view.getFloat32(b + 48, true),
6655
+ health
6656
+ };
6589
6657
  }
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;
6658
+ function decodeBdsAlm(view, b) {
6659
+ const prnStr = svidToPrn(view.getUint8(b + 14));
6660
+ if (!prnStr || prnStr[0] !== "C") return null;
6661
+ const prn = parseInt(prnStr.slice(1), 10);
6662
+ const toa = view.getUint32(b + 16, true);
6663
+ if (toa === U4_DNU) return null;
6664
+ const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
6665
+ const geo = prn <= 5 || prn >= 59;
6666
+ return {
6667
+ system: "C",
6668
+ prn: prnStr,
6669
+ weekAlm: adjustWeek(bdsWeekRef, view.getUint8(b + 15), 256),
6670
+ toaSec: toa,
6671
+ sqrtA: view.getFloat32(b + 20, true),
6672
+ e: view.getFloat32(b + 24, true),
6673
+ i0OrDeltaI: (geo ? 0 : I_REF_03) + view.getFloat32(b + 44, true) * PI2,
6674
+ omega0: view.getFloat32(b + 36, true) * PI2,
6675
+ omega: view.getFloat32(b + 28, true) * PI2,
6676
+ m0: view.getFloat32(b + 32, true) * PI2,
6677
+ omegaDot: view.getFloat32(b + 40, true) * PI2,
6678
+ af0: view.getFloat32(b + 48, true),
6679
+ af1: view.getFloat32(b + 52, true),
6680
+ health: view.getUint16(b + 56, true)
6681
+ };
6616
6682
  }
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;
6683
+ function decodeGloAlm(view, b) {
6684
+ const prn = svidToPrn(view.getUint8(b + 14));
6685
+ if (!prn || prn[0] !== "R") return null;
6686
+ const wnc = view.getUint16(b + 12, true);
6687
+ const toa = view.getUint32(b + 20, true);
6688
+ if (toa === U4_DNU) return null;
6687
6689
  return {
6688
6690
  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
6691
+ prn,
6692
+ freqNr: view.getUint8(b + 15) - 8,
6693
+ weekAlm: adjustWeek(wnc, view.getUint8(b + 52), 256),
6694
+ toaSec: toa,
6695
+ epsilon: view.getFloat32(b + 16, true),
6696
+ deltaI: view.getFloat32(b + 24, true) * PI2,
6697
+ lambda: view.getFloat32(b + 28, true) * PI2,
6698
+ tLambda: view.getFloat32(b + 32, true),
6699
+ omega: view.getFloat32(b + 36, true) * PI2,
6700
+ deltaT: view.getFloat32(b + 40, true),
6701
+ deltaTDot: view.getFloat32(b + 44, true),
6702
+ tau: view.getFloat32(b + 48, true),
6703
+ health: view.getUint8(b + 53),
6704
+ nDay: view.getUint16(b + 54, true),
6705
+ n4: view.getUint8(b + 57)
6706
+ };
6707
+ }
6708
+ function parseSbfAlmanac(data) {
6709
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6710
+ const almanacs = [];
6711
+ const badCrc = scanSbfFrames(data, view, (id, b, len) => {
6712
+ let alm = null;
6713
+ if (id === 5892 && len >= 60) alm = decodeGpsGalAlm(view, b, "G");
6714
+ else if (id === 4003 && len >= 61) alm = decodeGpsGalAlm(view, b, "E");
6715
+ else if (id === 4119 && len >= 60) alm = decodeBdsAlm(view, b);
6716
+ else if (id === 4005 && len >= 60) alm = decodeGloAlm(view, b);
6717
+ if (alm) almanacs.push(alm);
6718
+ });
6719
+ return { almanacs, badCrc };
6720
+ }
6721
+
6722
+ // src/sbf/iono.ts
6723
+ var F4_DNU2 = -2e10;
6724
+ function parseSbfIonoUtc(data) {
6725
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6726
+ const ionoCorrections = {};
6727
+ let leapSeconds = null;
6728
+ const f4s = (b, n) => {
6729
+ const out = [];
6730
+ for (let k = 0; k < n; k++) {
6731
+ const v = view.getFloat32(b + 4 * k, true);
6732
+ if (v === F4_DNU2) return null;
6733
+ out.push(v);
6734
+ }
6735
+ return out;
6736
+ };
6737
+ scanSbfFrames(data, view, (id, b, len) => {
6738
+ if ((id === 5893 || id === 4120) && len >= 48) {
6739
+ const alpha = f4s(b + 16, 4);
6740
+ const beta = f4s(b + 32, 4);
6741
+ if (!alpha || !beta) return;
6742
+ const sys = id === 5893 ? "GPS" : "BDS";
6743
+ ionoCorrections[`${sys}A`] = alpha;
6744
+ ionoCorrections[`${sys}B`] = beta;
6745
+ } else if (id === 4030 && len >= 28) {
6746
+ const ai = f4s(b + 16, 3);
6747
+ if (ai) ionoCorrections["GAL"] = ai;
6748
+ } else if (id === 5894 && len >= 37) {
6749
+ leapSeconds = view.getInt8(b + 33);
6750
+ }
6751
+ });
6752
+ return { ionoCorrections, leapSeconds };
6753
+ }
6754
+
6755
+ // src/sbf/rawnav.ts
6756
+ var SIGNAL_OF_BLOCK = {
6757
+ 4018: "L2C",
6758
+ 4019: "L5"
6759
+ };
6760
+ function parseSbfCnav(data) {
6761
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6762
+ const ephemerides = [];
6763
+ const assemblers = {
6764
+ L2C: new CnavAssembler(),
6765
+ L5: new CnavAssembler()
6709
6766
  };
6767
+ let badCrc = 0;
6768
+ let messages = 0;
6769
+ scanSbfFrames(data, view, (id, b, len) => {
6770
+ const signal = SIGNAL_OF_BLOCK[id];
6771
+ if (!signal || len < 60) return;
6772
+ messages++;
6773
+ const msg = new Uint8Array(40);
6774
+ for (let k = 0; k < 10; k++) {
6775
+ const w = view.getUint32(b + 20 + 4 * k, true);
6776
+ msg[4 * k] = w >>> 24;
6777
+ msg[4 * k + 1] = w >>> 16 & 255;
6778
+ msg[4 * k + 2] = w >>> 8 & 255;
6779
+ msg[4 * k + 3] = w & 255;
6780
+ }
6781
+ if (!cnavCrcOk(msg)) {
6782
+ badCrc++;
6783
+ return;
6784
+ }
6785
+ const eph = assemblers[signal].push(msg);
6786
+ if (eph) ephemerides.push({ ...eph, signal });
6787
+ });
6788
+ return { ephemerides, badCrc, messages };
6710
6789
  }
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;
6790
+
6791
+ // src/sbf/rawnav-gal.ts
6792
+ var INAV_SOURCES = [17, 21, 22];
6793
+ var FNAV_SOURCES = [20, 22];
6794
+ function parseSbfGalNav(data) {
6795
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
6796
+ const ephemerides = [];
6797
+ const inav = new GalInavAssembler();
6798
+ const fnav = new GalFnavAssembler();
6799
+ let badCrc = 0;
6800
+ let messages = 0;
6801
+ scanSbfFrames(data, view, (id, b, len) => {
6802
+ if (id !== 4022 && id !== 4023 || len < 52) return;
6803
+ messages++;
6804
+ const prnStr = svidToPrn(data[b + 14]);
6805
+ if (!prnStr || prnStr[0] !== "E") return;
6806
+ const prn = parseInt(prnStr.slice(1), 10);
6807
+ const source = data[b + 17] & 31;
6808
+ const page = new Uint8Array(32);
6809
+ for (let k = 0; k < 8; k++) {
6810
+ const w = view.getUint32(b + 20 + 4 * k, true);
6811
+ page[4 * k] = w >>> 24;
6812
+ page[4 * k + 1] = w >>> 16 & 255;
6813
+ page[4 * k + 2] = w >>> 8 & 255;
6814
+ page[4 * k + 3] = w & 255;
6731
6815
  }
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
- };
6816
+ let eph;
6817
+ let src;
6818
+ if (id === 4023) {
6819
+ src = "inav";
6820
+ if (!INAV_SOURCES.includes(source)) return;
6821
+ if (getBitU22(page, 0) !== 0 || getBitU22(page, 114) !== 1) return;
6822
+ if (!galInavPageCrcOk(page)) {
6823
+ badCrc++;
6824
+ return;
6825
+ }
6826
+ eph = inav.push(prn, page);
6827
+ } else {
6828
+ src = "fnav";
6829
+ if (!FNAV_SOURCES.includes(source)) return;
6830
+ if (isFnavDummy(page)) return;
6831
+ if (!galFnavPageCrcOk(page)) {
6832
+ badCrc++;
6833
+ return;
6834
+ }
6835
+ eph = fnav.push(prn, page);
6836
+ }
6837
+ if (eph) ephemerides.push({ ...eph, source: src });
6838
+ });
6839
+ return { ephemerides, badCrc, messages };
6840
+ }
6841
+ function getBitU22(buff, pos) {
6842
+ return buff[pos >> 3] >> 7 - (pos & 7) & 1;
6843
+ }
6844
+ function isFnavDummy(page) {
6845
+ return page[0] >> 2 === 63;
6846
+ }
6743
6847
 
6744
6848
  // src/sbf/rawnav-bds.ts
6745
- var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
6849
+ var GPS_EPOCH_MS9 = Date.UTC(1980, 0, 6);
6746
6850
  var MS_PER_WEEK5 = 7 * 86400 * 1e3;
6747
6851
  var TOW_DNU = 4294967295;
6748
6852
  var WNC_DNU = 65535;
@@ -6812,7 +6916,7 @@ function parseSbfGloNav(data) {
6812
6916
  const eph = assembler.push(
6813
6917
  prn,
6814
6918
  str,
6815
- new Date(GPS_EPOCH_MS8 + wnc * MS_PER_WEEK5 + towMs),
6919
+ new Date(GPS_EPOCH_MS9 + wnc * MS_PER_WEEK5 + towMs),
6816
6920
  view.getUint8(b + 18) - 8
6817
6921
  // FreqNr, offset by 8
6818
6922
  );
@@ -7346,7 +7450,7 @@ function parseSbfHas(data) {
7346
7450
 
7347
7451
  // src/sbf/index.ts
7348
7452
  var CLIGHT = 299792458;
7349
- var GPS_EPOCH_MS9 = Date.UTC(1980, 0, 6);
7453
+ var GPS_EPOCH_MS10 = Date.UTC(1980, 0, 6);
7350
7454
  var MS_PER_WEEK6 = 7 * 864e5;
7351
7455
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
7352
7456
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -7446,23 +7550,23 @@ var MEAS2_SIG = [
7446
7550
  ["J", "1E"],
7447
7551
  ["J", "5P"]
7448
7552
  ];
7449
- var two3 = (n) => String(n).padStart(2, "0");
7553
+ var two4 = (n) => String(n).padStart(2, "0");
7450
7554
  function m3Prn(navsys, svid) {
7451
7555
  switch (navsys) {
7452
7556
  case 0:
7453
- return svid < 32 ? `G${two3(svid + 1)}` : null;
7557
+ return svid < 32 ? `G${two4(svid + 1)}` : null;
7454
7558
  case 1:
7455
- return svid < 27 ? `R${two3(svid + 1)}` : null;
7559
+ return svid < 27 ? `R${two4(svid + 1)}` : null;
7456
7560
  case 2:
7457
- return svid < 36 ? `E${two3(svid + 1)}` : null;
7561
+ return svid < 36 ? `E${two4(svid + 1)}` : null;
7458
7562
  case 3:
7459
- return svid < 50 ? `C${two3(svid + 1)}` : null;
7563
+ return svid < 50 ? `C${two4(svid + 1)}` : null;
7460
7564
  case 4:
7461
- return svid <= 38 ? `S${two3(svid + 20)}` : null;
7565
+ return svid <= 38 ? `S${two4(svid + 20)}` : null;
7462
7566
  case 5:
7463
- return svid < 10 ? `J${two3(svid + 1)}` : null;
7567
+ return svid < 10 ? `J${two4(svid + 1)}` : null;
7464
7568
  case 6:
7465
- return svid < 14 ? `I${two3(svid + 1)}` : null;
7569
+ return svid < 14 ? `I${two4(svid + 1)}` : null;
7466
7570
  default:
7467
7571
  return null;
7468
7572
  }
@@ -8000,7 +8104,7 @@ function parseSbfMeas(data) {
8000
8104
  const tow = view.getUint32(i + 8, true);
8001
8105
  const wnc = view.getUint16(i + 12, true);
8002
8106
  if (tow !== 4294967295 && wnc !== 65535) {
8003
- ensureEpoch(GPS_EPOCH_MS9 + wnc * MS_PER_WEEK6 + tow);
8107
+ ensureEpoch(GPS_EPOCH_MS10 + wnc * MS_PER_WEEK6 + tow);
8004
8108
  if (id === 4109) decodeMeas3Ranges(i, tow);
8005
8109
  else if (id === 4110) decodeMeas3CN0(i, len);
8006
8110
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -8065,15 +8169,15 @@ var ID_GALEPHEMERIS = 1122;
8065
8169
  var ID_QZSSEPHEMERIS = 1336;
8066
8170
  var ID_GPSEPHEM = 7;
8067
8171
  var ID_BDSEPHEMERIS = 1696;
8068
- var GPS_EPOCH_MS10 = Date.UTC(1980, 0, 6);
8172
+ var GPS_EPOCH_MS11 = Date.UTC(1980, 0, 6);
8069
8173
  var BDT_EPOCH_MS3 = Date.UTC(2006, 0, 1);
8070
- var SEC_PER_WEEK8 = 7 * 86400;
8174
+ var SEC_PER_WEEK9 = 7 * 86400;
8071
8175
  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;
8176
+ var MS_PER_WEEK7 = SEC_PER_WEEK9 * 1e3;
8177
+ var gpsMs2 = (week, sec) => GPS_EPOCH_MS11 + week * MS_PER_WEEK7 + sec * 1e3;
8178
+ var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK9;
8075
8179
  function nearWeekMs(refMs, sow) {
8076
- const refWeek = Math.floor((refMs - GPS_EPOCH_MS10) / MS_PER_WEEK7);
8180
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS11) / MS_PER_WEEK7);
8077
8181
  let ms = gpsMs2(refWeek, sow);
8078
8182
  if (ms < refMs - MS_PER_WEEK7 / 2) ms += MS_PER_WEEK7;
8079
8183
  else if (ms > refMs + MS_PER_WEEK7 / 2) ms -= MS_PER_WEEK7;
@@ -8100,9 +8204,9 @@ function decodeGloEphemeris(view, p) {
8100
8204
  tof += Math.floor(tow / SEC_PER_DAY2) * SEC_PER_DAY2;
8101
8205
  if (tof < tow - 43200) tof += SEC_PER_DAY2;
8102
8206
  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;
8207
+ const toeGpsMs = GPS_EPOCH_MS11 + (week * SEC_PER_WEEK9 + tow) * 1e3;
8208
+ const tofGpsMs = GPS_EPOCH_MS11 + (week * SEC_PER_WEEK9 + tof) * 1e3;
8209
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS11) / 1e3;
8106
8210
  return {
8107
8211
  system: "R",
8108
8212
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -8110,7 +8214,7 @@ function decodeGloEphemeris(view, p) {
8110
8214
  tauN: -view.getFloat64(p + 100, true),
8111
8215
  // RINEX stores −τ_n
8112
8216
  gammaN: view.getFloat64(p + 116, true),
8113
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK8 + SEC_PER_WEEK8) % SEC_PER_WEEK8,
8217
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK9 + SEC_PER_WEEK9) % SEC_PER_WEEK9,
8114
8218
  x: view.getFloat64(p + 28, true) / 1e3,
8115
8219
  y: view.getFloat64(p + 36, true) / 1e3,
8116
8220
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -8167,7 +8271,7 @@ function decodeGalEphemeris(view, p, headerMs) {
8167
8271
  omega: view.getFloat64(p + 60, true),
8168
8272
  omegaDot: view.getFloat64(p + 140, true),
8169
8273
  idot: view.getFloat64(p + 124, true),
8170
- week: Math.floor((toeMs - GPS_EPOCH_MS10) / MS_PER_WEEK7),
8274
+ week: Math.floor((toeMs - GPS_EPOCH_MS11) / MS_PER_WEEK7),
8171
8275
  // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
8172
8276
  // 3-5, E5b in 6-8) — same packing as RTKLIB.
8173
8277
  svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
@@ -8339,7 +8443,7 @@ function parseNovatelNav(data) {
8339
8443
  }
8340
8444
 
8341
8445
  // src/novatel/index.ts
8342
- var GPS_EPOCH_MS11 = Date.UTC(1980, 0, 6);
8446
+ var GPS_EPOCH_MS12 = Date.UTC(1980, 0, 6);
8343
8447
  var MS_PER_WEEK8 = 7 * 864e5;
8344
8448
  var HDR = 28;
8345
8449
  var ID_RANGE = 43;
@@ -8509,7 +8613,7 @@ function parseNovatelRange(data) {
8509
8613
  if (!codes.includes(code)) codes.push(code);
8510
8614
  }
8511
8615
  epochs.push({
8512
- timeMs: GPS_EPOCH_MS11 + week * MS_PER_WEEK8 + towMs,
8616
+ timeMs: GPS_EPOCH_MS12 + week * MS_PER_WEEK8 + towMs,
8513
8617
  meas
8514
8618
  });
8515
8619
  }
@@ -8561,7 +8665,7 @@ function parseNovatelRange(data) {
8561
8665
  if (!codes.includes(code)) codes.push(code);
8562
8666
  }
8563
8667
  epochs.push({
8564
- timeMs: GPS_EPOCH_MS11 + week * MS_PER_WEEK8 + towMs,
8668
+ timeMs: GPS_EPOCH_MS12 + week * MS_PER_WEEK8 + towMs,
8565
8669
  meas
8566
8670
  });
8567
8671
  }
@@ -8575,7 +8679,7 @@ function parseNovatelRange(data) {
8575
8679
  var GM_GPS = 3986005e8;
8576
8680
  var GM_GAL = 3986004418e5;
8577
8681
  var GM_BDS = 3986004418e5;
8578
- var GPS_EPOCH_MS12 = Date.UTC(1980, 0, 6);
8682
+ var GPS_EPOCH_MS13 = Date.UTC(1980, 0, 6);
8579
8683
  var BDT_EPOCH_MS4 = Date.UTC(2006, 0, 1);
8580
8684
  var WEEK_MS = 6048e5;
8581
8685
  var BDT_MINUS_GPST_MS = 14e3;
@@ -8584,7 +8688,7 @@ function almanacEpochMs(alm) {
8584
8688
  if (alm.system === "C") {
8585
8689
  return BDT_EPOCH_MS4 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3 + BDT_MINUS_GPST_MS;
8586
8690
  }
8587
- return GPS_EPOCH_MS12 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
8691
+ return GPS_EPOCH_MS13 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
8588
8692
  }
8589
8693
  function almanacSatPosition(alm, timeMs) {
8590
8694
  const GM = alm.system === "E" ? GM_GAL : alm.system === "C" ? GM_BDS : GM_GPS;
@@ -8750,8 +8854,7 @@ var GM_GPS2 = 3986005e8;
8750
8854
  var GM_GAL2 = 3986004418e5;
8751
8855
  var GM_BDS2 = 3986004418e5;
8752
8856
  var GM_GLO = 39860044e7;
8753
- var BDS_GEO_MAX_INCLINATION_RAD = 0.1;
8754
- var BDS_GEO_MIN_SEMIMAJOR_AXIS_M = 4e7;
8857
+ var BDS_GEO_PRNS2 = /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 59, 60, 61, 62, 63]);
8755
8858
  var AE_GLO = 6378136;
8756
8859
  var J2_GLO = 108263e-8;
8757
8860
  var TWO_PI = 2 * Math.PI;
@@ -8798,7 +8901,7 @@ function keplerPosition(eph, t) {
8798
8901
  const yp = rk * Math.sin(uk);
8799
8902
  const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
8800
8903
  const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
8801
- const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
8904
+ const isBdsGeo = eph.system === "C" && BDS_GEO_PRNS2.has(Number(eph.prn.slice(1)));
8802
8905
  if (isBdsGeo) {
8803
8906
  const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
8804
8907
  const cosO2 = Math.cos(omegak2);
@@ -9094,10 +9197,10 @@ function selectBest(ephs, timeMs) {
9094
9197
  }
9095
9198
  return best;
9096
9199
  }
9097
- var GPS_EPOCH_MS13 = START_GPS_TIME.getTime();
9200
+ var GPS_EPOCH_MS14 = START_GPS_TIME.getTime();
9098
9201
  var MS_PER_WEEK9 = 7 * 864e5;
9099
9202
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
9100
- var GAL_EPOCH_MS = GPS_EPOCH_MS13 + 1024 * MS_PER_WEEK9;
9203
+ var GAL_EPOCH_MS = GPS_EPOCH_MS14 + 1024 * MS_PER_WEEK9;
9101
9204
  function ephInfoToEphemeris(info) {
9102
9205
  const sys = info.prn.charAt(0);
9103
9206
  if (sys === "R") {
@@ -9148,8 +9251,8 @@ function ephInfoToEphemeris(info) {
9148
9251
  } else if (sys === "E") {
9149
9252
  epochMs = GAL_EPOCH_MS;
9150
9253
  } else {
9151
- epochMs = GPS_EPOCH_MS13;
9152
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS13) / MS_PER_WEEK9;
9254
+ epochMs = GPS_EPOCH_MS14;
9255
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS14) / MS_PER_WEEK9;
9153
9256
  week += 1024 * Math.round((weeksNow - week) / 1024);
9154
9257
  }
9155
9258
  const tocDate = new Date(epochMs + week * MS_PER_WEEK9 + tocSec * 1e3);
@@ -9577,6 +9680,706 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
9577
9680
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
9578
9681
  }
9579
9682
 
9683
+ // src/positioning/rtk.ts
9684
+ var L1_CODES = {
9685
+ G: ["1C"],
9686
+ E: ["1C"],
9687
+ R: ["1C"],
9688
+ J: ["1C"],
9689
+ C: ["2I", "1P"]
9690
+ // B1I first (classic B1), then B1C
9691
+ };
9692
+ function toRtkEpoch(meas) {
9693
+ const out = /* @__PURE__ */ new Map();
9694
+ const rank = /* @__PURE__ */ new Map();
9695
+ for (const m of meas) {
9696
+ const prefs = L1_CODES[m.prn[0]];
9697
+ if (!prefs || m.pr === null || !Number.isFinite(m.pr)) continue;
9698
+ const r = prefs.indexOf(m.code);
9699
+ if (r < 0) continue;
9700
+ const prev = rank.get(m.prn);
9701
+ if (prev !== void 0 && prev <= r) continue;
9702
+ rank.set(m.prn, r);
9703
+ out.set(m.prn, {
9704
+ code: m.code,
9705
+ pr: m.pr,
9706
+ cp: m.cp ?? null,
9707
+ lockTimeMs: m.lockTimeS !== void 0 ? Math.round(m.lockTimeS * 1e3) : void 0,
9708
+ gloChannel: m.gloChannel ?? null
9709
+ });
9710
+ }
9711
+ return out;
9712
+ }
9713
+ function carrierFreqHz(sys, code, gloChannel) {
9714
+ const band = code[0];
9715
+ switch (sys) {
9716
+ case "G":
9717
+ case "J":
9718
+ return band === "1" ? 157542e4 : band === "2" ? 12276e5 : 117645e4;
9719
+ case "E":
9720
+ return band === "1" ? 157542e4 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : band === "8" ? 1191795e3 : 127875e4;
9721
+ case "C":
9722
+ return band === "1" ? 157542e4 : band === "2" ? 1561098e3 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : 126852e4;
9723
+ case "R": {
9724
+ if (gloChannel === null) return 0;
9725
+ if (band === "1") return 1602e6 + gloChannel * 562500;
9726
+ if (band === "2") return 1246e6 + gloChannel * 437500;
9727
+ return 1202025e3;
9728
+ }
9729
+ default:
9730
+ return 0;
9731
+ }
9732
+ }
9733
+ function sagnac(pos, travelTimeS) {
9734
+ const theta = OMEGA_E * travelTimeS;
9735
+ const c = Math.cos(theta);
9736
+ const s = Math.sin(theta);
9737
+ return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
9738
+ }
9739
+ function tropoDelay(elevationRad) {
9740
+ const sinEl = Math.sin(elevationRad);
9741
+ return 2.47 / (sinEl + 0.0121);
9742
+ }
9743
+ function resolveEphemeris(src, prn, timeMs) {
9744
+ if (Array.isArray(src))
9745
+ return selectEphemeris(src, prn, timeMs);
9746
+ return src.get(prn) ?? null;
9747
+ }
9748
+ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere) {
9749
+ const out = [];
9750
+ for (const [prn, mR] of rover) {
9751
+ const sys = prn[0];
9752
+ if (!"GERCJ".includes(sys)) continue;
9753
+ const mB = base.get(prn);
9754
+ if (!mB || mB.code !== mR.code) continue;
9755
+ if (mR.pr === null || mB.pr === null || !Number.isFinite(mR.pr) || !Number.isFinite(mB.pr))
9756
+ continue;
9757
+ const eph = resolveEphemeris(ephemerides, prn, timeMs);
9758
+ if (!eph) continue;
9759
+ const satAt = (pr) => {
9760
+ const tTx = timeMs - pr / C_LIGHT * 1e3;
9761
+ const dts = satClockCorrection(eph, tTx);
9762
+ const sat = computeSatPosition(eph, tTx - dts * 1e3);
9763
+ return Number.isFinite(sat.x) ? sat : null;
9764
+ };
9765
+ const satB = satAt(mB.pr);
9766
+ const satR = satAt(mR.pr);
9767
+ if (!satB || !satR) continue;
9768
+ const travelB = Math.hypot(
9769
+ satB.x - basePos[0],
9770
+ satB.y - basePos[1],
9771
+ satB.z - basePos[2]
9772
+ ) / C_LIGHT;
9773
+ const [bx, by, bz] = sagnac(satB, travelB);
9774
+ const rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
9775
+ const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
9776
+ if (elB < maskRad) continue;
9777
+ const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
9778
+ const freq = carrierFreqHz(sys, mR.code, gloK);
9779
+ out.push({
9780
+ prn,
9781
+ group: sys + mR.code,
9782
+ lambda: freq > 0 ? C_LIGHT / freq : 0,
9783
+ satR,
9784
+ rhoB,
9785
+ elB,
9786
+ tropoB: troposphere ? tropoDelay(elB) : 0,
9787
+ prR: mR.pr,
9788
+ prB: mB.pr,
9789
+ cpR: mR.cp ?? null,
9790
+ cpB: mB.cp ?? null,
9791
+ lockR: mR.lockTimeMs,
9792
+ lockB: mB.lockTimeMs
9793
+ });
9794
+ }
9795
+ return out;
9796
+ }
9797
+ function roverTerms(g, x, y, z, troposphere) {
9798
+ const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
9799
+ const [sx, sy, sz] = sagnac(g.satR, travel);
9800
+ const rho = Math.hypot(sx - x, sy - y, sz - z);
9801
+ const u = [
9802
+ (x - sx) / rho,
9803
+ (y - sy) / rho,
9804
+ (z - sz) / rho
9805
+ ];
9806
+ let dTropo = 0;
9807
+ if (troposphere) {
9808
+ const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
9809
+ dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
9810
+ }
9811
+ return { rho, u, dTropo };
9812
+ }
9813
+ function sdVariance(sigmaM, elRad) {
9814
+ const sinEl = Math.max(Math.sin(elRad), 0.05);
9815
+ return 2 * sigmaM * sigmaM * (1 + 1 / (sinEl * sinEl));
9816
+ }
9817
+ function zeros(n, m) {
9818
+ return Array.from({ length: n }, () => new Array(m).fill(0));
9819
+ }
9820
+ function matInv(A) {
9821
+ const n = A.length;
9822
+ const M = A.map((row, i) => {
9823
+ const r = [...row, ...new Array(n).fill(0)];
9824
+ r[n + i] = 1;
9825
+ return r;
9826
+ });
9827
+ for (let col = 0; col < n; col++) {
9828
+ let pivot = col;
9829
+ for (let r = col + 1; r < n; r++) {
9830
+ if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
9831
+ }
9832
+ if (Math.abs(M[pivot][col]) < 1e-13) return null;
9833
+ [M[col], M[pivot]] = [M[pivot], M[col]];
9834
+ const d = M[col][col];
9835
+ for (let c = 0; c < 2 * n; c++) M[col][c] /= d;
9836
+ for (let r = 0; r < n; r++) {
9837
+ if (r === col) continue;
9838
+ const f = M[r][col];
9839
+ if (f === 0) continue;
9840
+ for (let c = 0; c < 2 * n; c++) M[r][c] -= f * M[col][c];
9841
+ }
9842
+ }
9843
+ return M.map((row) => row.slice(n));
9844
+ }
9845
+ function matMul(A, B) {
9846
+ const n = A.length;
9847
+ const k = B.length;
9848
+ const m = B[0]?.length ?? 0;
9849
+ const C = zeros(n, m);
9850
+ for (let i = 0; i < n; i++) {
9851
+ for (let l = 0; l < k; l++) {
9852
+ const a = A[i][l];
9853
+ if (a === 0) continue;
9854
+ for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
9855
+ }
9856
+ }
9857
+ return C;
9858
+ }
9859
+ function transpose(A) {
9860
+ const n = A.length;
9861
+ const m = A[0]?.length ?? 0;
9862
+ const T = zeros(m, n);
9863
+ for (let i = 0; i < n; i++) for (let j = 0; j < m; j++) T[j][i] = A[i][j];
9864
+ return T;
9865
+ }
9866
+ function groupEntries(geom) {
9867
+ const groups = /* @__PURE__ */ new Map();
9868
+ for (const g of geom) {
9869
+ const list = groups.get(g.group);
9870
+ if (list) list.push(g);
9871
+ else groups.set(g.group, [g]);
9872
+ }
9873
+ return groups;
9874
+ }
9875
+ function ddCovariance(rows, sigmaCode, sigmaPhase) {
9876
+ const n = rows.length;
9877
+ const R = zeros(n, n);
9878
+ const sd2 = (r, g) => sdVariance(r.kind === "code" ? sigmaCode : sigmaPhase, g.elB);
9879
+ for (let i = 0; i < n; i++) {
9880
+ const ri = rows[i];
9881
+ R[i][i] = sd2(ri, ri.g) + sd2(ri, ri.ref);
9882
+ for (let j = i + 1; j < n; j++) {
9883
+ const rj = rows[j];
9884
+ if (ri.ref.prn === rj.ref.prn && ri.kind === rj.kind && ri.g.group === rj.g.group) {
9885
+ R[i][j] = R[j][i] = sd2(ri, ri.ref);
9886
+ }
9887
+ }
9888
+ }
9889
+ return R;
9890
+ }
9891
+ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
9892
+ const {
9893
+ elevationMaskDeg = 10,
9894
+ troposphere = true,
9895
+ maxIterations = 15,
9896
+ convergenceM = 1e-4,
9897
+ codeSigmaM = 0.3,
9898
+ rejectThresholdM = 20,
9899
+ initialPosition
9900
+ } = opts;
9901
+ let geom = buildGeometry(
9902
+ rover,
9903
+ base,
9904
+ basePos,
9905
+ ephemerides,
9906
+ timeMs,
9907
+ elevationMaskDeg * Math.PI / 180,
9908
+ troposphere
9909
+ );
9910
+ const rejected = [];
9911
+ for (; ; ) {
9912
+ const groups = groupEntries(geom);
9913
+ const rows = [];
9914
+ const refSatellites = {};
9915
+ for (const [group, list] of groups) {
9916
+ if (list.length < 2) continue;
9917
+ const ref = list.reduce((a, b) => b.elB > a.elB ? b : a);
9918
+ refSatellites[group] = ref.prn;
9919
+ for (const g of list) {
9920
+ if (g === ref) continue;
9921
+ rows.push({
9922
+ g,
9923
+ ref,
9924
+ kind: "code",
9925
+ z: g.prR - g.prB - (ref.prR - ref.prB)
9926
+ });
9927
+ }
9928
+ }
9929
+ if (rows.length < 3) return null;
9930
+ const W = matInv(ddCovariance(rows, codeSigmaM, codeSigmaM));
9931
+ if (!W) return null;
9932
+ let [x, y, z] = initialPosition ?? basePos;
9933
+ let iterations = 0;
9934
+ let converged = false;
9935
+ const residuals = {};
9936
+ let cov = null;
9937
+ for (let iter = 0; iter < maxIterations; iter++) {
9938
+ iterations = iter + 1;
9939
+ const H2 = [];
9940
+ const v = [];
9941
+ const terms = /* @__PURE__ */ new Map();
9942
+ const termOf = (g) => {
9943
+ let t = terms.get(g.prn);
9944
+ if (!t) {
9945
+ t = roverTerms(g, x, y, z, troposphere);
9946
+ terms.set(g.prn, t);
9947
+ }
9948
+ return t;
9949
+ };
9950
+ for (const row of rows) {
9951
+ const ts = termOf(row.g);
9952
+ const tr = termOf(row.ref);
9953
+ const pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
9954
+ v.push(row.z - pred);
9955
+ H2.push([ts.u[0] - tr.u[0], ts.u[1] - tr.u[1], ts.u[2] - tr.u[2]]);
9956
+ }
9957
+ const Ht = transpose(H2);
9958
+ const HtW = matMul(Ht, W);
9959
+ const N = matMul(HtW, H2);
9960
+ const b = matMul(
9961
+ HtW,
9962
+ v.map((s) => [s])
9963
+ );
9964
+ const Ninv = matInv(N);
9965
+ if (!Ninv) return null;
9966
+ const dx = matMul(
9967
+ Ninv,
9968
+ b.map((r) => [r[0]])
9969
+ );
9970
+ x += dx[0][0];
9971
+ y += dx[1][0];
9972
+ z += dx[2][0];
9973
+ cov = Ninv;
9974
+ rows.forEach((row, i) => {
9975
+ residuals[row.g.prn] = v[i] - (H2[i][0] * dx[0][0] + H2[i][1] * dx[1][0] + H2[i][2] * dx[2][0]);
9976
+ });
9977
+ if (Math.hypot(dx[0][0], dx[1][0], dx[2][0]) < convergenceM) {
9978
+ converged = true;
9979
+ break;
9980
+ }
9981
+ }
9982
+ let worst = null;
9983
+ let worstAbs = rejectThresholdM;
9984
+ for (const [prn, r] of Object.entries(residuals)) {
9985
+ if (Math.abs(r) > worstAbs) {
9986
+ worstAbs = Math.abs(r);
9987
+ worst = prn;
9988
+ }
9989
+ }
9990
+ if (worst && geom.length > 4) {
9991
+ rejected.push(worst);
9992
+ geom = geom.filter((g) => g.prn !== worst);
9993
+ continue;
9994
+ }
9995
+ const usedSatellites = [
9996
+ ...new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn]))
9997
+ ].sort();
9998
+ return {
9999
+ position: [x, y, z],
10000
+ baseline: [x - basePos[0], y - basePos[1], z - basePos[2]],
10001
+ sigmas: cov ? [
10002
+ Math.sqrt(Math.max(cov[0][0], 0)),
10003
+ Math.sqrt(Math.max(cov[1][1], 0)),
10004
+ Math.sqrt(Math.max(cov[2][2], 0))
10005
+ ] : [0, 0, 0],
10006
+ usedSatellites,
10007
+ refSatellites,
10008
+ residuals,
10009
+ rejectedSatellites: rejected,
10010
+ nSats: usedSatellites.length,
10011
+ iterations,
10012
+ converged
10013
+ };
10014
+ }
10015
+ }
10016
+ var RtkFloatEngine = class {
10017
+ basePos;
10018
+ ephemerides;
10019
+ o;
10020
+ /** State vector [x, y, z, N₁…] and covariance; null before init. */
10021
+ x = null;
10022
+ P = [];
10023
+ amb = [];
10024
+ refs = /* @__PURE__ */ new Map();
10025
+ track = /* @__PURE__ */ new Map();
10026
+ lastMs = null;
10027
+ constructor(basePos, ephemerides, opts = {}) {
10028
+ this.basePos = basePos;
10029
+ this.ephemerides = ephemerides;
10030
+ this.o = {
10031
+ mode: opts.mode ?? "kinematic",
10032
+ elevationMaskDeg: opts.elevationMaskDeg ?? 10,
10033
+ troposphere: opts.troposphere ?? true,
10034
+ codeSigmaM: opts.codeSigmaM ?? 0.3,
10035
+ phaseSigmaM: opts.phaseSigmaM ?? 3e-3,
10036
+ processNoisePosM: opts.processNoisePosM ?? 0,
10037
+ kinematicSigmaM: opts.kinematicSigmaM ?? 30,
10038
+ ambProcessNoiseCycles: opts.ambProcessNoiseCycles ?? 1e-4,
10039
+ ambInitSigmaCycles: opts.ambInitSigmaCycles ?? 30,
10040
+ maxGapMs: opts.maxGapMs ?? 1e4,
10041
+ codeGateM: opts.codeGateM ?? 30,
10042
+ slipGateM: opts.slipGateM ?? 5,
10043
+ updateIterations: opts.updateIterations ?? 2
10044
+ };
10045
+ }
10046
+ /** Clear all filter state (position, ambiguities, lock history). */
10047
+ reset() {
10048
+ this.x = null;
10049
+ this.P = [];
10050
+ this.amb = [];
10051
+ this.refs.clear();
10052
+ this.track.clear();
10053
+ this.lastMs = null;
10054
+ }
10055
+ ambIndex(prn) {
10056
+ return this.amb.findIndex((a) => a.prn === prn);
10057
+ }
10058
+ /** Remove one ambiguity state (row/col) from x and P. */
10059
+ dropAmb(idx) {
10060
+ const s = 3 + idx;
10061
+ this.amb.splice(idx, 1);
10062
+ this.x.splice(s, 1);
10063
+ this.P.splice(s, 1);
10064
+ for (const row of this.P) row.splice(s, 1);
10065
+ }
10066
+ /** Append an ambiguity state with the given value and variance. */
10067
+ addAmb(entry, value, variance) {
10068
+ this.amb.push(entry);
10069
+ this.x.push(value);
10070
+ const n = this.x.length;
10071
+ for (const row of this.P) row.push(0);
10072
+ const newRow = new Array(n).fill(0);
10073
+ newRow[n - 1] = variance;
10074
+ this.P.push(newRow);
10075
+ }
10076
+ /**
10077
+ * Re-map a group's DD ambiguities from the old reference r to the
10078
+ * new reference r' (which must hold a state): N_i' = N_i − N_r' and
10079
+ * the old reference's slot becomes N_r = −N_r'. Exact linear
10080
+ * transform of state and covariance (P' = T P Tᵀ).
10081
+ */
10082
+ retarget(group, oldRef, newRefIdx) {
10083
+ const n = this.x.length;
10084
+ const j = 3 + newRefIdx;
10085
+ const T = zeros(n, n);
10086
+ for (let i = 0; i < n; i++) T[i][i] = 1;
10087
+ for (let k = 0; k < this.amb.length; k++) {
10088
+ if (this.amb[k].group !== group) continue;
10089
+ const s = 3 + k;
10090
+ if (s === j) T[s][s] = -1;
10091
+ else T[s][j] -= 1;
10092
+ }
10093
+ this.x = matMul(
10094
+ T,
10095
+ this.x.map((v) => [v])
10096
+ ).map((r) => r[0]);
10097
+ this.P = matMul(matMul(T, this.P), transpose(T));
10098
+ const entry = this.amb[newRefIdx];
10099
+ entry.prn = oldRef;
10100
+ }
10101
+ /**
10102
+ * Process one synchronized epoch (same nominal `timeMs` for both
10103
+ * receivers). Returns null until a first position can be
10104
+ * initialised (via an internal DGNSS solve) or when the epoch has
10105
+ * fewer than 3 usable DD rows.
10106
+ */
10107
+ process(rover, base, timeMs) {
10108
+ const o = this.o;
10109
+ const geom = buildGeometry(
10110
+ rover,
10111
+ base,
10112
+ this.basePos,
10113
+ this.ephemerides,
10114
+ timeMs,
10115
+ o.elevationMaskDeg * Math.PI / 180,
10116
+ o.troposphere
10117
+ );
10118
+ const byPrn = new Map(geom.map((g) => [g.prn, g]));
10119
+ const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
10120
+ if (this.x) {
10121
+ for (let i = this.amb.length - 1; i >= 0; i--) {
10122
+ const prn = this.amb[i].prn;
10123
+ const tr = this.track.get(prn);
10124
+ const seen = byPrn.get(prn);
10125
+ const hasPhase = seen && seen.cpR !== null && seen.cpB !== null;
10126
+ const staleMs = tr ? timeMs - tr.lastMs : Infinity;
10127
+ if (!hasPhase && staleMs > o.maxGapMs) this.dropAmb(i);
10128
+ }
10129
+ }
10130
+ const slipped = /* @__PURE__ */ new Set();
10131
+ for (const g of geom) {
10132
+ if (g.cpR === null || g.cpB === null) continue;
10133
+ const tr = this.track.get(g.prn);
10134
+ if (!tr) continue;
10135
+ if (timeMs - tr.lastMs > o.maxGapMs) slipped.add(g.prn);
10136
+ else if (g.lockR !== void 0 && tr.lockR !== void 0 && g.lockR < tr.lockR || g.lockB !== void 0 && tr.lockB !== void 0 && g.lockB < tr.lockB)
10137
+ slipped.add(g.prn);
10138
+ }
10139
+ if (!this.x) {
10140
+ const dg = solveDgnss(
10141
+ rover,
10142
+ base,
10143
+ this.basePos,
10144
+ this.ephemerides,
10145
+ timeMs,
10146
+ {
10147
+ elevationMaskDeg: o.elevationMaskDeg,
10148
+ troposphere: o.troposphere,
10149
+ codeSigmaM: o.codeSigmaM
10150
+ }
10151
+ );
10152
+ if (!dg) return null;
10153
+ this.x = [...dg.position];
10154
+ this.P = zeros(3, 3);
10155
+ for (let i = 0; i < 3; i++) {
10156
+ const s = Math.max(dg.sigmas[i], 1);
10157
+ this.P[i][i] = 25 * s * s;
10158
+ }
10159
+ this.amb = [];
10160
+ } else if (o.mode === "kinematic") {
10161
+ for (let i = 0; i < 3; i++) {
10162
+ for (let j = 0; j < this.x.length; j++) {
10163
+ this.P[i][j] = 0;
10164
+ this.P[j][i] = 0;
10165
+ }
10166
+ this.P[i][i] = o.kinematicSigmaM * o.kinematicSigmaM;
10167
+ }
10168
+ } else {
10169
+ const q = o.processNoisePosM * o.processNoisePosM * dtS;
10170
+ for (let i = 0; i < 3; i++) this.P[i][i] += q;
10171
+ }
10172
+ const qAmb = o.ambProcessNoiseCycles * o.ambProcessNoiseCycles * dtS;
10173
+ for (let k = 0; k < this.amb.length; k++) this.P[3 + k][3 + k] += qAmb;
10174
+ const groups = groupEntries(geom);
10175
+ const newRefs = /* @__PURE__ */ new Map();
10176
+ for (const [group, list] of groups) {
10177
+ if (list.length < 2) continue;
10178
+ const phase = list.filter(
10179
+ (g) => g.cpR !== null && g.cpB !== null && !slipped.has(g.prn)
10180
+ );
10181
+ const pool = phase.length >= 2 ? phase : list;
10182
+ const prevRef = this.refs.get(group);
10183
+ const best = pool.reduce((a, b) => b.elB > a.elB ? b : a);
10184
+ let ref = best;
10185
+ if (best.prn !== prevRef) {
10186
+ const anyState = this.amb.some((a) => a.group === group);
10187
+ if (anyState) {
10188
+ if (this.ambIndex(best.prn) >= 0) {
10189
+ this.retarget(group, prevRef ?? best.prn, this.ambIndex(best.prn));
10190
+ } else if (prevRef && pool.some((g) => g.prn === prevRef)) {
10191
+ ref = pool.find((g) => g.prn === prevRef);
10192
+ } else {
10193
+ for (let i = this.amb.length - 1; i >= 0; i--)
10194
+ if (this.amb[i].group === group) this.dropAmb(i);
10195
+ }
10196
+ }
10197
+ }
10198
+ newRefs.set(group, ref);
10199
+ this.refs.set(group, ref.prn);
10200
+ }
10201
+ for (const group of [...this.refs.keys()])
10202
+ if (!newRefs.has(group)) this.refs.delete(group);
10203
+ for (const prn of slipped) {
10204
+ const i = this.ambIndex(prn);
10205
+ if (i >= 0) this.dropAmb(i);
10206
+ }
10207
+ const initAmb = (g, ref) => {
10208
+ if (g.lambda <= 0 || ref.lambda <= 0) return;
10209
+ const zPhi = g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB);
10210
+ const zP = g.prR - g.prB - (ref.prR - ref.prB);
10211
+ const n0 = (zPhi - zP) / g.lambda;
10212
+ this.addAmb(
10213
+ { prn: g.prn, group: g.group, lambda: g.lambda },
10214
+ n0,
10215
+ o.ambInitSigmaCycles * o.ambInitSigmaCycles
10216
+ );
10217
+ };
10218
+ for (const [group, ref] of newRefs) {
10219
+ for (const g of groups.get(group)) {
10220
+ if (g === ref || g.cpR === null || g.cpB === null) continue;
10221
+ if (ref.cpR === null || ref.cpB === null) continue;
10222
+ const i = this.ambIndex(g.prn);
10223
+ if (i >= 0) {
10224
+ this.amb[i].lambda = g.lambda;
10225
+ this.amb[i].group = g.group;
10226
+ continue;
10227
+ }
10228
+ initAmb(g, ref);
10229
+ }
10230
+ }
10231
+ for (let i = this.amb.length - 1; i >= 0; i--) {
10232
+ const a = this.amb[i];
10233
+ if (newRefs.get(a.group)?.prn === a.prn) this.dropAmb(i);
10234
+ }
10235
+ let rows = [];
10236
+ for (const [group, ref] of newRefs) {
10237
+ for (const g of groups.get(group)) {
10238
+ if (g === ref) continue;
10239
+ rows.push({
10240
+ g,
10241
+ ref,
10242
+ kind: "code",
10243
+ z: g.prR - g.prB - (ref.prR - ref.prB),
10244
+ ambIdx: -1
10245
+ });
10246
+ const i = this.ambIndex(g.prn);
10247
+ if (i >= 0 && g.cpR !== null && g.cpB !== null && ref.cpR !== null && ref.cpB !== null && g.lambda > 0 && ref.lambda > 0) {
10248
+ rows.push({
10249
+ g,
10250
+ ref,
10251
+ kind: "phase",
10252
+ z: g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB),
10253
+ ambIdx: i
10254
+ });
10255
+ }
10256
+ }
10257
+ }
10258
+ if (rows.length < 3) {
10259
+ this.lastMs = timeMs;
10260
+ return null;
10261
+ }
10262
+ const predict = (row, xs) => {
10263
+ const ts = roverTerms(row.g, xs[0], xs[1], xs[2], o.troposphere);
10264
+ const tr = roverTerms(row.ref, xs[0], xs[1], xs[2], o.troposphere);
10265
+ let pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
10266
+ const h = new Array(this.x.length).fill(0);
10267
+ h[0] = ts.u[0] - tr.u[0];
10268
+ h[1] = ts.u[1] - tr.u[1];
10269
+ h[2] = ts.u[2] - tr.u[2];
10270
+ if (row.ambIdx >= 0) {
10271
+ pred += this.amb[row.ambIdx].lambda * this.x[3 + row.ambIdx];
10272
+ h[3 + row.ambIdx] = this.amb[row.ambIdx].lambda;
10273
+ }
10274
+ return { pred, h };
10275
+ };
10276
+ const dropPrns = /* @__PURE__ */ new Set();
10277
+ const codeInn = /* @__PURE__ */ new Map();
10278
+ for (const row of rows) {
10279
+ if (row.kind !== "code") continue;
10280
+ const v = row.z - predict(row, this.x).pred;
10281
+ codeInn.set(row.g.prn, v);
10282
+ if (Math.abs(v) > o.codeGateM) dropPrns.add(row.g.prn);
10283
+ }
10284
+ for (const row of rows) {
10285
+ if (row.kind !== "phase" || dropPrns.has(row.g.prn)) continue;
10286
+ const v = row.z - predict(row, this.x).pred;
10287
+ const vc = codeInn.get(row.g.prn) ?? 0;
10288
+ if (Math.abs(v - vc) > o.slipGateM) {
10289
+ const zP = row.g.prR - row.g.prB - (row.ref.prR - row.ref.prB);
10290
+ const lam = this.amb[row.ambIdx].lambda;
10291
+ this.x[3 + row.ambIdx] = (row.z - zP) / lam;
10292
+ const s = 3 + row.ambIdx;
10293
+ for (let j = 0; j < this.x.length; j++) {
10294
+ this.P[s][j] = 0;
10295
+ this.P[j][s] = 0;
10296
+ }
10297
+ this.P[s][s] = o.ambInitSigmaCycles * o.ambInitSigmaCycles;
10298
+ }
10299
+ }
10300
+ if (dropPrns.size) rows = rows.filter((r) => !dropPrns.has(r.g.prn));
10301
+ if (rows.length < 3) {
10302
+ this.lastMs = timeMs;
10303
+ return null;
10304
+ }
10305
+ const n = this.x.length;
10306
+ const R = ddCovariance(rows, o.codeSigmaM, o.phaseSigmaM);
10307
+ const xPrior = [...this.x];
10308
+ const PPrior = this.P;
10309
+ let xi = [...this.x];
10310
+ let K = [];
10311
+ let H2 = [];
10312
+ for (let it = 0; it < Math.max(o.updateIterations, 1); it++) {
10313
+ H2 = [];
10314
+ const v = [];
10315
+ for (const row of rows) {
10316
+ const { pred, h } = predict(row, xi);
10317
+ let corr = 0;
10318
+ for (let j = 0; j < n; j++) corr += h[j] * (xPrior[j] - xi[j]);
10319
+ v.push(row.z - pred - corr);
10320
+ H2.push(h);
10321
+ }
10322
+ const Ht = transpose(H2);
10323
+ const S = matMul(matMul(H2, PPrior), Ht);
10324
+ for (let i = 0; i < rows.length; i++)
10325
+ for (let j = 0; j < rows.length; j++) S[i][j] += R[i][j];
10326
+ const Sinv = matInv(S);
10327
+ if (!Sinv) {
10328
+ this.lastMs = timeMs;
10329
+ return null;
10330
+ }
10331
+ K = matMul(matMul(PPrior, Ht), Sinv);
10332
+ const dx = matMul(
10333
+ K,
10334
+ v.map((s) => [s])
10335
+ );
10336
+ xi = xPrior.map((s, i) => s + dx[i][0]);
10337
+ }
10338
+ this.x = xi;
10339
+ const KH = matMul(K, H2);
10340
+ const IKH = zeros(n, n);
10341
+ for (let i = 0; i < n; i++)
10342
+ for (let j = 0; j < n; j++) IKH[i][j] = (i === j ? 1 : 0) - KH[i][j];
10343
+ const Pnew = matMul(IKH, PPrior);
10344
+ for (let i = 0; i < n; i++)
10345
+ for (let j = i; j < n; j++) {
10346
+ const s = (Pnew[i][j] + Pnew[j][i]) / 2;
10347
+ Pnew[i][j] = s;
10348
+ Pnew[j][i] = s;
10349
+ }
10350
+ this.P = Pnew;
10351
+ for (const g of geom) {
10352
+ if (g.cpR === null || g.cpB === null) continue;
10353
+ this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
10354
+ }
10355
+ this.lastMs = timeMs;
10356
+ const ambiguities = {};
10357
+ for (let k = 0; k < this.amb.length; k++)
10358
+ ambiguities[this.amb[k].prn] = this.x[3 + k];
10359
+ const refSatellites = {};
10360
+ for (const [group, ref] of newRefs) refSatellites[group] = ref.prn;
10361
+ const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
10362
+ return {
10363
+ timeMs,
10364
+ position: [this.x[0], this.x[1], this.x[2]],
10365
+ floatBaseline: [
10366
+ this.x[0] - this.basePos[0],
10367
+ this.x[1] - this.basePos[1],
10368
+ this.x[2] - this.basePos[2]
10369
+ ],
10370
+ nSats,
10371
+ ratio: void 0,
10372
+ sigmas: [
10373
+ Math.sqrt(Math.max(this.P[0][0], 0)),
10374
+ Math.sqrt(Math.max(this.P[1][1], 0)),
10375
+ Math.sqrt(Math.max(this.P[2][2], 0))
10376
+ ],
10377
+ ambiguities,
10378
+ refSatellites
10379
+ };
10380
+ }
10381
+ };
10382
+
9580
10383
  // src/positioning/index.ts
9581
10384
  var GM_GPS3 = 3986005e8;
9582
10385
  var F_REL = -4442807633e-19;
@@ -9613,11 +10416,11 @@ var PRIMARY_FREQ_HZ = {
9613
10416
  R: 1602e6
9614
10417
  };
9615
10418
  var F_L1 = 157542e4;
9616
- function tropoDelay(elevationRad) {
10419
+ function tropoDelay2(elevationRad) {
9617
10420
  const sinEl = Math.sin(elevationRad);
9618
10421
  return 2.47 / (sinEl + 0.0121);
9619
10422
  }
9620
- function sagnac(pos, travelTimeS) {
10423
+ function sagnac2(pos, travelTimeS) {
9621
10424
  const theta = OMEGA_E * travelTimeS;
9622
10425
  const c = Math.cos(theta);
9623
10426
  const s = Math.sin(theta);
@@ -9688,7 +10491,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
9688
10491
  const isKepler3 = eph.system !== "R" && eph.system !== "S";
9689
10492
  const dts = dtsClock - (tgd && isKepler3 ? eph.tgd : 0);
9690
10493
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
9691
- const [sx, sy, sz] = sagnac(sat, travel);
10494
+ const [sx, sy, sz] = sagnac2(sat, travel);
9692
10495
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
9693
10496
  const ux = (x2 - sx) / rho;
9694
10497
  const uy = (y2 - sy) / rho;
@@ -9705,7 +10508,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
9705
10508
  const sinEl = Math.max(Math.sin(elev), 0.1);
9706
10509
  weight = sinEl * sinEl;
9707
10510
  }
9708
- const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
10511
+ const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
9709
10512
  let ionoM = 0;
9710
10513
  if (iono && positionSane) {
9711
10514
  const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
@@ -12111,6 +12914,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12111
12914
  RINEX_CODES,
12112
12915
  RTCM3_MESSAGE_NAMES,
12113
12916
  Rtcm3Decoder,
12917
+ RtkFloatEngine,
12114
12918
  SECONDS_IN_DAY,
12115
12919
  SECONDS_IN_HOUR,
12116
12920
  SECONDS_IN_MINUTE,
@@ -12270,6 +13074,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12270
13074
  parseUbxCnav,
12271
13075
  parseUbxIonoUtc,
12272
13076
  parseUbxNav,
13077
+ parseUbxRawNav,
12273
13078
  parseUbxRawx,
12274
13079
  phiAltBOC,
12275
13080
  phiBOCc,
@@ -12288,11 +13093,13 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12288
13093
  selectEphemeris,
12289
13094
  setGloFreqNumber,
12290
13095
  setRtcm3DebugHandler,
13096
+ solveDgnss,
12291
13097
  solveSpp,
12292
13098
  sp3Position,
12293
13099
  stationHeaderLines,
12294
13100
  systemCmp,
12295
13101
  systemName,
13102
+ toRtkEpoch,
12296
13103
  transformFrame,
12297
13104
  ubxFrames,
12298
13105
  updateStationMeta,