gnss-js 1.16.0 → 1.18.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,8 @@ 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,
25
+ parseSbfIonoUtc: () => parseSbfIonoUtc,
24
26
  parseSbfMeas: () => parseSbfMeas,
25
27
  parseSbfNav: () => parseSbfNav,
26
28
  sbfCrc16: () => crc16,
@@ -484,9 +486,291 @@ function parseSbfAlmanac(data) {
484
486
  return { almanacs, badCrc };
485
487
  }
486
488
 
489
+ // src/sbf/iono.ts
490
+ var F4_DNU2 = -2e10;
491
+ function parseSbfIonoUtc(data) {
492
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
493
+ const ionoCorrections = {};
494
+ let leapSeconds = null;
495
+ const f4s = (b, n) => {
496
+ const out = [];
497
+ for (let k = 0; k < n; k++) {
498
+ const v = view.getFloat32(b + 4 * k, true);
499
+ if (v === F4_DNU2) return null;
500
+ out.push(v);
501
+ }
502
+ return out;
503
+ };
504
+ scanSbfFrames(data, view, (id, b, len) => {
505
+ if ((id === 5893 || id === 4120) && len >= 48) {
506
+ const alpha = f4s(b + 16, 4);
507
+ const beta = f4s(b + 32, 4);
508
+ if (!alpha || !beta) return;
509
+ const sys = id === 5893 ? "GPS" : "BDS";
510
+ ionoCorrections[`${sys}A`] = alpha;
511
+ ionoCorrections[`${sys}B`] = beta;
512
+ } else if (id === 4030 && len >= 28) {
513
+ const ai = f4s(b + 16, 3);
514
+ if (ai) ionoCorrections["GAL"] = ai;
515
+ } else if (id === 5894 && len >= 37) {
516
+ leapSeconds = view.getInt8(b + 33);
517
+ }
518
+ });
519
+ return { ionoCorrections, leapSeconds };
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
+
487
771
  // src/sbf/index.ts
488
772
  var CLIGHT = 299792458;
489
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
773
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
490
774
  var MS_PER_WEEK2 = 7 * 864e5;
491
775
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
492
776
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -1140,7 +1424,7 @@ function parseSbfMeas(data) {
1140
1424
  const tow = view.getUint32(i + 8, true);
1141
1425
  const wnc = view.getUint16(i + 12, true);
1142
1426
  if (tow !== 4294967295 && wnc !== 65535) {
1143
- ensureEpoch(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK2 + tow);
1427
+ ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK2 + tow);
1144
1428
  if (id === 4109) decodeMeas3Ranges(i, tow);
1145
1429
  else if (id === 4110) decodeMeas3CN0(i, len);
1146
1430
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -1154,6 +1438,8 @@ function parseSbfMeas(data) {
1154
1438
  // Annotate the CommonJS export names for ESM import in node:
1155
1439
  0 && (module.exports = {
1156
1440
  parseSbfAlmanac,
1441
+ parseSbfCnav,
1442
+ parseSbfIonoUtc,
1157
1443
  parseSbfMeas,
1158
1444
  parseSbfNav,
1159
1445
  sbfCrc16,
package/dist/sbf.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
2
+ import { C as CnavEphemeris } from './cnav-CRns0ECn.cjs';
2
3
 
3
4
  /**
4
5
  * Septentrio SBF framing shared by the measurement and navigation
@@ -151,6 +152,86 @@ interface SbfAlmanacResult {
151
152
  */
152
153
  declare function parseSbfAlmanac(data: Uint8Array): SbfAlmanacResult;
153
154
 
155
+ /**
156
+ * Septentrio SBF decoded ionosphere / UTC blocks.
157
+ *
158
+ * GPSIon (5893) and BDSIon (4120) carry the eight Klobuchar vertical
159
+ * delay coefficients, GALIon (4030) the three NeQuick-G effective
160
+ * ionisation level coefficients, and GPSUtc (5894) the GPS-UTC
161
+ * parameters from LNAV subframe 4 page 18 (only ΔtLS, the current
162
+ * leap-second count, is extracted here).
163
+ *
164
+ * Units: SBF stores the coefficients as f4 floats already in the
165
+ * semicircle-based SI units of the broadcast messages — alpha_n in
166
+ * s/semicircle^n, beta_n in s/semicircle^n, ai_n in sfu/deg^n — which
167
+ * are exactly the units a RINEX nav header prints. Values are passed
168
+ * through unscaled, so the output matches what `parseNavFile` reads
169
+ * from a converted nav header to float32 precision.
170
+ *
171
+ * Ported from RTKLIB demo5 (rtklibexplorer), src/rcv/septentrio.c
172
+ * (decode_gpsion / decode_galion / decode_cmpion / decode_gpsutc),
173
+ * BSD-2-Clause, and cross-checked against the Septentrio mosaic-X5
174
+ * reference guide (which documents all four block layouts, including
175
+ * the do-not-use markers RTKLIB does not check).
176
+ */
177
+ interface SbfIonoUtcResult {
178
+ /**
179
+ * Iono coefficient sets keyed like `NavHeader.ionoCorrections`:
180
+ * `GPSA`/`GPSB` (Klobuchar alpha/beta), `GAL` ([ai0, ai1, ai2],
181
+ * RINEX GAL header convention), `BDSA`/`BDSB`. The blocks repeat;
182
+ * the last valid block of each type in the stream wins.
183
+ */
184
+ ionoCorrections: Record<string, number[]>;
185
+ /** GPS-UTC ΔtLS from the last GPSUtc block, if any. */
186
+ leapSeconds: number | null;
187
+ }
188
+ /**
189
+ * Decode every GPSIon/GALIon/BDSIon/GPSUtc block in an SBF byte
190
+ * stream. Blocks with do-not-use coefficients are skipped; other block
191
+ * types are skipped silently.
192
+ */
193
+ declare function parseSbfIonoUtc(data: Uint8Array): SbfIonoUtcResult;
194
+
195
+ /**
196
+ * Septentrio SBF raw CNAV navigation-bit blocks: GPSRawL2C (4018) and
197
+ * GPSRawL5 (4019) each carry one 300-bit GPS CNAV message.
198
+ *
199
+ * Block layout (mosaic-X5 reference guide §4): after the 8-byte SBF
200
+ * header, TOW u4 + WNc u2, SVID u1, CRCPassed u1, ViterbiCnt u1,
201
+ * Source u1, FreqNr u1, RxChannel u1, then NAVBits as u4[10] — the
202
+ * first received bit is the MSB of NAVBits[0] (each u4 little-endian
203
+ * in the stream, message bits MSB-first within the word), the unused
204
+ * 20 bits of NAVBits[9] to be ignored. RTKLIB demo5's
205
+ * decode_gpsrawcnav (src/rcv/septentrio.c) reads the same header but
206
+ * leaves the message body undecoded (a TODO stub); the field decoding
207
+ * here is `src/navbits/cnav.ts` working from IS-GPS-200 directly.
208
+ *
209
+ * The receiver's own CRCPassed flag is ignored in favor of re-running
210
+ * CRC-24Q on the transported bits, so `badCrc` counts exactly the
211
+ * messages this library rejected.
212
+ */
213
+
214
+ /** A CNAV ephemeris tagged with the SBF block (signal) it came from. */
215
+ interface SbfCnavEphemeris extends CnavEphemeris {
216
+ signal: 'L2C' | 'L5';
217
+ }
218
+ interface SbfCnavResult {
219
+ /** Assembled CNAV ephemerides in stream order, repeats suppressed. */
220
+ ephemerides: SbfCnavEphemeris[];
221
+ /** Raw messages whose CRC-24Q check failed (dropped). */
222
+ badCrc: number;
223
+ /** Total GPSRawL2C/GPSRawL5 blocks seen (with valid SBF framing). */
224
+ messages: number;
225
+ }
226
+ /**
227
+ * Decode every GPSRawL2C/GPSRawL5 block in an SBF byte stream and
228
+ * assemble the carried CNAV messages into ephemerides. L2C and L5 are
229
+ * assembled independently (one `CnavAssembler` per signal), so a data
230
+ * set complete on both signals yields one record per signal; each
231
+ * record's `signal` property names its source.
232
+ */
233
+ declare function parseSbfCnav(data: Uint8Array): SbfCnavResult;
234
+
154
235
  /**
155
236
  * Septentrio SBF raw-measurement decoding (MeasEpoch + Meas3) — the
156
237
  * path from a receiver log to RINEX-grade observables.
@@ -214,4 +295,4 @@ interface SbfParseResult {
214
295
  */
215
296
  declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
216
297
 
217
- export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
298
+ 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,5 @@
1
1
  import { E as Ephemeris } from './nav-BAI1a9vK.js';
2
+ import { C as CnavEphemeris } from './cnav-CRns0ECn.js';
2
3
 
3
4
  /**
4
5
  * Septentrio SBF framing shared by the measurement and navigation
@@ -151,6 +152,86 @@ interface SbfAlmanacResult {
151
152
  */
152
153
  declare function parseSbfAlmanac(data: Uint8Array): SbfAlmanacResult;
153
154
 
155
+ /**
156
+ * Septentrio SBF decoded ionosphere / UTC blocks.
157
+ *
158
+ * GPSIon (5893) and BDSIon (4120) carry the eight Klobuchar vertical
159
+ * delay coefficients, GALIon (4030) the three NeQuick-G effective
160
+ * ionisation level coefficients, and GPSUtc (5894) the GPS-UTC
161
+ * parameters from LNAV subframe 4 page 18 (only ΔtLS, the current
162
+ * leap-second count, is extracted here).
163
+ *
164
+ * Units: SBF stores the coefficients as f4 floats already in the
165
+ * semicircle-based SI units of the broadcast messages — alpha_n in
166
+ * s/semicircle^n, beta_n in s/semicircle^n, ai_n in sfu/deg^n — which
167
+ * are exactly the units a RINEX nav header prints. Values are passed
168
+ * through unscaled, so the output matches what `parseNavFile` reads
169
+ * from a converted nav header to float32 precision.
170
+ *
171
+ * Ported from RTKLIB demo5 (rtklibexplorer), src/rcv/septentrio.c
172
+ * (decode_gpsion / decode_galion / decode_cmpion / decode_gpsutc),
173
+ * BSD-2-Clause, and cross-checked against the Septentrio mosaic-X5
174
+ * reference guide (which documents all four block layouts, including
175
+ * the do-not-use markers RTKLIB does not check).
176
+ */
177
+ interface SbfIonoUtcResult {
178
+ /**
179
+ * Iono coefficient sets keyed like `NavHeader.ionoCorrections`:
180
+ * `GPSA`/`GPSB` (Klobuchar alpha/beta), `GAL` ([ai0, ai1, ai2],
181
+ * RINEX GAL header convention), `BDSA`/`BDSB`. The blocks repeat;
182
+ * the last valid block of each type in the stream wins.
183
+ */
184
+ ionoCorrections: Record<string, number[]>;
185
+ /** GPS-UTC ΔtLS from the last GPSUtc block, if any. */
186
+ leapSeconds: number | null;
187
+ }
188
+ /**
189
+ * Decode every GPSIon/GALIon/BDSIon/GPSUtc block in an SBF byte
190
+ * stream. Blocks with do-not-use coefficients are skipped; other block
191
+ * types are skipped silently.
192
+ */
193
+ declare function parseSbfIonoUtc(data: Uint8Array): SbfIonoUtcResult;
194
+
195
+ /**
196
+ * Septentrio SBF raw CNAV navigation-bit blocks: GPSRawL2C (4018) and
197
+ * GPSRawL5 (4019) each carry one 300-bit GPS CNAV message.
198
+ *
199
+ * Block layout (mosaic-X5 reference guide §4): after the 8-byte SBF
200
+ * header, TOW u4 + WNc u2, SVID u1, CRCPassed u1, ViterbiCnt u1,
201
+ * Source u1, FreqNr u1, RxChannel u1, then NAVBits as u4[10] — the
202
+ * first received bit is the MSB of NAVBits[0] (each u4 little-endian
203
+ * in the stream, message bits MSB-first within the word), the unused
204
+ * 20 bits of NAVBits[9] to be ignored. RTKLIB demo5's
205
+ * decode_gpsrawcnav (src/rcv/septentrio.c) reads the same header but
206
+ * leaves the message body undecoded (a TODO stub); the field decoding
207
+ * here is `src/navbits/cnav.ts` working from IS-GPS-200 directly.
208
+ *
209
+ * The receiver's own CRCPassed flag is ignored in favor of re-running
210
+ * CRC-24Q on the transported bits, so `badCrc` counts exactly the
211
+ * messages this library rejected.
212
+ */
213
+
214
+ /** A CNAV ephemeris tagged with the SBF block (signal) it came from. */
215
+ interface SbfCnavEphemeris extends CnavEphemeris {
216
+ signal: 'L2C' | 'L5';
217
+ }
218
+ interface SbfCnavResult {
219
+ /** Assembled CNAV ephemerides in stream order, repeats suppressed. */
220
+ ephemerides: SbfCnavEphemeris[];
221
+ /** Raw messages whose CRC-24Q check failed (dropped). */
222
+ badCrc: number;
223
+ /** Total GPSRawL2C/GPSRawL5 blocks seen (with valid SBF framing). */
224
+ messages: number;
225
+ }
226
+ /**
227
+ * Decode every GPSRawL2C/GPSRawL5 block in an SBF byte stream and
228
+ * assemble the carried CNAV messages into ephemerides. L2C and L5 are
229
+ * assembled independently (one `CnavAssembler` per signal), so a data
230
+ * set complete on both signals yields one record per signal; each
231
+ * record's `signal` property names its source.
232
+ */
233
+ declare function parseSbfCnav(data: Uint8Array): SbfCnavResult;
234
+
154
235
  /**
155
236
  * Septentrio SBF raw-measurement decoding (MeasEpoch + Meas3) — the
156
237
  * path from a receiver log to RINEX-grade observables.
@@ -214,4 +295,4 @@ interface SbfParseResult {
214
295
  */
215
296
  declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
216
297
 
217
- export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
298
+ 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,14 +1,20 @@
1
1
  import {
2
2
  crc16,
3
3
  parseSbfAlmanac,
4
+ parseSbfCnav,
5
+ parseSbfIonoUtc,
4
6
  parseSbfMeas,
5
7
  parseSbfNav,
6
8
  scanSbfFrames
7
- } from "./chunk-YPOVPXNK.js";
9
+ } from "./chunk-CG7SF45F.js";
10
+ import "./chunk-Y5IWUPJQ.js";
11
+ import "./chunk-IQ5OIMQD.js";
8
12
  import "./chunk-HVXYFUCB.js";
9
13
  import "./chunk-LEEU5OIO.js";
10
14
  export {
11
15
  parseSbfAlmanac,
16
+ parseSbfCnav,
17
+ parseSbfIonoUtc,
12
18
  parseSbfMeas,
13
19
  parseSbfNav,
14
20
  crc16 as sbfCrc16,