gnss-js 1.17.0 → 1.19.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/sbf.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var sbf_exports = {};
22
22
  __export(sbf_exports, {
23
23
  parseSbfAlmanac: () => parseSbfAlmanac,
24
+ parseSbfCnav: () => parseSbfCnav,
24
25
  parseSbfIonoUtc: () => parseSbfIonoUtc,
25
26
  parseSbfMeas: () => parseSbfMeas,
26
27
  parseSbfNav: () => parseSbfNav,
@@ -518,9 +519,258 @@ function parseSbfIonoUtc(data) {
518
519
  return { ionoCorrections, leapSeconds };
519
520
  }
520
521
 
522
+ // src/navbits/index.ts
523
+ var GPS_PI = 3.1415926535898;
524
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
525
+ var SEC_PER_WEEK2 = 7 * 86400;
526
+ function getBitU(buff, pos, len) {
527
+ let bits = 0;
528
+ for (let i = pos; i < pos + len; i++) {
529
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
530
+ }
531
+ return bits;
532
+ }
533
+ function getBitS(buff, pos, len) {
534
+ const bits = getBitU(buff, pos, len);
535
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
536
+ return bits - 2 ** len;
537
+ }
538
+
539
+ // src/navbits/cnav.ts
540
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
541
+ var SEC_PER_WEEK3 = 7 * 86400;
542
+ var HALF_WEEK = 302400;
543
+ var CNAV_PREAMBLE = 139;
544
+ var CNAV_A_REF = 26559710;
545
+ var CNAV_OMEGA_DOT_REF = -26e-10;
546
+ function crc24q(buff, bitLen) {
547
+ let crc = 0;
548
+ for (let i = 0; i < bitLen; i++) {
549
+ crc ^= (buff[i >> 3] >> 7 - (i & 7) & 1) << 23;
550
+ const msb = crc & 8388608;
551
+ crc = crc << 1 & 16777215;
552
+ if (msb) crc ^= 8801531;
553
+ }
554
+ return crc;
555
+ }
556
+ function cnavCrcOk(msg) {
557
+ if (msg.length < 38) return false;
558
+ return crc24q(msg, 276) === getBitU(msg, 276, 24);
559
+ }
560
+ function getBitU33(b, pos) {
561
+ return getBitU(b, pos, 1) * 2 ** 32 + getBitU(b, pos + 1, 32);
562
+ }
563
+ function getBitS33(b, pos) {
564
+ const u = getBitU33(b, pos);
565
+ return u < 2 ** 32 ? u : u - 2 ** 33;
566
+ }
567
+ function decodeType10(b, tow) {
568
+ return {
569
+ tow,
570
+ week: getBitU(b, 38, 13),
571
+ health: getBitU(b, 51, 3),
572
+ top: getBitU(b, 54, 11) * 300,
573
+ uraEd: getBitS(b, 65, 5),
574
+ toe: getBitU(b, 70, 11) * 300,
575
+ deltaA: getBitS(b, 81, 26) * 2 ** -9,
576
+ aDot: getBitS(b, 107, 25) * 2 ** -21,
577
+ deltaN0: getBitS(b, 132, 17) * 2 ** -44 * GPS_PI,
578
+ deltaN0Dot: getBitS(b, 149, 23) * 2 ** -57 * GPS_PI,
579
+ m0: getBitS33(b, 172) * 2 ** -32 * GPS_PI,
580
+ e: getBitU33(b, 205) * 2 ** -34,
581
+ omega: getBitS33(b, 238) * 2 ** -32 * GPS_PI,
582
+ integrityFlag: getBitU(b, 271, 1) === 1,
583
+ l2cPhasing: getBitU(b, 272, 1) === 1
584
+ };
585
+ }
586
+ function decodeType11(b) {
587
+ return {
588
+ toe: getBitU(b, 38, 11) * 300,
589
+ omega0: getBitS33(b, 49) * 2 ** -32 * GPS_PI,
590
+ i0: getBitS33(b, 82) * 2 ** -32 * GPS_PI,
591
+ deltaOmegaDot: getBitS(b, 115, 17) * 2 ** -44 * GPS_PI,
592
+ i0Dot: getBitS(b, 132, 15) * 2 ** -44 * GPS_PI,
593
+ cis: getBitS(b, 147, 16) * 2 ** -30,
594
+ cic: getBitS(b, 163, 16) * 2 ** -30,
595
+ crs: getBitS(b, 179, 24) * 2 ** -8,
596
+ crc: getBitS(b, 203, 24) * 2 ** -8,
597
+ cus: getBitS(b, 227, 21) * 2 ** -30,
598
+ cuc: getBitS(b, 248, 21) * 2 ** -30
599
+ };
600
+ }
601
+ var isc = (b, pos) => {
602
+ const raw = getBitS(b, pos, 13);
603
+ return raw === -4096 ? null : raw * 2 ** -35;
604
+ };
605
+ function decodeClock(b, msgType) {
606
+ const clock = {
607
+ msgType,
608
+ top: getBitU(b, 38, 11) * 300,
609
+ uraNed0: getBitS(b, 49, 5),
610
+ uraNed1: getBitU(b, 54, 3),
611
+ uraNed2: getBitU(b, 57, 3),
612
+ toc: getBitU(b, 60, 11) * 300,
613
+ af0: getBitS(b, 71, 26) * 2 ** -35,
614
+ af1: getBitS(b, 97, 20) * 2 ** -48,
615
+ af2: getBitS(b, 117, 10) * 2 ** -60
616
+ };
617
+ if (msgType === 30) {
618
+ clock.tgd = isc(b, 127);
619
+ clock.iscL1ca = isc(b, 140);
620
+ clock.iscL2c = isc(b, 153);
621
+ clock.iscL5i5 = isc(b, 166);
622
+ clock.iscL5q5 = isc(b, 179);
623
+ clock.ionoAlpha = [
624
+ getBitS(b, 192, 8) * 2 ** -30,
625
+ getBitS(b, 200, 8) * 2 ** -27,
626
+ getBitS(b, 208, 8) * 2 ** -24,
627
+ getBitS(b, 216, 8) * 2 ** -24
628
+ ];
629
+ clock.ionoBeta = [
630
+ getBitS(b, 224, 8) * 2 ** 11,
631
+ getBitS(b, 232, 8) * 2 ** 14,
632
+ getBitS(b, 240, 8) * 2 ** 16,
633
+ getBitS(b, 248, 8) * 2 ** 16
634
+ ];
635
+ clock.wnOp = getBitU(b, 256, 8);
636
+ }
637
+ return clock;
638
+ }
639
+ function resolveWeek(week, towSec, sec) {
640
+ if (sec < towSec - HALF_WEEK) return week + 1;
641
+ if (sec > towSec + HALF_WEEK) return week - 1;
642
+ return week;
643
+ }
644
+ function gpsDate(week, sec) {
645
+ return new Date(GPS_EPOCH_MS3 + (week * SEC_PER_WEEK3 + sec) * 1e3);
646
+ }
647
+ var CnavAssembler = class {
648
+ sats = /* @__PURE__ */ new Map();
649
+ /**
650
+ * Push one 300-bit CNAV message (38+ bytes, bit 0 = first bit of
651
+ * the preamble). Returns the newly completed ephemeris, or null.
652
+ * Messages that are not MT10/MT11/MT30-37, or whose preamble/PRN
653
+ * are out of range, are ignored.
654
+ */
655
+ push(msg) {
656
+ if (msg.length < 38 || getBitU(msg, 0, 8) !== CNAV_PREAMBLE) return null;
657
+ const prn = getBitU(msg, 8, 6);
658
+ if (prn < 1 || prn > 32) return null;
659
+ const type = getBitU(msg, 14, 6);
660
+ const tow = getBitU(msg, 20, 17) * 6;
661
+ let sat = this.sats.get(prn);
662
+ if (!sat) {
663
+ sat = {};
664
+ this.sats.set(prn, sat);
665
+ }
666
+ if (type === 10) sat.t10 = decodeType10(msg, tow);
667
+ else if (type === 11) sat.t11 = decodeType11(msg);
668
+ else if (type >= 30 && type <= 37) {
669
+ sat.clock = decodeClock(msg, type);
670
+ if (type === 30) sat.extras = sat.clock;
671
+ } else return null;
672
+ return this.tryEmit(prn, sat);
673
+ }
674
+ tryEmit(prn, sat) {
675
+ const { t10, t11, clock, extras } = sat;
676
+ if (!t10 || !t11 || !clock) return null;
677
+ if (t10.toe !== t11.toe || clock.toc !== t10.toe) return null;
678
+ const key = `${t10.week}:${t10.toe}:${clock.af0}:${clock.af1}`;
679
+ if (key === sat.lastKey) return null;
680
+ sat.lastKey = key;
681
+ const weekToe = resolveWeek(t10.week, t10.tow, t10.toe);
682
+ const a = CNAV_A_REF + t10.deltaA;
683
+ return {
684
+ system: "G",
685
+ prn: `G${String(prn).padStart(2, "0")}`,
686
+ week: t10.week,
687
+ health: t10.health,
688
+ uraEd: t10.uraEd,
689
+ uraNed0: clock.uraNed0,
690
+ uraNed1: clock.uraNed1,
691
+ uraNed2: clock.uraNed2,
692
+ top: t10.top,
693
+ toe: t10.toe,
694
+ toeDate: gpsDate(weekToe, t10.toe),
695
+ a,
696
+ deltaA: t10.deltaA,
697
+ aDot: t10.aDot,
698
+ deltaN0: t10.deltaN0,
699
+ deltaN0Dot: t10.deltaN0Dot,
700
+ m0: t10.m0,
701
+ e: t10.e,
702
+ omega: t10.omega,
703
+ omega0: t11.omega0,
704
+ i0: t11.i0,
705
+ omegaDot: CNAV_OMEGA_DOT_REF * GPS_PI + t11.deltaOmegaDot,
706
+ deltaOmegaDot: t11.deltaOmegaDot,
707
+ i0Dot: t11.i0Dot,
708
+ cis: t11.cis,
709
+ cic: t11.cic,
710
+ crs: t11.crs,
711
+ crc: t11.crc,
712
+ cus: t11.cus,
713
+ cuc: t11.cuc,
714
+ toc: clock.toc,
715
+ tocDate: gpsDate(weekToe, clock.toc),
716
+ af0: clock.af0,
717
+ af1: clock.af1,
718
+ af2: clock.af2,
719
+ clockMsgType: clock.msgType,
720
+ tgd: extras?.tgd ?? null,
721
+ iscL1ca: extras?.iscL1ca ?? null,
722
+ iscL2c: extras?.iscL2c ?? null,
723
+ iscL5i5: extras?.iscL5i5 ?? null,
724
+ iscL5q5: extras?.iscL5q5 ?? null,
725
+ ...extras?.ionoAlpha && { ionoAlpha: extras.ionoAlpha },
726
+ ...extras?.ionoBeta && { ionoBeta: extras.ionoBeta },
727
+ ...extras?.wnOp !== void 0 && { wnOp: extras.wnOp },
728
+ integrityFlag: t10.integrityFlag,
729
+ l2cPhasing: t10.l2cPhasing,
730
+ tow: t10.tow
731
+ };
732
+ }
733
+ };
734
+
735
+ // src/sbf/rawnav.ts
736
+ var SIGNAL_OF_BLOCK = {
737
+ 4018: "L2C",
738
+ 4019: "L5"
739
+ };
740
+ function parseSbfCnav(data) {
741
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
742
+ const ephemerides = [];
743
+ const assemblers = {
744
+ L2C: new CnavAssembler(),
745
+ L5: new CnavAssembler()
746
+ };
747
+ let badCrc = 0;
748
+ let messages = 0;
749
+ scanSbfFrames(data, view, (id, b, len) => {
750
+ const signal = SIGNAL_OF_BLOCK[id];
751
+ if (!signal || len < 60) return;
752
+ messages++;
753
+ const msg = new Uint8Array(40);
754
+ for (let k = 0; k < 10; k++) {
755
+ const w = view.getUint32(b + 20 + 4 * k, true);
756
+ msg[4 * k] = w >>> 24;
757
+ msg[4 * k + 1] = w >>> 16 & 255;
758
+ msg[4 * k + 2] = w >>> 8 & 255;
759
+ msg[4 * k + 3] = w & 255;
760
+ }
761
+ if (!cnavCrcOk(msg)) {
762
+ badCrc++;
763
+ return;
764
+ }
765
+ const eph = assemblers[signal].push(msg);
766
+ if (eph) ephemerides.push({ ...eph, signal });
767
+ });
768
+ return { ephemerides, badCrc, messages };
769
+ }
770
+
521
771
  // src/sbf/index.ts
522
772
  var CLIGHT = 299792458;
523
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
773
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
524
774
  var MS_PER_WEEK2 = 7 * 864e5;
525
775
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
526
776
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -1174,7 +1424,7 @@ function parseSbfMeas(data) {
1174
1424
  const tow = view.getUint32(i + 8, true);
1175
1425
  const wnc = view.getUint16(i + 12, true);
1176
1426
  if (tow !== 4294967295 && wnc !== 65535) {
1177
- ensureEpoch(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK2 + tow);
1427
+ ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK2 + tow);
1178
1428
  if (id === 4109) decodeMeas3Ranges(i, tow);
1179
1429
  else if (id === 4110) decodeMeas3CN0(i, len);
1180
1430
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -1188,6 +1438,7 @@ function parseSbfMeas(data) {
1188
1438
  // Annotate the CommonJS export names for ESM import in node:
1189
1439
  0 && (module.exports = {
1190
1440
  parseSbfAlmanac,
1441
+ parseSbfCnav,
1191
1442
  parseSbfIonoUtc,
1192
1443
  parseSbfMeas,
1193
1444
  parseSbfNav,
package/dist/sbf.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
1
+ import { E as Ephemeris, C as CnavEphemeris } from './nav-DImXUdbp.cjs';
2
2
 
3
3
  /**
4
4
  * Septentrio SBF framing shared by the measurement and navigation
@@ -191,6 +191,46 @@ interface SbfIonoUtcResult {
191
191
  */
192
192
  declare function parseSbfIonoUtc(data: Uint8Array): SbfIonoUtcResult;
193
193
 
194
+ /**
195
+ * Septentrio SBF raw CNAV navigation-bit blocks: GPSRawL2C (4018) and
196
+ * GPSRawL5 (4019) each carry one 300-bit GPS CNAV message.
197
+ *
198
+ * Block layout (mosaic-X5 reference guide §4): after the 8-byte SBF
199
+ * header, TOW u4 + WNc u2, SVID u1, CRCPassed u1, ViterbiCnt u1,
200
+ * Source u1, FreqNr u1, RxChannel u1, then NAVBits as u4[10] — the
201
+ * first received bit is the MSB of NAVBits[0] (each u4 little-endian
202
+ * in the stream, message bits MSB-first within the word), the unused
203
+ * 20 bits of NAVBits[9] to be ignored. RTKLIB demo5's
204
+ * decode_gpsrawcnav (src/rcv/septentrio.c) reads the same header but
205
+ * leaves the message body undecoded (a TODO stub); the field decoding
206
+ * here is `src/navbits/cnav.ts` working from IS-GPS-200 directly.
207
+ *
208
+ * The receiver's own CRCPassed flag is ignored in favor of re-running
209
+ * CRC-24Q on the transported bits, so `badCrc` counts exactly the
210
+ * messages this library rejected.
211
+ */
212
+
213
+ /** A CNAV ephemeris tagged with the SBF block (signal) it came from. */
214
+ interface SbfCnavEphemeris extends CnavEphemeris {
215
+ signal: 'L2C' | 'L5';
216
+ }
217
+ interface SbfCnavResult {
218
+ /** Assembled CNAV ephemerides in stream order, repeats suppressed. */
219
+ ephemerides: SbfCnavEphemeris[];
220
+ /** Raw messages whose CRC-24Q check failed (dropped). */
221
+ badCrc: number;
222
+ /** Total GPSRawL2C/GPSRawL5 blocks seen (with valid SBF framing). */
223
+ messages: number;
224
+ }
225
+ /**
226
+ * Decode every GPSRawL2C/GPSRawL5 block in an SBF byte stream and
227
+ * assemble the carried CNAV messages into ephemerides. L2C and L5 are
228
+ * assembled independently (one `CnavAssembler` per signal), so a data
229
+ * set complete on both signals yields one record per signal; each
230
+ * record's `signal` property names its source.
231
+ */
232
+ declare function parseSbfCnav(data: Uint8Array): SbfCnavResult;
233
+
194
234
  /**
195
235
  * Septentrio SBF raw-measurement decoding (MeasEpoch + Meas3) — the
196
236
  * path from a receiver log to RINEX-grade observables.
@@ -254,4 +294,4 @@ interface SbfParseResult {
254
294
  */
255
295
  declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
256
296
 
257
- export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfIonoUtcResult, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
297
+ export { CnavEphemeris, type SbfAlmanac, type SbfAlmanacResult, type SbfCnavEphemeris, type SbfCnavResult, type SbfGlonassAlmanac, type SbfIonoUtcResult, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfCnav, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
package/dist/sbf.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { E as Ephemeris } from './nav-BAI1a9vK.js';
1
+ import { E as Ephemeris, C as CnavEphemeris } from './nav-DImXUdbp.js';
2
2
 
3
3
  /**
4
4
  * Septentrio SBF framing shared by the measurement and navigation
@@ -191,6 +191,46 @@ interface SbfIonoUtcResult {
191
191
  */
192
192
  declare function parseSbfIonoUtc(data: Uint8Array): SbfIonoUtcResult;
193
193
 
194
+ /**
195
+ * Septentrio SBF raw CNAV navigation-bit blocks: GPSRawL2C (4018) and
196
+ * GPSRawL5 (4019) each carry one 300-bit GPS CNAV message.
197
+ *
198
+ * Block layout (mosaic-X5 reference guide §4): after the 8-byte SBF
199
+ * header, TOW u4 + WNc u2, SVID u1, CRCPassed u1, ViterbiCnt u1,
200
+ * Source u1, FreqNr u1, RxChannel u1, then NAVBits as u4[10] — the
201
+ * first received bit is the MSB of NAVBits[0] (each u4 little-endian
202
+ * in the stream, message bits MSB-first within the word), the unused
203
+ * 20 bits of NAVBits[9] to be ignored. RTKLIB demo5's
204
+ * decode_gpsrawcnav (src/rcv/septentrio.c) reads the same header but
205
+ * leaves the message body undecoded (a TODO stub); the field decoding
206
+ * here is `src/navbits/cnav.ts` working from IS-GPS-200 directly.
207
+ *
208
+ * The receiver's own CRCPassed flag is ignored in favor of re-running
209
+ * CRC-24Q on the transported bits, so `badCrc` counts exactly the
210
+ * messages this library rejected.
211
+ */
212
+
213
+ /** A CNAV ephemeris tagged with the SBF block (signal) it came from. */
214
+ interface SbfCnavEphemeris extends CnavEphemeris {
215
+ signal: 'L2C' | 'L5';
216
+ }
217
+ interface SbfCnavResult {
218
+ /** Assembled CNAV ephemerides in stream order, repeats suppressed. */
219
+ ephemerides: SbfCnavEphemeris[];
220
+ /** Raw messages whose CRC-24Q check failed (dropped). */
221
+ badCrc: number;
222
+ /** Total GPSRawL2C/GPSRawL5 blocks seen (with valid SBF framing). */
223
+ messages: number;
224
+ }
225
+ /**
226
+ * Decode every GPSRawL2C/GPSRawL5 block in an SBF byte stream and
227
+ * assemble the carried CNAV messages into ephemerides. L2C and L5 are
228
+ * assembled independently (one `CnavAssembler` per signal), so a data
229
+ * set complete on both signals yields one record per signal; each
230
+ * record's `signal` property names its source.
231
+ */
232
+ declare function parseSbfCnav(data: Uint8Array): SbfCnavResult;
233
+
194
234
  /**
195
235
  * Septentrio SBF raw-measurement decoding (MeasEpoch + Meas3) — the
196
236
  * path from a receiver log to RINEX-grade observables.
@@ -254,4 +294,4 @@ interface SbfParseResult {
254
294
  */
255
295
  declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
256
296
 
257
- export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfIonoUtcResult, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
297
+ export { CnavEphemeris, type SbfAlmanac, type SbfAlmanacResult, type SbfCnavEphemeris, type SbfCnavResult, type SbfGlonassAlmanac, type SbfIonoUtcResult, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfCnav, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
package/dist/sbf.js CHANGED
@@ -1,15 +1,19 @@
1
1
  import {
2
2
  crc16,
3
3
  parseSbfAlmanac,
4
+ parseSbfCnav,
4
5
  parseSbfIonoUtc,
5
6
  parseSbfMeas,
6
7
  parseSbfNav,
7
8
  scanSbfFrames
8
- } from "./chunk-ZNCQNHNI.js";
9
+ } from "./chunk-5S5HMAKW.js";
10
+ import "./chunk-6NCMACDO.js";
11
+ import "./chunk-IQ5OIMQD.js";
9
12
  import "./chunk-HVXYFUCB.js";
10
13
  import "./chunk-LEEU5OIO.js";
11
14
  export {
12
15
  parseSbfAlmanac,
16
+ parseSbfCnav,
13
17
  parseSbfIonoUtc,
14
18
  parseSbfMeas,
15
19
  parseSbfNav,
package/dist/ubx.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/ubx/index.ts
21
21
  var ubx_exports = {};
22
22
  __export(ubx_exports, {
23
+ parseUbxCnav: () => parseUbxCnav,
23
24
  parseUbxIonoUtc: () => parseUbxIonoUtc,
24
25
  parseUbxNav: () => parseUbxNav,
25
26
  parseUbxRawx: () => parseUbxRawx,
@@ -307,8 +308,238 @@ function parseUbxIonoUtc(data) {
307
308
  return { ionoCorrections, leapSeconds };
308
309
  }
309
310
 
310
- // src/ubx/index.ts
311
+ // src/navbits/cnav.ts
311
312
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
313
+ var SEC_PER_WEEK2 = 7 * 86400;
314
+ var HALF_WEEK = 302400;
315
+ var CNAV_PREAMBLE = 139;
316
+ var CNAV_A_REF = 26559710;
317
+ var CNAV_OMEGA_DOT_REF = -26e-10;
318
+ function crc24q(buff, bitLen) {
319
+ let crc = 0;
320
+ for (let i = 0; i < bitLen; i++) {
321
+ crc ^= (buff[i >> 3] >> 7 - (i & 7) & 1) << 23;
322
+ const msb = crc & 8388608;
323
+ crc = crc << 1 & 16777215;
324
+ if (msb) crc ^= 8801531;
325
+ }
326
+ return crc;
327
+ }
328
+ function cnavCrcOk(msg) {
329
+ if (msg.length < 38) return false;
330
+ return crc24q(msg, 276) === getBitU(msg, 276, 24);
331
+ }
332
+ function getBitU33(b, pos) {
333
+ return getBitU(b, pos, 1) * 2 ** 32 + getBitU(b, pos + 1, 32);
334
+ }
335
+ function getBitS33(b, pos) {
336
+ const u = getBitU33(b, pos);
337
+ return u < 2 ** 32 ? u : u - 2 ** 33;
338
+ }
339
+ function decodeType10(b, tow) {
340
+ return {
341
+ tow,
342
+ week: getBitU(b, 38, 13),
343
+ health: getBitU(b, 51, 3),
344
+ top: getBitU(b, 54, 11) * 300,
345
+ uraEd: getBitS(b, 65, 5),
346
+ toe: getBitU(b, 70, 11) * 300,
347
+ deltaA: getBitS(b, 81, 26) * 2 ** -9,
348
+ aDot: getBitS(b, 107, 25) * 2 ** -21,
349
+ deltaN0: getBitS(b, 132, 17) * 2 ** -44 * GPS_PI,
350
+ deltaN0Dot: getBitS(b, 149, 23) * 2 ** -57 * GPS_PI,
351
+ m0: getBitS33(b, 172) * 2 ** -32 * GPS_PI,
352
+ e: getBitU33(b, 205) * 2 ** -34,
353
+ omega: getBitS33(b, 238) * 2 ** -32 * GPS_PI,
354
+ integrityFlag: getBitU(b, 271, 1) === 1,
355
+ l2cPhasing: getBitU(b, 272, 1) === 1
356
+ };
357
+ }
358
+ function decodeType11(b) {
359
+ return {
360
+ toe: getBitU(b, 38, 11) * 300,
361
+ omega0: getBitS33(b, 49) * 2 ** -32 * GPS_PI,
362
+ i0: getBitS33(b, 82) * 2 ** -32 * GPS_PI,
363
+ deltaOmegaDot: getBitS(b, 115, 17) * 2 ** -44 * GPS_PI,
364
+ i0Dot: getBitS(b, 132, 15) * 2 ** -44 * GPS_PI,
365
+ cis: getBitS(b, 147, 16) * 2 ** -30,
366
+ cic: getBitS(b, 163, 16) * 2 ** -30,
367
+ crs: getBitS(b, 179, 24) * 2 ** -8,
368
+ crc: getBitS(b, 203, 24) * 2 ** -8,
369
+ cus: getBitS(b, 227, 21) * 2 ** -30,
370
+ cuc: getBitS(b, 248, 21) * 2 ** -30
371
+ };
372
+ }
373
+ var isc = (b, pos) => {
374
+ const raw = getBitS(b, pos, 13);
375
+ return raw === -4096 ? null : raw * 2 ** -35;
376
+ };
377
+ function decodeClock(b, msgType) {
378
+ const clock = {
379
+ msgType,
380
+ top: getBitU(b, 38, 11) * 300,
381
+ uraNed0: getBitS(b, 49, 5),
382
+ uraNed1: getBitU(b, 54, 3),
383
+ uraNed2: getBitU(b, 57, 3),
384
+ toc: getBitU(b, 60, 11) * 300,
385
+ af0: getBitS(b, 71, 26) * 2 ** -35,
386
+ af1: getBitS(b, 97, 20) * 2 ** -48,
387
+ af2: getBitS(b, 117, 10) * 2 ** -60
388
+ };
389
+ if (msgType === 30) {
390
+ clock.tgd = isc(b, 127);
391
+ clock.iscL1ca = isc(b, 140);
392
+ clock.iscL2c = isc(b, 153);
393
+ clock.iscL5i5 = isc(b, 166);
394
+ clock.iscL5q5 = isc(b, 179);
395
+ clock.ionoAlpha = [
396
+ getBitS(b, 192, 8) * 2 ** -30,
397
+ getBitS(b, 200, 8) * 2 ** -27,
398
+ getBitS(b, 208, 8) * 2 ** -24,
399
+ getBitS(b, 216, 8) * 2 ** -24
400
+ ];
401
+ clock.ionoBeta = [
402
+ getBitS(b, 224, 8) * 2 ** 11,
403
+ getBitS(b, 232, 8) * 2 ** 14,
404
+ getBitS(b, 240, 8) * 2 ** 16,
405
+ getBitS(b, 248, 8) * 2 ** 16
406
+ ];
407
+ clock.wnOp = getBitU(b, 256, 8);
408
+ }
409
+ return clock;
410
+ }
411
+ function resolveWeek(week, towSec, sec) {
412
+ if (sec < towSec - HALF_WEEK) return week + 1;
413
+ if (sec > towSec + HALF_WEEK) return week - 1;
414
+ return week;
415
+ }
416
+ function gpsDate(week, sec) {
417
+ return new Date(GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + sec) * 1e3);
418
+ }
419
+ var CnavAssembler = class {
420
+ sats = /* @__PURE__ */ new Map();
421
+ /**
422
+ * Push one 300-bit CNAV message (38+ bytes, bit 0 = first bit of
423
+ * the preamble). Returns the newly completed ephemeris, or null.
424
+ * Messages that are not MT10/MT11/MT30-37, or whose preamble/PRN
425
+ * are out of range, are ignored.
426
+ */
427
+ push(msg) {
428
+ if (msg.length < 38 || getBitU(msg, 0, 8) !== CNAV_PREAMBLE) return null;
429
+ const prn = getBitU(msg, 8, 6);
430
+ if (prn < 1 || prn > 32) return null;
431
+ const type = getBitU(msg, 14, 6);
432
+ const tow = getBitU(msg, 20, 17) * 6;
433
+ let sat = this.sats.get(prn);
434
+ if (!sat) {
435
+ sat = {};
436
+ this.sats.set(prn, sat);
437
+ }
438
+ if (type === 10) sat.t10 = decodeType10(msg, tow);
439
+ else if (type === 11) sat.t11 = decodeType11(msg);
440
+ else if (type >= 30 && type <= 37) {
441
+ sat.clock = decodeClock(msg, type);
442
+ if (type === 30) sat.extras = sat.clock;
443
+ } else return null;
444
+ return this.tryEmit(prn, sat);
445
+ }
446
+ tryEmit(prn, sat) {
447
+ const { t10, t11, clock, extras } = sat;
448
+ if (!t10 || !t11 || !clock) return null;
449
+ if (t10.toe !== t11.toe || clock.toc !== t10.toe) return null;
450
+ const key = `${t10.week}:${t10.toe}:${clock.af0}:${clock.af1}`;
451
+ if (key === sat.lastKey) return null;
452
+ sat.lastKey = key;
453
+ const weekToe = resolveWeek(t10.week, t10.tow, t10.toe);
454
+ const a = CNAV_A_REF + t10.deltaA;
455
+ return {
456
+ system: "G",
457
+ prn: `G${String(prn).padStart(2, "0")}`,
458
+ week: t10.week,
459
+ health: t10.health,
460
+ uraEd: t10.uraEd,
461
+ uraNed0: clock.uraNed0,
462
+ uraNed1: clock.uraNed1,
463
+ uraNed2: clock.uraNed2,
464
+ top: t10.top,
465
+ toe: t10.toe,
466
+ toeDate: gpsDate(weekToe, t10.toe),
467
+ a,
468
+ deltaA: t10.deltaA,
469
+ aDot: t10.aDot,
470
+ deltaN0: t10.deltaN0,
471
+ deltaN0Dot: t10.deltaN0Dot,
472
+ m0: t10.m0,
473
+ e: t10.e,
474
+ omega: t10.omega,
475
+ omega0: t11.omega0,
476
+ i0: t11.i0,
477
+ omegaDot: CNAV_OMEGA_DOT_REF * GPS_PI + t11.deltaOmegaDot,
478
+ deltaOmegaDot: t11.deltaOmegaDot,
479
+ i0Dot: t11.i0Dot,
480
+ cis: t11.cis,
481
+ cic: t11.cic,
482
+ crs: t11.crs,
483
+ crc: t11.crc,
484
+ cus: t11.cus,
485
+ cuc: t11.cuc,
486
+ toc: clock.toc,
487
+ tocDate: gpsDate(weekToe, clock.toc),
488
+ af0: clock.af0,
489
+ af1: clock.af1,
490
+ af2: clock.af2,
491
+ clockMsgType: clock.msgType,
492
+ tgd: extras?.tgd ?? null,
493
+ iscL1ca: extras?.iscL1ca ?? null,
494
+ iscL2c: extras?.iscL2c ?? null,
495
+ iscL5i5: extras?.iscL5i5 ?? null,
496
+ iscL5q5: extras?.iscL5q5 ?? null,
497
+ ...extras?.ionoAlpha && { ionoAlpha: extras.ionoAlpha },
498
+ ...extras?.ionoBeta && { ionoBeta: extras.ionoBeta },
499
+ ...extras?.wnOp !== void 0 && { wnOp: extras.wnOp },
500
+ integrityFlag: t10.integrityFlag,
501
+ l2cPhasing: t10.l2cPhasing,
502
+ tow: t10.tow
503
+ };
504
+ }
505
+ };
506
+
507
+ // src/ubx/cnav.ts
508
+ function parseUbxCnav(data) {
509
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
510
+ const assembler = new CnavAssembler();
511
+ const ephemerides = [];
512
+ let badCrc = 0;
513
+ let messages = 0;
514
+ for (const f of ubxFrames(data)) {
515
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
516
+ const p = f.payload;
517
+ if (p.length < 8 + 40) continue;
518
+ if (p[0] !== 0 || p[2] !== 3 && p[2] !== 4) continue;
519
+ const svId = p[1];
520
+ if (svId < 1 || svId > 32) continue;
521
+ messages++;
522
+ const msg = new Uint8Array(40);
523
+ const base = f.payloadStart + 8;
524
+ for (let k = 0; k < 10; k++) {
525
+ const w = view.getUint32(base + 4 * k, true);
526
+ msg[4 * k] = w >>> 24;
527
+ msg[4 * k + 1] = w >>> 16 & 255;
528
+ msg[4 * k + 2] = w >>> 8 & 255;
529
+ msg[4 * k + 3] = w & 255;
530
+ }
531
+ if (!cnavCrcOk(msg)) {
532
+ badCrc++;
533
+ continue;
534
+ }
535
+ const eph = assembler.push(msg);
536
+ if (eph) ephemerides.push(eph);
537
+ }
538
+ return { ephemerides, badCrc, messages };
539
+ }
540
+
541
+ // src/ubx/index.ts
542
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
312
543
  var MS_PER_WEEK = 7 * 864e5;
313
544
  var SIGNALS = {
314
545
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -391,7 +622,7 @@ function parseUbxRawx(data) {
391
622
  if (!codes.includes(sig[1])) codes.push(sig[1]);
392
623
  }
393
624
  epochs.push({
394
- timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + Math.round(rcvTow * 1e3),
625
+ timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK + Math.round(rcvTow * 1e3),
395
626
  leapS: leapValid ? leapS : null,
396
627
  meas
397
628
  });
@@ -401,6 +632,7 @@ function parseUbxRawx(data) {
401
632
  }
402
633
  // Annotate the CommonJS export names for ESM import in node:
403
634
  0 && (module.exports = {
635
+ parseUbxCnav,
404
636
  parseUbxIonoUtc,
405
637
  parseUbxNav,
406
638
  parseUbxRawx,