gnss-js 1.15.0 → 1.17.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.d.cts CHANGED
@@ -9,8 +9,8 @@ export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResul
9
9
  export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, p as parseNavFile } from './nav-BAI1a9vK.cjs';
10
10
  export { B as BitReader, E as EphemerisInfo, R as Rtcm3DebugHandler, a as Rtcm3Decoder, b as Rtcm3Frame, d as decodeEphemeris, r as readString, c as reportDecodeError, s as setRtcm3DebugHandler } from './ephemeris-Ohl6NAfw.cjs';
11
11
  export { MessageTypeStats, MsmEpoch, MsmSatObs, MsmSignal, RTCM3_MESSAGE_NAMES, SatCn0, SignalCn0, StationMeta, StreamStats, createStationMeta, createStreamStats, decodeMsmFull, msmEpochToDate, resetGloFreqCache, rtcm3Constellation, setGloFreqNumber, updateStationMeta, updateStreamStats } from './rtcm3.cjs';
12
- export { UbxMeasurement, UbxParseResult, UbxRawxEpoch, parseUbxRawx } from './ubx.cjs';
13
- export { SbfAlmanac, SbfAlmanacResult, SbfGlonassAlmanac, SbfKeplerAlmanac, SbfMeasEpoch, SbfMeasurement, SbfNavResult, SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav } from './sbf.cjs';
12
+ export { UbxFrame, UbxIonoUtcResult, UbxMeasurement, UbxNavOptions, UbxNavResult, UbxParseResult, UbxRawxEpoch, parseUbxIonoUtc, parseUbxNav, parseUbxRawx, ubxFrames } from './ubx.cjs';
13
+ export { SbfAlmanac, SbfAlmanacResult, SbfGlonassAlmanac, SbfIonoUtcResult, SbfKeplerAlmanac, SbfMeasEpoch, SbfMeasurement, SbfNavResult, SbfParseResult, parseSbfAlmanac, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, sbfCrc16, scanSbfFrames } from './sbf.cjs';
14
14
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelRange } from './novatel.cjs';
15
15
  export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.cjs';
16
16
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
package/dist/index.d.ts CHANGED
@@ -9,8 +9,8 @@ export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResul
9
9
  export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, p as parseNavFile } from './nav-BAI1a9vK.js';
10
10
  export { B as BitReader, E as EphemerisInfo, R as Rtcm3DebugHandler, a as Rtcm3Decoder, b as Rtcm3Frame, d as decodeEphemeris, r as readString, c as reportDecodeError, s as setRtcm3DebugHandler } from './ephemeris-Ohl6NAfw.js';
11
11
  export { MessageTypeStats, MsmEpoch, MsmSatObs, MsmSignal, RTCM3_MESSAGE_NAMES, SatCn0, SignalCn0, StationMeta, StreamStats, createStationMeta, createStreamStats, decodeMsmFull, msmEpochToDate, resetGloFreqCache, rtcm3Constellation, setGloFreqNumber, updateStationMeta, updateStreamStats } from './rtcm3.js';
12
- export { UbxMeasurement, UbxParseResult, UbxRawxEpoch, parseUbxRawx } from './ubx.js';
13
- export { SbfAlmanac, SbfAlmanacResult, SbfGlonassAlmanac, SbfKeplerAlmanac, SbfMeasEpoch, SbfMeasurement, SbfNavResult, SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav } from './sbf.js';
12
+ export { UbxFrame, UbxIonoUtcResult, UbxMeasurement, UbxNavOptions, UbxNavResult, UbxParseResult, UbxRawxEpoch, parseUbxIonoUtc, parseUbxNav, parseUbxRawx, ubxFrames } from './ubx.js';
13
+ export { SbfAlmanac, SbfAlmanacResult, SbfGlonassAlmanac, SbfIonoUtcResult, SbfKeplerAlmanac, SbfMeasEpoch, SbfMeasurement, SbfNavResult, SbfParseResult, parseSbfAlmanac, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, sbfCrc16, scanSbfFrames } from './sbf.js';
14
14
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelRange } from './novatel.js';
15
15
  export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.js';
16
16
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
package/dist/index.js CHANGED
@@ -34,20 +34,27 @@ import {
34
34
  phiBPSK
35
35
  } from "./chunk-2K3FCJBX.js";
36
36
  import {
37
- parseUbxRawx
38
- } from "./chunk-5IXK25MX.js";
37
+ parseUbxIonoUtc,
38
+ parseUbxNav,
39
+ parseUbxRawx,
40
+ ubxFrames
41
+ } from "./chunk-IG5CDNDS.js";
39
42
  import {
43
+ crc16,
40
44
  parseSbfAlmanac,
45
+ parseSbfIonoUtc,
41
46
  parseSbfMeas,
42
- parseSbfNav
43
- } from "./chunk-BEQSEJMU.js";
47
+ parseSbfNav,
48
+ scanSbfFrames
49
+ } from "./chunk-ZNCQNHNI.js";
44
50
  import {
45
51
  OEM4_HLEN,
46
52
  crc32,
47
53
  oem4Frames,
48
54
  parseNovatelNav,
49
55
  parseNovatelRange
50
- } from "./chunk-I4UV45NT.js";
56
+ } from "./chunk-7QR2KPDU.js";
57
+ import "./chunk-REYOYF7O.js";
51
58
  import {
52
59
  connectToMountpoint,
53
60
  fetchSourcetable,
@@ -482,11 +489,14 @@ export {
482
489
  parseNovatelRange,
483
490
  parseRinexStream,
484
491
  parseSbfAlmanac,
492
+ parseSbfIonoUtc,
485
493
  parseSbfMeas,
486
494
  parseSbfNav,
487
495
  parseSinexBiasDcb,
488
496
  parseSourcetable,
489
497
  parseSp3,
498
+ parseUbxIonoUtc,
499
+ parseUbxNav,
490
500
  parseUbxRawx,
491
501
  phiAltBOC,
492
502
  phiBOCc,
@@ -500,6 +510,8 @@ export {
500
510
  rhumbLine,
501
511
  rtcm3Constellation,
502
512
  satClockCorrection,
513
+ crc16 as sbfCrc16,
514
+ scanSbfFrames,
503
515
  selectEphemeris,
504
516
  setGloFreqNumber,
505
517
  setRtcm3DebugHandler,
@@ -509,6 +521,7 @@ export {
509
521
  systemCmp,
510
522
  systemName,
511
523
  transformFrame,
524
+ ubxFrames,
512
525
  updateStationMeta,
513
526
  updateStreamStats,
514
527
  verifyChecksum,
package/dist/novatel.cjs CHANGED
@@ -290,11 +290,26 @@ function getUtcDate(date) {
290
290
  }
291
291
 
292
292
  // src/novatel/nav.ts
293
+ var ID_IONUTC = 8;
293
294
  var ID_RAWEPHEM = 41;
294
295
  var ID_GLOEPHEMERIS = 723;
296
+ var ID_GALEPHEMERIS = 1122;
297
+ var ID_QZSSEPHEMERIS = 1336;
298
+ var ID_BDSEPHEMERIS = 1696;
295
299
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
300
+ var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
296
301
  var SEC_PER_WEEK2 = 7 * 86400;
297
302
  var SEC_PER_DAY = 86400;
303
+ var MS_PER_WEEK = SEC_PER_WEEK2 * 1e3;
304
+ var gpsMs = (week, sec) => GPS_EPOCH_MS2 + week * MS_PER_WEEK + sec * 1e3;
305
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK2;
306
+ function nearWeekMs(refMs, sow) {
307
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS2) / MS_PER_WEEK);
308
+ let ms = gpsMs(refWeek, sow);
309
+ if (ms < refMs - MS_PER_WEEK / 2) ms += MS_PER_WEEK;
310
+ else if (ms > refMs + MS_PER_WEEK / 2) ms -= MS_PER_WEEK;
311
+ return ms;
312
+ }
298
313
  function decodeRawEphem(data, view, p) {
299
314
  const prn = view.getUint32(p, true);
300
315
  if (prn < 1 || prn > 32) return null;
@@ -340,23 +355,184 @@ function decodeGloEphemeris(view, p) {
340
355
  freqNum
341
356
  };
342
357
  }
358
+ function decodeGalEphemeris(view, p, headerMs) {
359
+ const prn = view.getUint32(p, true);
360
+ if (prn < 1 || prn > 36) return null;
361
+ const fnav = (view.getUint32(p + 4, true) & 1) !== 0;
362
+ const svhE1b = view.getUint8(p + 12) & 3;
363
+ const svhE5a = view.getUint8(p + 13) & 3;
364
+ const svhE5b = view.getUint8(p + 14) & 3;
365
+ const dvsE1b = view.getUint8(p + 15) & 1;
366
+ const dvsE5a = view.getUint8(p + 16) & 1;
367
+ const dvsE5b = view.getUint8(p + 17) & 1;
368
+ const iodNav = view.getUint32(p + 20, true);
369
+ const toes = view.getUint32(p + 24, true);
370
+ const c = fnav ? p + 148 : p + 176;
371
+ const tocs = view.getUint32(c, true);
372
+ const toeMs = nearWeekMs(headerMs, toes);
373
+ const tocDate = new Date(nearWeekMs(headerMs, tocs));
374
+ return {
375
+ fnav,
376
+ eph: {
377
+ system: "E",
378
+ prn: `E${String(prn).padStart(2, "0")}`,
379
+ toc: sowOf(tocDate.getTime()),
380
+ tocDate,
381
+ af0: view.getFloat64(c + 4, true),
382
+ af1: view.getFloat64(c + 12, true),
383
+ af2: view.getFloat64(c + 20, true),
384
+ iode: iodNav,
385
+ crs: view.getFloat64(p + 92, true),
386
+ deltaN: view.getFloat64(p + 36, true),
387
+ m0: view.getFloat64(p + 44, true),
388
+ cuc: view.getFloat64(p + 68, true),
389
+ e: view.getFloat64(p + 52, true),
390
+ cus: view.getFloat64(p + 76, true),
391
+ sqrtA: view.getFloat64(p + 28, true),
392
+ toe: toes,
393
+ cic: view.getFloat64(p + 100, true),
394
+ omega0: view.getFloat64(p + 132, true),
395
+ cis: view.getFloat64(p + 108, true),
396
+ i0: view.getFloat64(p + 116, true),
397
+ crc: view.getFloat64(p + 84, true),
398
+ omega: view.getFloat64(p + 60, true),
399
+ omegaDot: view.getFloat64(p + 140, true),
400
+ idot: view.getFloat64(p + 124, true),
401
+ week: Math.floor((toeMs - GPS_EPOCH_MS2) / MS_PER_WEEK),
402
+ // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
403
+ // 3-5, E5b in 6-8) — same packing as RTKLIB.
404
+ svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
405
+ tgd: view.getFloat64(p + 204, true)
406
+ // BGD E5a/E1 (RINEX slot)
407
+ }
408
+ };
409
+ }
410
+ function decodeBdsEphemeris(view, p) {
411
+ const prn = view.getUint32(p, true);
412
+ if (prn < 1 || prn > 63) return null;
413
+ const week = view.getUint32(p + 4, true);
414
+ const health = view.getUint32(p + 16, true) & 1;
415
+ const tgd1 = view.getFloat64(p + 20, true);
416
+ const tocs = view.getUint32(p + 40, true);
417
+ const toes = view.getUint32(p + 72, true);
418
+ const tocDate = new Date(BDT_EPOCH_MS + week * MS_PER_WEEK + tocs * 1e3);
419
+ return {
420
+ system: "C",
421
+ prn: `C${String(prn).padStart(2, "0")}`,
422
+ toc: sowOf(tocDate.getTime()),
423
+ tocDate,
424
+ af0: view.getFloat64(p + 44, true),
425
+ af1: view.getFloat64(p + 52, true),
426
+ af2: view.getFloat64(p + 60, true),
427
+ iode: view.getUint32(p + 68, true),
428
+ // AODE
429
+ crs: view.getFloat64(p + 172, true),
430
+ deltaN: view.getFloat64(p + 100, true),
431
+ m0: view.getFloat64(p + 108, true),
432
+ cuc: view.getFloat64(p + 148, true),
433
+ e: view.getFloat64(p + 84, true),
434
+ cus: view.getFloat64(p + 156, true),
435
+ sqrtA: view.getFloat64(p + 76, true),
436
+ toe: toes,
437
+ cic: view.getFloat64(p + 180, true),
438
+ omega0: view.getFloat64(p + 116, true),
439
+ cis: view.getFloat64(p + 188, true),
440
+ i0: view.getFloat64(p + 132, true),
441
+ crc: view.getFloat64(p + 164, true),
442
+ omega: view.getFloat64(p + 92, true),
443
+ omegaDot: view.getFloat64(p + 124, true),
444
+ idot: view.getFloat64(p + 140, true),
445
+ week,
446
+ // RINEX BDS week field is the BDT week
447
+ svHealth: health,
448
+ tgd: tgd1
449
+ // TGD1 (B1) — RINEX slot
450
+ };
451
+ }
452
+ function decodeQzssEphemeris(view, p) {
453
+ const prn = view.getUint32(p, true);
454
+ if (prn < 193 || prn > 202) return null;
455
+ const health = view.getUint32(p + 12, true) & 63;
456
+ const iode1 = view.getUint32(p + 16, true);
457
+ const week = view.getUint32(p + 24, true);
458
+ const toes = view.getFloat64(p + 32, true);
459
+ const toeMs = gpsMs(week, toes);
460
+ const tocs = view.getFloat64(p + 164, true);
461
+ const tocDate = new Date(nearWeekMs(toeMs, tocs));
462
+ return {
463
+ system: "J",
464
+ prn: `J${String(prn - 192).padStart(2, "0")}`,
465
+ toc: sowOf(tocDate.getTime()),
466
+ tocDate,
467
+ af0: view.getFloat64(p + 180, true),
468
+ af1: view.getFloat64(p + 188, true),
469
+ af2: view.getFloat64(p + 196, true),
470
+ iode: iode1,
471
+ crs: view.getFloat64(p + 104, true),
472
+ deltaN: view.getFloat64(p + 48, true),
473
+ m0: view.getFloat64(p + 56, true),
474
+ cuc: view.getFloat64(p + 80, true),
475
+ e: view.getFloat64(p + 64, true),
476
+ cus: view.getFloat64(p + 88, true),
477
+ sqrtA: Math.sqrt(view.getFloat64(p + 40, true)),
478
+ toe: toes,
479
+ cic: view.getFloat64(p + 112, true),
480
+ omega0: view.getFloat64(p + 144, true),
481
+ cis: view.getFloat64(p + 120, true),
482
+ i0: view.getFloat64(p + 128, true),
483
+ crc: view.getFloat64(p + 96, true),
484
+ omega: view.getFloat64(p + 72, true),
485
+ omegaDot: view.getFloat64(p + 152, true),
486
+ idot: view.getFloat64(p + 136, true),
487
+ week,
488
+ svHealth: health,
489
+ tgd: view.getFloat64(p + 172, true)
490
+ };
491
+ }
343
492
  function parseNovatelNav(data) {
344
493
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
345
494
  const stats = { badCrc: 0 };
346
495
  const ephemerides = [];
347
- const lastGps = /* @__PURE__ */ new Map();
496
+ const ionoCorrections = {};
497
+ let leapSeconds = null;
498
+ const lastKep = /* @__PURE__ */ new Map();
348
499
  const lastGlo = /* @__PURE__ */ new Map();
500
+ const pushKepler = (key, eph) => {
501
+ const prev = lastKep.get(key);
502
+ if (prev && prev.iode === eph.iode && prev.toe === eph.toe && prev.tocDate.getTime() === eph.tocDate.getTime()) {
503
+ return;
504
+ }
505
+ lastKep.set(key, eph);
506
+ ephemerides.push(eph);
507
+ };
349
508
  for (const frame of oem4Frames(data, view, stats)) {
350
509
  if (!frame.binary) continue;
351
510
  if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
352
511
  const eph = decodeRawEphem(data, view, frame.payload);
353
- if (!eph) continue;
354
- const prev = lastGps.get(eph.prn);
355
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
356
- continue;
512
+ if (eph) pushKepler(eph.prn, eph);
513
+ } else if (frame.id === ID_GALEPHEMERIS && frame.msgLen >= 220) {
514
+ if (frame.week === 0) continue;
515
+ const headerMs = gpsMs(frame.week, frame.towMs / 1e3);
516
+ const dec = decodeGalEphemeris(view, frame.payload, headerMs);
517
+ if (dec)
518
+ pushKepler(`${dec.eph.prn}:${dec.fnav ? "fnav" : "inav"}`, dec.eph);
519
+ } else if (frame.id === ID_BDSEPHEMERIS && frame.msgLen >= 196) {
520
+ const eph = decodeBdsEphemeris(view, frame.payload);
521
+ if (eph) pushKepler(eph.prn, eph);
522
+ } else if (frame.id === ID_QZSSEPHEMERIS && frame.msgLen >= 204) {
523
+ const eph = decodeQzssEphemeris(view, frame.payload);
524
+ if (eph) pushKepler(eph.prn, eph);
525
+ } else if (frame.id === ID_IONUTC && frame.msgLen >= 108) {
526
+ const p = frame.payload;
527
+ const alpha = [];
528
+ const beta = [];
529
+ for (let i = 0; i < 4; i++) {
530
+ alpha.push(view.getFloat64(p + i * 8, true));
531
+ beta.push(view.getFloat64(p + 32 + i * 8, true));
357
532
  }
358
- lastGps.set(eph.prn, eph);
359
- ephemerides.push(eph);
533
+ ionoCorrections["GPSA"] = alpha;
534
+ ionoCorrections["GPSB"] = beta;
535
+ leapSeconds = view.getInt32(p + 96, true);
360
536
  } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
361
537
  const eph = decodeGloEphemeris(view, frame.payload);
362
538
  if (!eph) continue;
@@ -368,12 +544,12 @@ function parseNovatelNav(data) {
368
544
  ephemerides.push(eph);
369
545
  }
370
546
  }
371
- return { ephemerides, badCrc: stats.badCrc };
547
+ return { ephemerides, ionoCorrections, leapSeconds, badCrc: stats.badCrc };
372
548
  }
373
549
 
374
550
  // src/novatel/index.ts
375
551
  var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
376
- var MS_PER_WEEK = 7 * 864e5;
552
+ var MS_PER_WEEK2 = 7 * 864e5;
377
553
  var HDR = 28;
378
554
  var ID_RANGE = 43;
379
555
  var ID_RANGECMP = 140;
@@ -542,7 +718,7 @@ function parseNovatelRange(data) {
542
718
  if (!codes.includes(code)) codes.push(code);
543
719
  }
544
720
  epochs.push({
545
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK + towMs,
721
+ timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK2 + towMs,
546
722
  meas
547
723
  });
548
724
  }
@@ -594,7 +770,7 @@ function parseNovatelRange(data) {
594
770
  if (!codes.includes(code)) codes.push(code);
595
771
  }
596
772
  epochs.push({
597
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK + towMs,
773
+ timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK2 + towMs,
598
774
  meas
599
775
  });
600
776
  }
@@ -37,31 +37,54 @@ declare function oem4Frames(data: Uint8Array, view: DataView, stats: {
37
37
 
38
38
  /**
39
39
  * NovAtel navigation-message decoding: RAWEPHEM (message 41, raw GPS
40
- * LNAV subframes 1–3) and GLOEPHEMERIS (message 723, decoded GLONASS
41
- * L1 C/A ephemeris).
40
+ * LNAV subframes 1–3), GLOEPHEMERIS (message 723, decoded GLONASS
41
+ * L1 C/A ephemeris), GALEPHEMERIS (1122), BDSEPHEMERIS (1696),
42
+ * QZSSEPHEMERIS (1336) and IONUTC (8, GPS Klobuchar + UTC parameters).
42
43
  *
43
44
  * Ported from RTKLIB demo5 (rtklibexplorer fork, src/rcv/novatel.c:
44
- * decode_rawephemb / decode_gloephemerisb, Copyright (c) 2007-2020
45
+ * decode_rawephemb / decode_gloephemerisb / decode_galephemerisb /
46
+ * decode_bdsephemerisb / decode_ionutcb, Copyright (c) 2007-2020
45
47
  * T. Takasu, BSD-2-Clause) and cross-checked against the OEM7
46
- * Commands and Logs Reference Manual. Output records mirror what
47
- * `parseNavFile` produces for the equivalent RINEX 3 navigation file:
48
- * GPS `tocDate` is a GPS-scale Date, GLONASS `tocDate` is the UTC
49
- * epoch, GLONASS state vectors are in km (PZ-90), the RINEX clock bias
50
- * is −τ_n, and `messageFrameTime` is the frame time as seconds of the
51
- * UTC week.
48
+ * Commands and Logs Reference Manual. QZSSEPHEMERIS has no RTKLIB
49
+ * decoder (RTKLIB only handles the raw-subframe QZSSRAWEPHEM 1331);
50
+ * its layout is taken from the OEM7 manual §3.150 and is therefore
51
+ * synthetic-tested only, like SBF's QZSNav.
52
+ *
53
+ * Output records mirror what `parseNavFile` produces for the
54
+ * equivalent RINEX 3 navigation file: Keplerian `tocDate` is a
55
+ * GPS-scale Date except BeiDou, whose epochs and week stay on the BDT
56
+ * scale like a RINEX file; angles arrive from the receiver already in
57
+ * radians (NovAtel decodes the semicircle SIS fields itself), so no
58
+ * GPS_PI scaling is applied here; the Galileo clock set follows the
59
+ * received data source (F/NAV when the rcv_fnav flag is set, I/NAV
60
+ * otherwise — RTKLIB's convention, which convbin tags with the RINEX
61
+ * I/NAV / F/NAV data-source flags) and `tgd` is BGD E5a/E1, the first
62
+ * RINEX BGD slot; GLONASS `tocDate` is the UTC epoch, state vectors
63
+ * are in km (PZ-90), the RINEX clock bias is −τ_n, and
64
+ * `messageFrameTime` is the frame time as seconds of the UTC week.
52
65
  */
53
66
 
54
67
  interface NovatelNavResult {
55
68
  /** Broadcast ephemerides in stream order, duplicates suppressed. */
56
69
  ephemerides: Ephemeris[];
70
+ /**
71
+ * Klobuchar coefficients from IONUTC in the RINEX-header shape
72
+ * (`GPSA` = alpha0..3, `GPSB` = beta0..3), last message wins.
73
+ * Empty when no IONUTC message was seen.
74
+ */
75
+ ionoCorrections: Record<string, number[]>;
76
+ /** GPS−UTC leap seconds (Δt_LS) from IONUTC, null when unseen. */
77
+ leapSeconds: number | null;
57
78
  /** Frames whose CRC failed (corruption indicator). */
58
79
  badCrc: number;
59
80
  }
60
81
  /**
61
- * Decode every navigation message (RAWEPHEM, GLOEPHEMERIS) in a
62
- * NovAtel binary byte stream. Repeated broadcasts of an unchanged
63
- * ephemeris are suppressed the same way RTKLIB does: GPS by issue of
64
- * data, GLONASS by reference time and health.
82
+ * Decode every navigation message (RAWEPHEM, GLOEPHEMERIS,
83
+ * GALEPHEMERIS, BDSEPHEMERIS, QZSSEPHEMERIS, IONUTC) in a NovAtel
84
+ * binary byte stream. Repeated broadcasts of an unchanged ephemeris
85
+ * are suppressed the same way RTKLIB does: GPS/QZSS by issue of data,
86
+ * Galileo by IODNav + toe + toc per data source, BeiDou by toe + toc,
87
+ * GLONASS by reference time and health.
65
88
  */
66
89
  declare function parseNovatelNav(data: Uint8Array): NovatelNavResult;
67
90
 
package/dist/novatel.d.ts CHANGED
@@ -37,31 +37,54 @@ declare function oem4Frames(data: Uint8Array, view: DataView, stats: {
37
37
 
38
38
  /**
39
39
  * NovAtel navigation-message decoding: RAWEPHEM (message 41, raw GPS
40
- * LNAV subframes 1–3) and GLOEPHEMERIS (message 723, decoded GLONASS
41
- * L1 C/A ephemeris).
40
+ * LNAV subframes 1–3), GLOEPHEMERIS (message 723, decoded GLONASS
41
+ * L1 C/A ephemeris), GALEPHEMERIS (1122), BDSEPHEMERIS (1696),
42
+ * QZSSEPHEMERIS (1336) and IONUTC (8, GPS Klobuchar + UTC parameters).
42
43
  *
43
44
  * Ported from RTKLIB demo5 (rtklibexplorer fork, src/rcv/novatel.c:
44
- * decode_rawephemb / decode_gloephemerisb, Copyright (c) 2007-2020
45
+ * decode_rawephemb / decode_gloephemerisb / decode_galephemerisb /
46
+ * decode_bdsephemerisb / decode_ionutcb, Copyright (c) 2007-2020
45
47
  * T. Takasu, BSD-2-Clause) and cross-checked against the OEM7
46
- * Commands and Logs Reference Manual. Output records mirror what
47
- * `parseNavFile` produces for the equivalent RINEX 3 navigation file:
48
- * GPS `tocDate` is a GPS-scale Date, GLONASS `tocDate` is the UTC
49
- * epoch, GLONASS state vectors are in km (PZ-90), the RINEX clock bias
50
- * is −τ_n, and `messageFrameTime` is the frame time as seconds of the
51
- * UTC week.
48
+ * Commands and Logs Reference Manual. QZSSEPHEMERIS has no RTKLIB
49
+ * decoder (RTKLIB only handles the raw-subframe QZSSRAWEPHEM 1331);
50
+ * its layout is taken from the OEM7 manual §3.150 and is therefore
51
+ * synthetic-tested only, like SBF's QZSNav.
52
+ *
53
+ * Output records mirror what `parseNavFile` produces for the
54
+ * equivalent RINEX 3 navigation file: Keplerian `tocDate` is a
55
+ * GPS-scale Date except BeiDou, whose epochs and week stay on the BDT
56
+ * scale like a RINEX file; angles arrive from the receiver already in
57
+ * radians (NovAtel decodes the semicircle SIS fields itself), so no
58
+ * GPS_PI scaling is applied here; the Galileo clock set follows the
59
+ * received data source (F/NAV when the rcv_fnav flag is set, I/NAV
60
+ * otherwise — RTKLIB's convention, which convbin tags with the RINEX
61
+ * I/NAV / F/NAV data-source flags) and `tgd` is BGD E5a/E1, the first
62
+ * RINEX BGD slot; GLONASS `tocDate` is the UTC epoch, state vectors
63
+ * are in km (PZ-90), the RINEX clock bias is −τ_n, and
64
+ * `messageFrameTime` is the frame time as seconds of the UTC week.
52
65
  */
53
66
 
54
67
  interface NovatelNavResult {
55
68
  /** Broadcast ephemerides in stream order, duplicates suppressed. */
56
69
  ephemerides: Ephemeris[];
70
+ /**
71
+ * Klobuchar coefficients from IONUTC in the RINEX-header shape
72
+ * (`GPSA` = alpha0..3, `GPSB` = beta0..3), last message wins.
73
+ * Empty when no IONUTC message was seen.
74
+ */
75
+ ionoCorrections: Record<string, number[]>;
76
+ /** GPS−UTC leap seconds (Δt_LS) from IONUTC, null when unseen. */
77
+ leapSeconds: number | null;
57
78
  /** Frames whose CRC failed (corruption indicator). */
58
79
  badCrc: number;
59
80
  }
60
81
  /**
61
- * Decode every navigation message (RAWEPHEM, GLOEPHEMERIS) in a
62
- * NovAtel binary byte stream. Repeated broadcasts of an unchanged
63
- * ephemeris are suppressed the same way RTKLIB does: GPS by issue of
64
- * data, GLONASS by reference time and health.
82
+ * Decode every navigation message (RAWEPHEM, GLOEPHEMERIS,
83
+ * GALEPHEMERIS, BDSEPHEMERIS, QZSSEPHEMERIS, IONUTC) in a NovAtel
84
+ * binary byte stream. Repeated broadcasts of an unchanged ephemeris
85
+ * are suppressed the same way RTKLIB does: GPS/QZSS by issue of data,
86
+ * Galileo by IODNav + toe + toc per data source, BeiDou by toe + toc,
87
+ * GLONASS by reference time and health.
65
88
  */
66
89
  declare function parseNovatelNav(data: Uint8Array): NovatelNavResult;
67
90
 
package/dist/novatel.js CHANGED
@@ -4,7 +4,8 @@ import {
4
4
  oem4Frames,
5
5
  parseNovatelNav,
6
6
  parseNovatelRange
7
- } from "./chunk-I4UV45NT.js";
7
+ } from "./chunk-7QR2KPDU.js";
8
+ import "./chunk-REYOYF7O.js";
8
9
  import "./chunk-HVXYFUCB.js";
9
10
  import "./chunk-LEEU5OIO.js";
10
11
  export {
package/dist/sbf.cjs CHANGED
@@ -21,8 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var sbf_exports = {};
22
22
  __export(sbf_exports, {
23
23
  parseSbfAlmanac: () => parseSbfAlmanac,
24
+ parseSbfIonoUtc: () => parseSbfIonoUtc,
24
25
  parseSbfMeas: () => parseSbfMeas,
25
- parseSbfNav: () => parseSbfNav
26
+ parseSbfNav: () => parseSbfNav,
27
+ sbfCrc16: () => crc16,
28
+ scanSbfFrames: () => scanSbfFrames
26
29
  });
27
30
  module.exports = __toCommonJS(sbf_exports);
28
31
 
@@ -482,6 +485,39 @@ function parseSbfAlmanac(data) {
482
485
  return { almanacs, badCrc };
483
486
  }
484
487
 
488
+ // src/sbf/iono.ts
489
+ var F4_DNU2 = -2e10;
490
+ function parseSbfIonoUtc(data) {
491
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
492
+ const ionoCorrections = {};
493
+ let leapSeconds = null;
494
+ const f4s = (b, n) => {
495
+ const out = [];
496
+ for (let k = 0; k < n; k++) {
497
+ const v = view.getFloat32(b + 4 * k, true);
498
+ if (v === F4_DNU2) return null;
499
+ out.push(v);
500
+ }
501
+ return out;
502
+ };
503
+ scanSbfFrames(data, view, (id, b, len) => {
504
+ if ((id === 5893 || id === 4120) && len >= 48) {
505
+ const alpha = f4s(b + 16, 4);
506
+ const beta = f4s(b + 32, 4);
507
+ if (!alpha || !beta) return;
508
+ const sys = id === 5893 ? "GPS" : "BDS";
509
+ ionoCorrections[`${sys}A`] = alpha;
510
+ ionoCorrections[`${sys}B`] = beta;
511
+ } else if (id === 4030 && len >= 28) {
512
+ const ai = f4s(b + 16, 3);
513
+ if (ai) ionoCorrections["GAL"] = ai;
514
+ } else if (id === 5894 && len >= 37) {
515
+ leapSeconds = view.getInt8(b + 33);
516
+ }
517
+ });
518
+ return { ionoCorrections, leapSeconds };
519
+ }
520
+
485
521
  // src/sbf/index.ts
486
522
  var CLIGHT = 299792458;
487
523
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
@@ -1152,6 +1188,9 @@ function parseSbfMeas(data) {
1152
1188
  // Annotate the CommonJS export names for ESM import in node:
1153
1189
  0 && (module.exports = {
1154
1190
  parseSbfAlmanac,
1191
+ parseSbfIonoUtc,
1155
1192
  parseSbfMeas,
1156
- parseSbfNav
1193
+ parseSbfNav,
1194
+ sbfCrc16,
1195
+ scanSbfFrames
1157
1196
  });
package/dist/sbf.d.cts CHANGED
@@ -1,5 +1,22 @@
1
1
  import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
2
2
 
3
+ /**
4
+ * Septentrio SBF framing shared by the measurement and navigation
5
+ * decoders: 0x24 0x40 sync, CRC16-CCITT (poly 0x1021, init 0) as U2,
6
+ * block ID U2 (number in bits 0..12, revision in 13..15), total length
7
+ * U2 (multiple of 4, includes the 8-byte header). The CRC covers block
8
+ * ID through payload end.
9
+ */
10
+ declare function crc16(data: Uint8Array, start: number, end: number): number;
11
+ /**
12
+ * Scan a byte stream for valid SBF frames and invoke `onBlock` for each
13
+ * one, passing the block number (revision bits stripped), the frame
14
+ * start offset and the total frame length. Frames with bad CRC are
15
+ * counted and a resync continues at the next byte; the count of bad
16
+ * frames is returned.
17
+ */
18
+ declare function scanSbfFrames(data: Uint8Array, view: DataView, onBlock: (id: number, offset: number, len: number) => void): number;
19
+
3
20
  /**
4
21
  * Septentrio SBF decoded navigation and almanac blocks.
5
22
  *
@@ -134,6 +151,46 @@ interface SbfAlmanacResult {
134
151
  */
135
152
  declare function parseSbfAlmanac(data: Uint8Array): SbfAlmanacResult;
136
153
 
154
+ /**
155
+ * Septentrio SBF decoded ionosphere / UTC blocks.
156
+ *
157
+ * GPSIon (5893) and BDSIon (4120) carry the eight Klobuchar vertical
158
+ * delay coefficients, GALIon (4030) the three NeQuick-G effective
159
+ * ionisation level coefficients, and GPSUtc (5894) the GPS-UTC
160
+ * parameters from LNAV subframe 4 page 18 (only ΔtLS, the current
161
+ * leap-second count, is extracted here).
162
+ *
163
+ * Units: SBF stores the coefficients as f4 floats already in the
164
+ * semicircle-based SI units of the broadcast messages — alpha_n in
165
+ * s/semicircle^n, beta_n in s/semicircle^n, ai_n in sfu/deg^n — which
166
+ * are exactly the units a RINEX nav header prints. Values are passed
167
+ * through unscaled, so the output matches what `parseNavFile` reads
168
+ * from a converted nav header to float32 precision.
169
+ *
170
+ * Ported from RTKLIB demo5 (rtklibexplorer), src/rcv/septentrio.c
171
+ * (decode_gpsion / decode_galion / decode_cmpion / decode_gpsutc),
172
+ * BSD-2-Clause, and cross-checked against the Septentrio mosaic-X5
173
+ * reference guide (which documents all four block layouts, including
174
+ * the do-not-use markers RTKLIB does not check).
175
+ */
176
+ interface SbfIonoUtcResult {
177
+ /**
178
+ * Iono coefficient sets keyed like `NavHeader.ionoCorrections`:
179
+ * `GPSA`/`GPSB` (Klobuchar alpha/beta), `GAL` ([ai0, ai1, ai2],
180
+ * RINEX GAL header convention), `BDSA`/`BDSB`. The blocks repeat;
181
+ * the last valid block of each type in the stream wins.
182
+ */
183
+ ionoCorrections: Record<string, number[]>;
184
+ /** GPS-UTC ΔtLS from the last GPSUtc block, if any. */
185
+ leapSeconds: number | null;
186
+ }
187
+ /**
188
+ * Decode every GPSIon/GALIon/BDSIon/GPSUtc block in an SBF byte
189
+ * stream. Blocks with do-not-use coefficients are skipped; other block
190
+ * types are skipped silently.
191
+ */
192
+ declare function parseSbfIonoUtc(data: Uint8Array): SbfIonoUtcResult;
193
+
137
194
  /**
138
195
  * Septentrio SBF raw-measurement decoding (MeasEpoch + Meas3) — the
139
196
  * path from a receiver log to RINEX-grade observables.
@@ -197,4 +254,4 @@ interface SbfParseResult {
197
254
  */
198
255
  declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
199
256
 
200
- export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav };
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 };