gnss-js 1.36.0 → 1.37.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/binex.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  decodeBinexEph,
7
7
  getBnxi,
8
8
  parseBinex
9
- } from "./chunk-OK4GGNOO.js";
10
- import "./chunk-IQ5OIMQD.js";
9
+ } from "./chunk-D25WNE43.js";
10
+ import "./chunk-V2D5KUKH.js";
11
11
  export {
12
12
  binexCrc16,
13
13
  binexCrc32,
@@ -7,16 +7,18 @@ import {
7
7
  galFnavPageCrcOk,
8
8
  galInavPageCrcOk,
9
9
  testGloString
10
- } from "./chunk-5AUK42AL.js";
10
+ } from "./chunk-FA2UTLEQ.js";
11
11
  import {
12
12
  CnavAssembler,
13
13
  cnavCrcOk,
14
14
  crc24q
15
- } from "./chunk-726LQBGM.js";
15
+ } from "./chunk-YUW33VXK.js";
16
16
  import {
17
+ GpsLnavAssembler,
17
18
  getBitS,
18
- getBitU
19
- } from "./chunk-IQ5OIMQD.js";
19
+ getBitU,
20
+ setBitU
21
+ } from "./chunk-V2D5KUKH.js";
20
22
  import {
21
23
  getGpsLeap
22
24
  } from "./chunk-HVXYFUCB.js";
@@ -423,38 +425,78 @@ function parseSbfIonoUtc(data) {
423
425
  return { ionoCorrections, leapSeconds };
424
426
  }
425
427
 
428
+ // src/sbf/rawnav-gps.ts
429
+ var WNC_DNU = 65535;
430
+ function extractSbfLnavSubframe(view, b) {
431
+ const sf = new Uint8Array(30);
432
+ for (let k = 0; k < 10; k++) {
433
+ const w = view.getUint32(b + 20 + 4 * k, true);
434
+ setBitU(sf, 24 * k, 24, w >>> 6 & 16777215);
435
+ }
436
+ return sf;
437
+ }
438
+ function feedGpsLnavBlock(view, b, assembler) {
439
+ const prn = svidToPrn(view.getUint8(b + 14));
440
+ if (!prn || prn[0] !== "G" && prn[0] !== "J") return {};
441
+ if (view.getUint8(b + 15) !== 1) return {};
442
+ const wnc = view.getUint16(b + 12, true);
443
+ if (wnc === WNC_DNU) return {};
444
+ const r = assembler.push(prn, extractSbfLnavSubframe(view, b), wnc);
445
+ if (r.kind === "eph") return { eph: r.eph };
446
+ if (r.kind === "decodeFailed") return { badParity: true };
447
+ return {};
448
+ }
449
+ function parseSbfGpsNav(data) {
450
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
451
+ const ephemerides = [];
452
+ const assembler = new GpsLnavAssembler();
453
+ let badParity = 0;
454
+ let messages = 0;
455
+ scanSbfFrames(data, view, (id, b, len) => {
456
+ if (id !== 4017 && id !== 4066 || len < 60) return;
457
+ messages++;
458
+ const r = feedGpsLnavBlock(view, b, assembler);
459
+ if (r.eph) ephemerides.push(r.eph);
460
+ else if (r.badParity) badParity++;
461
+ });
462
+ return { ephemerides, badParity, messages };
463
+ }
464
+
426
465
  // src/sbf/rawnav.ts
427
- var SIGNAL_OF_BLOCK = {
466
+ var CNAV_SIGNAL_OF_BLOCK = {
428
467
  4018: "L2C",
429
468
  4019: "L5"
430
469
  };
470
+ function newCnavAssemblers() {
471
+ return { L2C: new CnavAssembler(), L5: new CnavAssembler() };
472
+ }
473
+ function feedCnavBlock(view, b, id, asm) {
474
+ const signal = CNAV_SIGNAL_OF_BLOCK[id];
475
+ if (!signal) return {};
476
+ const msg = new Uint8Array(40);
477
+ for (let k = 0; k < 10; k++) {
478
+ const w = view.getUint32(b + 20 + 4 * k, true);
479
+ msg[4 * k] = w >>> 24;
480
+ msg[4 * k + 1] = w >>> 16 & 255;
481
+ msg[4 * k + 2] = w >>> 8 & 255;
482
+ msg[4 * k + 3] = w & 255;
483
+ }
484
+ if (!cnavCrcOk(msg)) return { badCrc: true };
485
+ const eph = asm[signal].push(msg);
486
+ return eph ? { eph: { ...eph, signal } } : {};
487
+ }
431
488
  function parseSbfCnav(data) {
432
489
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
433
490
  const ephemerides = [];
434
- const assemblers = {
435
- L2C: new CnavAssembler(),
436
- L5: new CnavAssembler()
437
- };
491
+ const asm = newCnavAssemblers();
438
492
  let badCrc = 0;
439
493
  let messages = 0;
440
494
  scanSbfFrames(data, view, (id, b, len) => {
441
- const signal = SIGNAL_OF_BLOCK[id];
442
- if (!signal || len < 60) return;
495
+ if (!CNAV_SIGNAL_OF_BLOCK[id] || len < 60) return;
443
496
  messages++;
444
- const msg = new Uint8Array(40);
445
- for (let k = 0; k < 10; k++) {
446
- const w = view.getUint32(b + 20 + 4 * k, true);
447
- msg[4 * k] = w >>> 24;
448
- msg[4 * k + 1] = w >>> 16 & 255;
449
- msg[4 * k + 2] = w >>> 8 & 255;
450
- msg[4 * k + 3] = w & 255;
451
- }
452
- if (!cnavCrcOk(msg)) {
453
- badCrc++;
454
- return;
455
- }
456
- const eph = assemblers[signal].push(msg);
457
- if (eph) ephemerides.push({ ...eph, signal });
497
+ const r = feedCnavBlock(view, b, id, asm);
498
+ if (r.eph) ephemerides.push(r.eph);
499
+ else if (r.badCrc) badCrc++;
458
500
  });
459
501
  return { ephemerides, badCrc, messages };
460
502
  }
@@ -462,50 +504,51 @@ function parseSbfCnav(data) {
462
504
  // src/sbf/rawnav-gal.ts
463
505
  var INAV_SOURCES = [17, 21, 22];
464
506
  var FNAV_SOURCES = [20, 22];
507
+ function newGalAssemblers() {
508
+ return { inav: new GalInavAssembler(), fnav: new GalFnavAssembler() };
509
+ }
510
+ function feedGalBlock(data, view, b, id, asm) {
511
+ const prnStr = svidToPrn(data[b + 14]);
512
+ if (!prnStr || prnStr[0] !== "E") return {};
513
+ const prn = parseInt(prnStr.slice(1), 10);
514
+ const source = data[b + 17] & 31;
515
+ const page = new Uint8Array(32);
516
+ for (let k = 0; k < 8; k++) {
517
+ const w = view.getUint32(b + 20 + 4 * k, true);
518
+ page[4 * k] = w >>> 24;
519
+ page[4 * k + 1] = w >>> 16 & 255;
520
+ page[4 * k + 2] = w >>> 8 & 255;
521
+ page[4 * k + 3] = w & 255;
522
+ }
523
+ let eph;
524
+ let src;
525
+ if (id === 4023) {
526
+ src = "inav";
527
+ if (!INAV_SOURCES.includes(source)) return {};
528
+ if (getBitU2(page, 0) !== 0 || getBitU2(page, 114) !== 1) return {};
529
+ if (!galInavPageCrcOk(page)) return { badCrc: true };
530
+ eph = asm.inav.push(prn, page);
531
+ } else {
532
+ src = "fnav";
533
+ if (!FNAV_SOURCES.includes(source)) return {};
534
+ if (isFnavDummy(page)) return {};
535
+ if (!galFnavPageCrcOk(page)) return { badCrc: true };
536
+ eph = asm.fnav.push(prn, page);
537
+ }
538
+ return eph ? { eph: { ...eph, source: src } } : {};
539
+ }
465
540
  function parseSbfGalNav(data) {
466
541
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
467
542
  const ephemerides = [];
468
- const inav = new GalInavAssembler();
469
- const fnav = new GalFnavAssembler();
543
+ const asm = newGalAssemblers();
470
544
  let badCrc = 0;
471
545
  let messages = 0;
472
546
  scanSbfFrames(data, view, (id, b, len) => {
473
547
  if (id !== 4022 && id !== 4023 || len < 52) return;
474
548
  messages++;
475
- const prnStr = svidToPrn(data[b + 14]);
476
- if (!prnStr || prnStr[0] !== "E") return;
477
- const prn = parseInt(prnStr.slice(1), 10);
478
- const source = data[b + 17] & 31;
479
- const page = new Uint8Array(32);
480
- for (let k = 0; k < 8; k++) {
481
- const w = view.getUint32(b + 20 + 4 * k, true);
482
- page[4 * k] = w >>> 24;
483
- page[4 * k + 1] = w >>> 16 & 255;
484
- page[4 * k + 2] = w >>> 8 & 255;
485
- page[4 * k + 3] = w & 255;
486
- }
487
- let eph;
488
- let src;
489
- if (id === 4023) {
490
- src = "inav";
491
- if (!INAV_SOURCES.includes(source)) return;
492
- if (getBitU2(page, 0) !== 0 || getBitU2(page, 114) !== 1) return;
493
- if (!galInavPageCrcOk(page)) {
494
- badCrc++;
495
- return;
496
- }
497
- eph = inav.push(prn, page);
498
- } else {
499
- src = "fnav";
500
- if (!FNAV_SOURCES.includes(source)) return;
501
- if (isFnavDummy(page)) return;
502
- if (!galFnavPageCrcOk(page)) {
503
- badCrc++;
504
- return;
505
- }
506
- eph = fnav.push(prn, page);
507
- }
508
- if (eph) ephemerides.push({ ...eph, source: src });
549
+ const r = feedGalBlock(data, view, b, id, asm);
550
+ if (r.eph) ephemerides.push(r.eph);
551
+ else if (r.badCrc) badCrc++;
509
552
  });
510
553
  return { ephemerides, badCrc, messages };
511
554
  }
@@ -520,7 +563,26 @@ function isFnavDummy(page) {
520
563
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
521
564
  var MS_PER_WEEK2 = 7 * 86400 * 1e3;
522
565
  var TOW_DNU = 4294967295;
523
- var WNC_DNU = 65535;
566
+ var WNC_DNU2 = 65535;
567
+ function feedBdsBlock(view, b, assembler) {
568
+ const prn = svidToPrn(view.getUint8(b + 14));
569
+ if (!prn || prn[0] !== "C") return {};
570
+ const sf = new Uint8Array(38);
571
+ for (let k = 0; k < 10; k++) {
572
+ const w = view.getUint32(b + 20 + 4 * k, true);
573
+ sf[4 * k] = w >>> 24;
574
+ if (k < 9) {
575
+ sf[4 * k + 1] = w >>> 16 & 255;
576
+ sf[4 * k + 2] = w >>> 8 & 255;
577
+ sf[4 * k + 3] = w & 255;
578
+ } else {
579
+ sf[37] = w >>> 16 & 240;
580
+ }
581
+ }
582
+ if (!bdsSubframeParityOk(sf)) return { badCrc: true };
583
+ const eph = assembler.push(prn, sf);
584
+ return eph ? { eph } : {};
585
+ }
524
586
  function parseSbfBdsNav(data) {
525
587
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
526
588
  const ephemerides = [];
@@ -530,29 +592,41 @@ function parseSbfBdsNav(data) {
530
592
  scanSbfFrames(data, view, (id, b, len) => {
531
593
  if (id !== 4047 || len < 60) return;
532
594
  messages++;
533
- const prn = svidToPrn(view.getUint8(b + 14));
534
- if (!prn || prn[0] !== "C") return;
535
- const sf = new Uint8Array(38);
536
- for (let k = 0; k < 10; k++) {
537
- const w = view.getUint32(b + 20 + 4 * k, true);
538
- sf[4 * k] = w >>> 24;
539
- if (k < 9) {
540
- sf[4 * k + 1] = w >>> 16 & 255;
541
- sf[4 * k + 2] = w >>> 8 & 255;
542
- sf[4 * k + 3] = w & 255;
543
- } else {
544
- sf[37] = w >>> 16 & 240;
545
- }
546
- }
547
- if (!bdsSubframeParityOk(sf)) {
548
- badCrc++;
549
- return;
550
- }
551
- const eph = assembler.push(prn, sf);
552
- if (eph) ephemerides.push(eph);
595
+ const r = feedBdsBlock(view, b, assembler);
596
+ if (r.eph) ephemerides.push(r.eph);
597
+ else if (r.badCrc) badCrc++;
553
598
  });
554
599
  return { ephemerides, badCrc, messages };
555
600
  }
601
+ function feedGloBlock(view, b, assembler) {
602
+ const prn = svidToPrn(view.getUint8(b + 14));
603
+ if (!prn || prn[0] !== "R") return {};
604
+ const towMs = view.getUint32(b + 8, true);
605
+ const wnc = view.getUint16(b + 12, true);
606
+ if (towMs === TOW_DNU || wnc === WNC_DNU2) return {};
607
+ const str = new Uint8Array(11);
608
+ for (let k = 0; k < 3; k++) {
609
+ const w = view.getUint32(b + 20 + 4 * k, true);
610
+ str[4 * k] = w >>> 24;
611
+ if (k < 2) {
612
+ str[4 * k + 1] = w >>> 16 & 255;
613
+ str[4 * k + 2] = w >>> 8 & 255;
614
+ str[4 * k + 3] = w & 255;
615
+ } else {
616
+ str[9] = w >>> 16 & 255;
617
+ str[10] = w >>> 8 & 248;
618
+ }
619
+ }
620
+ if (!testGloString(str)) return { badCrc: true };
621
+ const eph = assembler.push(
622
+ prn,
623
+ str,
624
+ new Date(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK2 + towMs),
625
+ view.getUint8(b + 18) - 8
626
+ // FreqNr, offset by 8
627
+ );
628
+ return eph ? { eph } : {};
629
+ }
556
630
  function parseSbfGloNav(data) {
557
631
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
558
632
  const ephemerides = [];
@@ -562,38 +636,161 @@ function parseSbfGloNav(data) {
562
636
  scanSbfFrames(data, view, (id, b, len) => {
563
637
  if (id !== 4026 || len < 32) return;
564
638
  messages++;
565
- const prn = svidToPrn(view.getUint8(b + 14));
566
- if (!prn || prn[0] !== "R") return;
567
- const towMs = view.getUint32(b + 8, true);
568
- const wnc = view.getUint16(b + 12, true);
569
- if (towMs === TOW_DNU || wnc === WNC_DNU) return;
570
- const str = new Uint8Array(11);
571
- for (let k = 0; k < 3; k++) {
572
- const w = view.getUint32(b + 20 + 4 * k, true);
573
- str[4 * k] = w >>> 24;
574
- if (k < 2) {
575
- str[4 * k + 1] = w >>> 16 & 255;
576
- str[4 * k + 2] = w >>> 8 & 255;
577
- str[4 * k + 3] = w & 255;
578
- } else {
579
- str[9] = w >>> 16 & 255;
580
- str[10] = w >>> 8 & 248;
581
- }
639
+ const r = feedGloBlock(view, b, assembler);
640
+ if (r.eph) ephemerides.push(r.eph);
641
+ else if (r.badCrc) badCrc++;
642
+ });
643
+ return { ephemerides, badCrc, messages };
644
+ }
645
+
646
+ // src/sbf/navigation.ts
647
+ var F4_DNU3 = -2e10;
648
+ function ephKey(e) {
649
+ const source = "source" in e ? e.source : "";
650
+ return `${e.prn}|${e.tocDate.getTime()}|${source}`;
651
+ }
652
+ function decodeSbfNavigation(data) {
653
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
654
+ const ephemerides = [];
655
+ const seen = /* @__PURE__ */ new Set();
656
+ const cnav = [];
657
+ const ionoCorrections = {};
658
+ let leapSeconds = null;
659
+ const counts = {
660
+ decodedNav: 0,
661
+ gpsLnavRaw: 0,
662
+ cnavRaw: 0,
663
+ galRaw: 0,
664
+ gloRaw: 0,
665
+ bdsRaw: 0,
666
+ badFrames: 0
667
+ };
668
+ const cnavAsm = newCnavAssemblers();
669
+ const galAsm = newGalAssemblers();
670
+ const gloAsm = new GloStringAssembler();
671
+ const bdsAsm = new BdsAssembler();
672
+ const lnavAsm = new GpsLnavAssembler();
673
+ const addEph = (e) => {
674
+ if (!e) return;
675
+ const key = ephKey(e);
676
+ if (seen.has(key)) return;
677
+ seen.add(key);
678
+ ephemerides.push(e);
679
+ };
680
+ const f4s = (b, n) => {
681
+ const out = [];
682
+ for (let k = 0; k < n; k++) {
683
+ const v = view.getFloat32(b + 4 * k, true);
684
+ if (v === F4_DNU3) return null;
685
+ out.push(v);
582
686
  }
583
- if (!testGloString(str)) {
584
- badCrc++;
585
- return;
687
+ return out;
688
+ };
689
+ scanSbfFrames(data, view, (id, b, len) => {
690
+ switch (id) {
691
+ /* ---- decoded ephemeris blocks ---- */
692
+ case 5891:
693
+ if (len >= 140) {
694
+ counts.decodedNav++;
695
+ addEph(decodeGpsQzsNav(view, b, "G"));
696
+ }
697
+ return;
698
+ case 4095:
699
+ if (len >= 140) {
700
+ counts.decodedNav++;
701
+ addEph(decodeGpsQzsNav(view, b, "J"));
702
+ }
703
+ return;
704
+ case 4002:
705
+ if (len >= 149) {
706
+ counts.decodedNav++;
707
+ addEph(decodeGalNav(view, b));
708
+ }
709
+ return;
710
+ case 4081:
711
+ if (len >= 140) {
712
+ counts.decodedNav++;
713
+ addEph(decodeBdsNav(view, b));
714
+ }
715
+ return;
716
+ case 4004:
717
+ if (len >= 96) {
718
+ counts.decodedNav++;
719
+ addEph(decodeGloNav(view, b));
720
+ }
721
+ return;
722
+ /* ---- raw navigation-frame blocks ---- */
723
+ case 4017:
724
+ // GPSRawCA
725
+ case 4066: {
726
+ if (len < 60) return;
727
+ counts.gpsLnavRaw++;
728
+ const r = feedGpsLnavBlock(view, b, lnavAsm);
729
+ if (r.eph) addEph(r.eph);
730
+ else if (r.badParity) counts.badFrames++;
731
+ return;
732
+ }
733
+ case 4018:
734
+ // GPSRawL2C
735
+ case 4019: {
736
+ if (len < 60) return;
737
+ counts.cnavRaw++;
738
+ const r = feedCnavBlock(view, b, id, cnavAsm);
739
+ if (r.eph) cnav.push(r.eph);
740
+ else if (r.badCrc) counts.badFrames++;
741
+ return;
742
+ }
743
+ case 4022:
744
+ // GALRawFNAV
745
+ case 4023: {
746
+ if (len < 52) return;
747
+ counts.galRaw++;
748
+ const r = feedGalBlock(data, view, b, id, galAsm);
749
+ if (r.eph) addEph(r.eph);
750
+ else if (r.badCrc) counts.badFrames++;
751
+ return;
752
+ }
753
+ case 4026: {
754
+ if (len < 32) return;
755
+ counts.gloRaw++;
756
+ const r = feedGloBlock(view, b, gloAsm);
757
+ if (r.eph) addEph(r.eph);
758
+ else if (r.badCrc) counts.badFrames++;
759
+ return;
760
+ }
761
+ case 4047: {
762
+ if (len < 60) return;
763
+ counts.bdsRaw++;
764
+ const r = feedBdsBlock(view, b, bdsAsm);
765
+ if (r.eph) addEph(r.eph);
766
+ else if (r.badCrc) counts.badFrames++;
767
+ return;
768
+ }
769
+ /* ---- iono / UTC ---- */
770
+ case 5893:
771
+ // GPSIon
772
+ case 4120: {
773
+ if (len < 48) return;
774
+ const alpha = f4s(b + 16, 4);
775
+ const beta = f4s(b + 32, 4);
776
+ if (!alpha || !beta) return;
777
+ const sys = id === 5893 ? "GPS" : "BDS";
778
+ ionoCorrections[`${sys}A`] = alpha;
779
+ ionoCorrections[`${sys}B`] = beta;
780
+ return;
781
+ }
782
+ case 4030: {
783
+ if (len < 28) return;
784
+ const ai = f4s(b + 16, 3);
785
+ if (ai) ionoCorrections["GAL"] = ai;
786
+ return;
787
+ }
788
+ case 5894:
789
+ if (len >= 37) leapSeconds = view.getInt8(b + 33);
790
+ return;
586
791
  }
587
- const eph = assembler.push(
588
- prn,
589
- str,
590
- new Date(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK2 + towMs),
591
- view.getUint8(b + 18) - 8
592
- // FreqNr, offset by 8
593
- );
594
- if (eph) ephemerides.push(eph);
595
792
  });
596
- return { ephemerides, badCrc, messages };
793
+ return { ephemerides, cnav, ionoCorrections, leapSeconds, counts };
597
794
  }
598
795
 
599
796
  // src/navbits/rs255.ts
@@ -1793,10 +1990,12 @@ export {
1793
1990
  parseSbfNav,
1794
1991
  parseSbfAlmanac,
1795
1992
  parseSbfIonoUtc,
1993
+ parseSbfGpsNav,
1796
1994
  parseSbfCnav,
1797
1995
  parseSbfGalNav,
1798
1996
  parseSbfBdsNav,
1799
1997
  parseSbfGloNav,
1998
+ decodeSbfNavigation,
1800
1999
  parseSbfHas,
1801
2000
  parseSbfMeas
1802
2001
  };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  CnavAssembler,
3
3
  cnavCrcOk
4
- } from "./chunk-726LQBGM.js";
4
+ } from "./chunk-YUW33VXK.js";
5
5
  import {
6
6
  decodeGpsLnavFrame
7
- } from "./chunk-IQ5OIMQD.js";
7
+ } from "./chunk-V2D5KUKH.js";
8
8
  import {
9
9
  getUtcDate
10
10
  } from "./chunk-HVXYFUCB.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  CNAV_A_REF,
3
3
  CNAV_OMEGA_DOT_REF
4
- } from "./chunk-726LQBGM.js";
4
+ } from "./chunk-YUW33VXK.js";
5
5
  import {
6
6
  GPS_PI
7
- } from "./chunk-IQ5OIMQD.js";
7
+ } from "./chunk-V2D5KUKH.js";
8
8
  import {
9
9
  C_LIGHT
10
10
  } from "./chunk-FIEWO4J4.js";
@@ -2,7 +2,7 @@ import {
2
2
  GPS_PI,
3
3
  getBitS,
4
4
  getBitU
5
- } from "./chunk-IQ5OIMQD.js";
5
+ } from "./chunk-V2D5KUKH.js";
6
6
 
7
7
  // src/binex/frame.ts
8
8
  var CRC16_TABLE = (() => {
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  crc24q
3
- } from "./chunk-726LQBGM.js";
3
+ } from "./chunk-YUW33VXK.js";
4
4
  import {
5
5
  GPS_PI,
6
6
  getBitS,
7
7
  getBitU
8
- } from "./chunk-IQ5OIMQD.js";
8
+ } from "./chunk-V2D5KUKH.js";
9
9
  import {
10
10
  getUtcDate
11
11
  } from "./chunk-HVXYFUCB.js";
@@ -25,6 +25,38 @@ function setBitU(buff, pos, len, data) {
25
25
  }
26
26
  }
27
27
  }
28
+ var GpsLnavAssembler = class _GpsLnavAssembler {
29
+ buffers = /* @__PURE__ */ new Map();
30
+ last = /* @__PURE__ */ new Map();
31
+ /** Subframe ID (1–5) from the HOW word of a parity-stripped subframe. */
32
+ static subframeId(subframe) {
33
+ return getBitU(subframe, 43, 3);
34
+ }
35
+ /**
36
+ * Push one parity-stripped 30-byte LNAV subframe for `prn`, resolving
37
+ * the 10-bit week against `refWeek`. See {@link LnavPush} for outcomes.
38
+ */
39
+ push(prn, subframe, refWeek) {
40
+ const id = _GpsLnavAssembler.subframeId(subframe);
41
+ if (id < 1 || id > 3) return { kind: "skipped" };
42
+ let sf = this.buffers.get(prn);
43
+ if (!sf) {
44
+ sf = { buf: new Uint8Array(90), have: 0 };
45
+ this.buffers.set(prn, sf);
46
+ }
47
+ sf.buf.set(subframe, (id - 1) * 30);
48
+ sf.have |= 1 << id - 1;
49
+ if (id !== 3 || sf.have !== 7) return { kind: "incomplete" };
50
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
51
+ if (!eph) return { kind: "decodeFailed" };
52
+ const prev = this.last.get(prn);
53
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
54
+ return { kind: "duplicate" };
55
+ }
56
+ this.last.set(prn, eph);
57
+ return { kind: "eph", eph };
58
+ }
59
+ };
28
60
  function decodeGpsLnavFrame(subframes, opts = {}) {
29
61
  if (subframes.length < 90) return null;
30
62
  const b = subframes;
@@ -144,5 +176,6 @@ export {
144
176
  getBitU,
145
177
  getBitS,
146
178
  setBitU,
179
+ GpsLnavAssembler,
147
180
  decodeGpsLnavFrame
148
181
  };
@@ -2,7 +2,7 @@ import {
2
2
  GPS_PI,
3
3
  getBitS,
4
4
  getBitU
5
- } from "./chunk-IQ5OIMQD.js";
5
+ } from "./chunk-V2D5KUKH.js";
6
6
 
7
7
  // src/navbits/cnav.ts
8
8
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
@@ -5,17 +5,17 @@ import {
5
5
  bdsSubframeParityOk,
6
6
  galInavPageCrcOk,
7
7
  testGloString
8
- } from "./chunk-5AUK42AL.js";
8
+ } from "./chunk-FA2UTLEQ.js";
9
9
  import {
10
10
  CnavAssembler,
11
11
  cnavCrcOk
12
- } from "./chunk-726LQBGM.js";
12
+ } from "./chunk-YUW33VXK.js";
13
13
  import {
14
- decodeGpsLnavFrame,
14
+ GpsLnavAssembler,
15
15
  getBitS,
16
16
  getBitU,
17
17
  setBitU
18
- } from "./chunk-IQ5OIMQD.js";
18
+ } from "./chunk-V2D5KUKH.js";
19
19
 
20
20
  // src/ubx/frame.ts
21
21
  function* ubxFrames(data, stats = { badChecksums: 0 }) {
@@ -92,38 +92,19 @@ function parseUbxNav(data, opts = {}) {
92
92
  }
93
93
  }
94
94
  if (refWeek === void 0) return { ephemerides, badParity };
95
- const subframes = /* @__PURE__ */ new Map();
96
- const last = /* @__PURE__ */ new Map();
95
+ const assembler = new GpsLnavAssembler();
97
96
  for (const f of ubxFrames(data)) {
98
97
  if (f.msgClass !== 2 || f.msgId !== 19) continue;
99
98
  const lnav = readLnavSubframe(view, f);
100
99
  if (!lnav) continue;
101
- const { prn, gnssId, svId, buff, id } = lnav;
100
+ const { prn, buff, id } = lnav;
102
101
  if (id < 1 || id > 5) {
103
102
  badParity++;
104
103
  continue;
105
104
  }
106
- if (id > 3) continue;
107
- const key = gnssId * 256 + svId;
108
- let sf = subframes.get(key);
109
- if (!sf) {
110
- sf = { buf: new Uint8Array(90), have: 0 };
111
- subframes.set(key, sf);
112
- }
113
- sf.buf.set(buff, (id - 1) * 30);
114
- sf.have |= 1 << id - 1;
115
- if (id !== 3 || sf.have !== 7) continue;
116
- const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
117
- if (!eph) {
118
- badParity++;
119
- continue;
120
- }
121
- const prev = last.get(prn);
122
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
123
- continue;
124
- }
125
- last.set(prn, eph);
126
- ephemerides.push(eph);
105
+ const r = assembler.push(prn, buff, refWeek);
106
+ if (r.kind === "eph") ephemerides.push(r.eph);
107
+ else if (r.kind === "decodeFailed") badParity++;
127
108
  }
128
109
  return { ephemerides, badParity };
129
110
  }