gnss-js 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -113,6 +113,7 @@ __export(src_exports, {
113
113
  WGS84_SEMI_MAJOR_AXIS: () => WGS84_SEMI_MAJOR_AXIS,
114
114
  WGS84_SEMI_MINOR_AXIS: () => WGS84_SEMI_MINOR_AXIS,
115
115
  WarningAccumulator: () => WarningAccumulator,
116
+ ZeroBaselineEngine: () => ZeroBaselineEngine,
116
117
  almanacEpochMs: () => almanacEpochMs,
117
118
  almanacSatPosition: () => almanacSatPosition,
118
119
  analyzeQuality: () => analyzeQuality,
@@ -169,9 +170,11 @@ __export(src_exports, {
169
170
  estimateNetworkFcbs: () => estimateNetworkFcbs,
170
171
  estimateWidelaneFcb: () => estimateWidelaneFcb,
171
172
  euclidean3D: () => euclidean3D,
173
+ extractSbfNavicSubframe: () => extractSbfNavicSubframe,
172
174
  extractWidelaneArcs: () => extractWidelaneArcs,
173
175
  feedGeoBlock: () => feedGeoBlock,
174
176
  feedGeoL5Block: () => feedGeoL5Block,
177
+ feedNavicBlock: () => feedNavicBlock,
175
178
  fetchSourcetable: () => fetchSourcetable,
176
179
  findSatBias: () => findSatBias,
177
180
  fixPppPosition: () => fixPppPosition,
@@ -291,6 +294,7 @@ __export(src_exports, {
291
294
  parseSbfIonoUtc: () => parseSbfIonoUtc,
292
295
  parseSbfMeas: () => parseSbfMeas,
293
296
  parseSbfNav: () => parseSbfNav,
297
+ parseSbfNavic: () => parseSbfNavic,
294
298
  parseSbfPvt: () => parseSbfPvt,
295
299
  parseSbfReceiverSetup: () => parseSbfReceiverSetup,
296
300
  parseSinexBiasDcb: () => parseSinexBiasDcb,
@@ -5270,6 +5274,70 @@ function decodeQzssEphemeris(payload) {
5270
5274
  fitInterval
5271
5275
  };
5272
5276
  }
5277
+ function decodeNavicEphemeris(payload) {
5278
+ if (payload.length < 60) return null;
5279
+ const r = new BitReader(payload);
5280
+ r.skip(12);
5281
+ const svId = r.readU(6);
5282
+ const week = r.readU(10);
5283
+ const af0 = r.readS(22) * 2 ** -31;
5284
+ const af1 = r.readS(16) * 2 ** -43;
5285
+ const af2 = r.readS(8) * 2 ** -55;
5286
+ const ura = r.readU(4);
5287
+ const toc = r.readU(16) * 16;
5288
+ const tgd = r.readS(8) * 2 ** -31;
5289
+ const deltaN = r.readS(22) * 2 ** -41 * PI;
5290
+ const iode = r.readU(8);
5291
+ r.skip(10);
5292
+ const l5Health = r.readU(1);
5293
+ const sHealth = r.readU(1);
5294
+ const cuc = r.readS(15) * 2 ** -28;
5295
+ const cus = r.readS(15) * 2 ** -28;
5296
+ const cic = r.readS(15) * 2 ** -28;
5297
+ const cis = r.readS(15) * 2 ** -28;
5298
+ const crc = r.readS(15) * 2 ** -4;
5299
+ const crs = r.readS(15) * 2 ** -4;
5300
+ const idot = r.readS(14) * 2 ** -43 * PI;
5301
+ const m0 = r.readS(32) * 2 ** -31 * PI;
5302
+ const toe = r.readU(16) * 16;
5303
+ const e = r.readU(32) * 2 ** -33;
5304
+ const sqrtA = r.readU(32) * 2 ** -19;
5305
+ const omega0 = r.readS(32) * 2 ** -31 * PI;
5306
+ const argPerigee = r.readS(32) * 2 ** -31 * PI;
5307
+ const omegaDot = r.readS(22) * 2 ** -41 * PI;
5308
+ const i0 = r.readS(32) * 2 ** -31 * PI;
5309
+ return {
5310
+ prn: `I${String(svId).padStart(2, "0")}`,
5311
+ constellation: "NavIC",
5312
+ health: l5Health << 1 | sHealth,
5313
+ lastReceived: Date.now(),
5314
+ messageType: 1041,
5315
+ week,
5316
+ ura,
5317
+ toc,
5318
+ toe,
5319
+ sqrtA,
5320
+ eccentricity: e,
5321
+ inclination: i0,
5322
+ omega0,
5323
+ omegaDot,
5324
+ argPerigee,
5325
+ meanAnomaly: m0,
5326
+ deltaN,
5327
+ idot,
5328
+ crs,
5329
+ crc,
5330
+ cuc,
5331
+ cus,
5332
+ cic,
5333
+ cis,
5334
+ af0,
5335
+ af1,
5336
+ af2,
5337
+ iode,
5338
+ tgd
5339
+ };
5340
+ }
5273
5341
  function decodeEphemeris(frame) {
5274
5342
  try {
5275
5343
  switch (frame.messageType) {
@@ -5277,6 +5345,8 @@ function decodeEphemeris(frame) {
5277
5345
  return decodeGpsEphemeris(frame.payload);
5278
5346
  case 1020:
5279
5347
  return decodeGlonassEphemeris(frame.payload);
5348
+ case 1041:
5349
+ return decodeNavicEphemeris(frame.payload);
5280
5350
  case 1042:
5281
5351
  return decodeBdsEphemeris(frame.payload);
5282
5352
  case 1043:
@@ -8379,6 +8449,173 @@ function parseSbfGpsNav(data) {
8379
8449
  return { ephemerides, badParity, messages };
8380
8450
  }
8381
8451
 
8452
+ // src/navbits/navic.ts
8453
+ var GPS_EPOCH_MS13 = Date.UTC(1980, 0, 6);
8454
+ var SEC_PER_WEEK9 = 604800;
8455
+ var DATA = 30;
8456
+ function navicSubframeCrcOk(sf) {
8457
+ return crc24q(sf, 262) === getBitU(sf, 262, 24);
8458
+ }
8459
+ function navicSubframeId(sf) {
8460
+ return getBitU(sf, 27, 2) + 1;
8461
+ }
8462
+ function decodeNavicSubframes(sf1, sf2, opts = {}) {
8463
+ let i = DATA;
8464
+ getBitU(sf1, i, 10);
8465
+ i += 10;
8466
+ const af0 = getBitS(sf1, i, 22) * 2 ** -31;
8467
+ i += 22;
8468
+ const af1 = getBitS(sf1, i, 16) * 2 ** -43;
8469
+ i += 16;
8470
+ const af2 = getBitS(sf1, i, 8) * 2 ** -55;
8471
+ i += 8;
8472
+ i += 4;
8473
+ const tocSec = getBitU(sf1, i, 16) * 16;
8474
+ i += 16;
8475
+ const tgd = getBitS(sf1, i, 8) * 2 ** -31;
8476
+ i += 8;
8477
+ const deltaN = getBitS(sf1, i, 22) * 2 ** -41 * GPS_PI;
8478
+ i += 22;
8479
+ const iode = getBitU(sf1, i, 8);
8480
+ i += 8;
8481
+ i += 10;
8482
+ const l5Health = getBitU(sf1, i, 1);
8483
+ i += 1;
8484
+ const sHealth = getBitU(sf1, i, 1);
8485
+ i += 1;
8486
+ const cuc = getBitS(sf1, i, 15) * 2 ** -28;
8487
+ i += 15;
8488
+ const cus = getBitS(sf1, i, 15) * 2 ** -28;
8489
+ i += 15;
8490
+ const cic = getBitS(sf1, i, 15) * 2 ** -28;
8491
+ i += 15;
8492
+ const cis = getBitS(sf1, i, 15) * 2 ** -28;
8493
+ i += 15;
8494
+ const crc = getBitS(sf1, i, 15) * 2 ** -4;
8495
+ i += 15;
8496
+ const crs = getBitS(sf1, i, 15) * 2 ** -4;
8497
+ i += 15;
8498
+ const idot = getBitS(sf1, i, 14) * 2 ** -43 * GPS_PI;
8499
+ let j = DATA;
8500
+ const m0 = getBitS(sf2, j, 32) * 2 ** -31 * GPS_PI;
8501
+ j += 32;
8502
+ const toeSec = getBitU(sf2, j, 16) * 16;
8503
+ j += 16;
8504
+ const e = getBitU(sf2, j, 32) * 2 ** -33;
8505
+ j += 32;
8506
+ const sqrtA = getBitU(sf2, j, 32) * 2 ** -19;
8507
+ j += 32;
8508
+ const omega0 = getBitS(sf2, j, 32) * 2 ** -31 * GPS_PI;
8509
+ j += 32;
8510
+ const omega = getBitS(sf2, j, 32) * 2 ** -31 * GPS_PI;
8511
+ j += 32;
8512
+ const omegaDot = getBitS(sf2, j, 22) * 2 ** -41 * GPS_PI;
8513
+ j += 22;
8514
+ const i0 = getBitS(sf2, j, 32) * 2 ** -31 * GPS_PI;
8515
+ if (sqrtA <= 0 || e >= 1) return null;
8516
+ const week = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS13) / 1e3 / SEC_PER_WEEK9);
8517
+ const prn = opts.prn ?? "I00";
8518
+ const tocDate = new Date(
8519
+ GPS_EPOCH_MS13 + (week * SEC_PER_WEEK9 + tocSec) * 1e3
8520
+ );
8521
+ return {
8522
+ system: "I",
8523
+ prn,
8524
+ tocDate,
8525
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK9,
8526
+ af0,
8527
+ af1,
8528
+ af2,
8529
+ iode,
8530
+ crs,
8531
+ deltaN,
8532
+ m0,
8533
+ cuc,
8534
+ e,
8535
+ cus,
8536
+ sqrtA,
8537
+ toe: toeSec,
8538
+ cic,
8539
+ omega0,
8540
+ cis,
8541
+ i0,
8542
+ crc,
8543
+ omega,
8544
+ omegaDot,
8545
+ idot,
8546
+ week,
8547
+ svHealth: l5Health << 1 | sHealth,
8548
+ tgd
8549
+ };
8550
+ }
8551
+ var NavicAssembler = class {
8552
+ sf1 = /* @__PURE__ */ new Map();
8553
+ last = /* @__PURE__ */ new Map();
8554
+ /**
8555
+ * Push one CRC-valid subframe. Subframe 1 opens a master frame (subframe 2
8556
+ * immediately follows it in the NavIC frame order), so an ephemeris is only
8557
+ * emitted when a subframe 2 pairs with the subframe 1 that opened the *same*
8558
+ * frame — never a stale subframe 1 left over across CRC drops.
8559
+ */
8560
+ push(prn, subframe, refWeek) {
8561
+ if (getBitU(subframe, 25, 1)) return null;
8562
+ const id = navicSubframeId(subframe);
8563
+ if (id === 1) {
8564
+ this.sf1.set(prn, subframe);
8565
+ return null;
8566
+ }
8567
+ if (id !== 2) return null;
8568
+ const a = this.sf1.get(prn);
8569
+ if (!a) return null;
8570
+ this.sf1.delete(prn);
8571
+ const eph = decodeNavicSubframes(a, subframe, { prn, refWeek });
8572
+ if (!eph) return null;
8573
+ const key = `${eph.toc}|${eph.iode}`;
8574
+ if (this.last.get(prn) === key) return null;
8575
+ this.last.set(prn, key);
8576
+ return eph;
8577
+ }
8578
+ };
8579
+
8580
+ // src/sbf/rawnav-navic.ts
8581
+ var WNC_DNU2 = 65535;
8582
+ function extractSbfNavicSubframe(view, b) {
8583
+ const sf = new Uint8Array(40);
8584
+ for (let k = 0; k < 10; k++) {
8585
+ const w = view.getUint32(b + 20 + 4 * k, true);
8586
+ sf[4 * k] = w >>> 24;
8587
+ sf[4 * k + 1] = w >>> 16 & 255;
8588
+ sf[4 * k + 2] = w >>> 8 & 255;
8589
+ sf[4 * k + 3] = w & 255;
8590
+ }
8591
+ return sf;
8592
+ }
8593
+ function feedNavicBlock(view, b, assembler) {
8594
+ const prn = svidToPrn(view.getUint8(b + 14));
8595
+ if (!prn || prn[0] !== "I") return {};
8596
+ const wnc = view.getUint16(b + 12, true);
8597
+ if (wnc === WNC_DNU2) return {};
8598
+ const sf = extractSbfNavicSubframe(view, b);
8599
+ if (!navicSubframeCrcOk(sf)) return { badCrc: true };
8600
+ const eph = assembler.push(prn, sf, wnc);
8601
+ return eph ? { eph } : {};
8602
+ }
8603
+ function parseSbfNavic(data) {
8604
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
8605
+ const ephemerides = [];
8606
+ const assembler = new NavicAssembler();
8607
+ let badCrc = 0;
8608
+ let messages = 0;
8609
+ scanSbfFrames(data, view, (id, b, len) => {
8610
+ if (id !== 4093 || len < 60) return;
8611
+ messages++;
8612
+ const r = feedNavicBlock(view, b, assembler);
8613
+ if (r.eph) ephemerides.push(r.eph);
8614
+ else if (r.badCrc) badCrc++;
8615
+ });
8616
+ return { ephemerides, badCrc, messages };
8617
+ }
8618
+
8382
8619
  // src/sbf/rawnav.ts
8383
8620
  var CNAV_SIGNAL_OF_BLOCK = {
8384
8621
  4018: "L2C",
@@ -8477,10 +8714,10 @@ function isFnavDummy(page) {
8477
8714
  }
8478
8715
 
8479
8716
  // src/sbf/rawnav-bds.ts
8480
- var GPS_EPOCH_MS13 = Date.UTC(1980, 0, 6);
8717
+ var GPS_EPOCH_MS14 = Date.UTC(1980, 0, 6);
8481
8718
  var MS_PER_WEEK6 = 7 * 86400 * 1e3;
8482
8719
  var TOW_DNU = 4294967295;
8483
- var WNC_DNU2 = 65535;
8720
+ var WNC_DNU3 = 65535;
8484
8721
  function feedBdsBlock(view, b, assembler) {
8485
8722
  const prn = svidToPrn(view.getUint8(b + 14));
8486
8723
  if (!prn || prn[0] !== "C") return {};
@@ -8520,7 +8757,7 @@ function feedGloBlock(view, b, assembler) {
8520
8757
  if (!prn || prn[0] !== "R") return {};
8521
8758
  const towMs = view.getUint32(b + 8, true);
8522
8759
  const wnc = view.getUint16(b + 12, true);
8523
- if (towMs === TOW_DNU || wnc === WNC_DNU2) return {};
8760
+ if (towMs === TOW_DNU || wnc === WNC_DNU3) return {};
8524
8761
  const str2 = new Uint8Array(11);
8525
8762
  for (let k = 0; k < 3; k++) {
8526
8763
  const w = view.getUint32(b + 20 + 4 * k, true);
@@ -8538,7 +8775,7 @@ function feedGloBlock(view, b, assembler) {
8538
8775
  const eph = assembler.push(
8539
8776
  prn,
8540
8777
  str2,
8541
- new Date(GPS_EPOCH_MS13 + wnc * MS_PER_WEEK6 + towMs),
8778
+ new Date(GPS_EPOCH_MS14 + wnc * MS_PER_WEEK6 + towMs),
8542
8779
  view.getUint8(b + 18) - 8
8543
8780
  // FreqNr, offset by 8
8544
8781
  );
@@ -8713,6 +8950,7 @@ function decodeSbfNavigation(data, opts = {}) {
8713
8950
  bdsRaw: 0,
8714
8951
  sbasRaw: 0,
8715
8952
  sbasL5Raw: 0,
8953
+ navicRaw: 0,
8716
8954
  badFrames: 0
8717
8955
  };
8718
8956
  const dfmcByType = {};
@@ -8722,6 +8960,7 @@ function decodeSbfNavigation(data, opts = {}) {
8722
8960
  const gloAsm = new GloStringAssembler();
8723
8961
  const bdsAsm = new BdsAssembler();
8724
8962
  const lnavAsm = new GpsLnavAssembler();
8963
+ const navicAsm = new NavicAssembler();
8725
8964
  const addEph = (e) => {
8726
8965
  if (!e) return;
8727
8966
  const key = ephKey(e);
@@ -8782,6 +9021,14 @@ function decodeSbfNavigation(data, opts = {}) {
8782
9021
  else if (r.badParity) counts.badFrames++;
8783
9022
  return;
8784
9023
  }
9024
+ case 4093: {
9025
+ if (len < 60) return;
9026
+ counts.navicRaw++;
9027
+ const r = feedNavicBlock(view, b, navicAsm);
9028
+ if (r.eph) addEph(r.eph);
9029
+ else if (r.badCrc) counts.badFrames++;
9030
+ return;
9031
+ }
8785
9032
  case 4018:
8786
9033
  // GPSRawL2C
8787
9034
  case 4019: {
@@ -9402,7 +9649,7 @@ function parseSbfHas(data) {
9402
9649
 
9403
9650
  // src/sbf/index.ts
9404
9651
  var CLIGHT = 299792458;
9405
- var GPS_EPOCH_MS14 = Date.UTC(1980, 0, 6);
9652
+ var GPS_EPOCH_MS15 = Date.UTC(1980, 0, 6);
9406
9653
  var MS_PER_WEEK7 = 7 * 864e5;
9407
9654
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
9408
9655
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -10056,7 +10303,7 @@ function parseSbfMeas(data) {
10056
10303
  const tow = view.getUint32(i + 8, true);
10057
10304
  const wnc = view.getUint16(i + 12, true);
10058
10305
  if (tow !== 4294967295 && wnc !== 65535) {
10059
- ensureEpoch(GPS_EPOCH_MS14 + wnc * MS_PER_WEEK7 + tow);
10306
+ ensureEpoch(GPS_EPOCH_MS15 + wnc * MS_PER_WEEK7 + tow);
10060
10307
  if (id === 4109) decodeMeas3Ranges(i, tow);
10061
10308
  else if (id === 4110) decodeMeas3CN0(i, len);
10062
10309
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -10123,15 +10370,15 @@ var ID_GPSEPHEM = 7;
10123
10370
  var ID_BDSEPHEMERIS = 1696;
10124
10371
  var ID_RAWWAASFRAME = 287;
10125
10372
  var ID_RAWSBASFRAME = 973;
10126
- var GPS_EPOCH_MS15 = Date.UTC(1980, 0, 6);
10373
+ var GPS_EPOCH_MS16 = Date.UTC(1980, 0, 6);
10127
10374
  var BDT_EPOCH_MS3 = Date.UTC(2006, 0, 1);
10128
- var SEC_PER_WEEK9 = 7 * 86400;
10375
+ var SEC_PER_WEEK10 = 7 * 86400;
10129
10376
  var SEC_PER_DAY2 = 86400;
10130
- var MS_PER_WEEK8 = SEC_PER_WEEK9 * 1e3;
10131
- var gpsMs2 = (week, sec) => GPS_EPOCH_MS15 + week * MS_PER_WEEK8 + sec * 1e3;
10132
- var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK9;
10377
+ var MS_PER_WEEK8 = SEC_PER_WEEK10 * 1e3;
10378
+ var gpsMs2 = (week, sec) => GPS_EPOCH_MS16 + week * MS_PER_WEEK8 + sec * 1e3;
10379
+ var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK10;
10133
10380
  function nearWeekMs(refMs, sow) {
10134
- const refWeek = Math.floor((refMs - GPS_EPOCH_MS15) / MS_PER_WEEK8);
10381
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS16) / MS_PER_WEEK8);
10135
10382
  let ms = gpsMs2(refWeek, sow);
10136
10383
  if (ms < refMs - MS_PER_WEEK8 / 2) ms += MS_PER_WEEK8;
10137
10384
  else if (ms > refMs + MS_PER_WEEK8 / 2) ms -= MS_PER_WEEK8;
@@ -10158,9 +10405,9 @@ function decodeGloEphemeris(view, p) {
10158
10405
  tof += Math.floor(tow / SEC_PER_DAY2) * SEC_PER_DAY2;
10159
10406
  if (tof < tow - 43200) tof += SEC_PER_DAY2;
10160
10407
  else if (tof > tow + 43200) tof -= SEC_PER_DAY2;
10161
- const toeGpsMs = GPS_EPOCH_MS15 + (week * SEC_PER_WEEK9 + tow) * 1e3;
10162
- const tofGpsMs = GPS_EPOCH_MS15 + (week * SEC_PER_WEEK9 + tof) * 1e3;
10163
- const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS15) / 1e3;
10408
+ const toeGpsMs = GPS_EPOCH_MS16 + (week * SEC_PER_WEEK10 + tow) * 1e3;
10409
+ const tofGpsMs = GPS_EPOCH_MS16 + (week * SEC_PER_WEEK10 + tof) * 1e3;
10410
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS16) / 1e3;
10164
10411
  return {
10165
10412
  system: "R",
10166
10413
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -10168,7 +10415,7 @@ function decodeGloEphemeris(view, p) {
10168
10415
  tauN: -view.getFloat64(p + 100, true),
10169
10416
  // RINEX stores −τ_n
10170
10417
  gammaN: view.getFloat64(p + 116, true),
10171
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK9 + SEC_PER_WEEK9) % SEC_PER_WEEK9,
10418
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK10 + SEC_PER_WEEK10) % SEC_PER_WEEK10,
10172
10419
  x: view.getFloat64(p + 28, true) / 1e3,
10173
10420
  y: view.getFloat64(p + 36, true) / 1e3,
10174
10421
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -10225,7 +10472,7 @@ function decodeGalEphemeris(view, p, headerMs) {
10225
10472
  omega: view.getFloat64(p + 60, true),
10226
10473
  omegaDot: view.getFloat64(p + 140, true),
10227
10474
  idot: view.getFloat64(p + 124, true),
10228
- week: Math.floor((toeMs - GPS_EPOCH_MS15) / MS_PER_WEEK8),
10475
+ week: Math.floor((toeMs - GPS_EPOCH_MS16) / MS_PER_WEEK8),
10229
10476
  // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
10230
10477
  // 3-5, E5b in 6-8) — same packing as RTKLIB.
10231
10478
  svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
@@ -10412,7 +10659,7 @@ function parseNovatelNav(data, opts = {}) {
10412
10659
  }
10413
10660
 
10414
10661
  // src/novatel/pvt.ts
10415
- var GPS_EPOCH_MS16 = Date.UTC(1980, 0, 6);
10662
+ var GPS_EPOCH_MS17 = Date.UTC(1980, 0, 6);
10416
10663
  var BESTPOS = 42;
10417
10664
  function posTypeToMode(t) {
10418
10665
  if (t === 0) return "no-pvt";
@@ -10444,7 +10691,7 @@ function parseNovatelPvt(data) {
10444
10691
  const nrSV = view.getUint8(p + 65);
10445
10692
  const hasFix = posType !== 0 && Number.isFinite(lat) && Number.isFinite(lon);
10446
10693
  records.push({
10447
- timeMs: GPS_EPOCH_MS16 + f.week * 6048e5 + f.towMs,
10694
+ timeMs: GPS_EPOCH_MS17 + f.week * 6048e5 + f.towMs,
10448
10695
  week: f.week,
10449
10696
  tow: f.towMs / 1e3,
10450
10697
  mode: posTypeToMode(posType),
@@ -10461,7 +10708,7 @@ function parseNovatelPvt(data) {
10461
10708
  }
10462
10709
 
10463
10710
  // src/novatel/index.ts
10464
- var GPS_EPOCH_MS17 = Date.UTC(1980, 0, 6);
10711
+ var GPS_EPOCH_MS18 = Date.UTC(1980, 0, 6);
10465
10712
  var MS_PER_WEEK9 = 7 * 864e5;
10466
10713
  var HDR = 28;
10467
10714
  var ID_RANGE = 43;
@@ -10631,7 +10878,7 @@ function parseNovatelRange(data) {
10631
10878
  if (!codes.includes(code)) codes.push(code);
10632
10879
  }
10633
10880
  epochs.push({
10634
- timeMs: GPS_EPOCH_MS17 + week * MS_PER_WEEK9 + towMs,
10881
+ timeMs: GPS_EPOCH_MS18 + week * MS_PER_WEEK9 + towMs,
10635
10882
  meas
10636
10883
  });
10637
10884
  }
@@ -10683,7 +10930,7 @@ function parseNovatelRange(data) {
10683
10930
  if (!codes.includes(code)) codes.push(code);
10684
10931
  }
10685
10932
  epochs.push({
10686
- timeMs: GPS_EPOCH_MS17 + week * MS_PER_WEEK9 + towMs,
10933
+ timeMs: GPS_EPOCH_MS18 + week * MS_PER_WEEK9 + towMs,
10687
10934
  meas
10688
10935
  });
10689
10936
  }
@@ -10734,15 +10981,15 @@ function* trimbleFrames(data, stats) {
10734
10981
  }
10735
10982
 
10736
10983
  // src/trimble/nav.ts
10737
- var GPS_EPOCH_MS18 = Date.UTC(1980, 0, 6);
10984
+ var GPS_EPOCH_MS19 = Date.UTC(1980, 0, 6);
10738
10985
  var BDT_EPOCH_MS4 = Date.UTC(2006, 0, 1);
10739
- var SEC_PER_WEEK10 = 7 * 86400;
10740
- var MS_PER_WEEK10 = SEC_PER_WEEK10 * 1e3;
10986
+ var SEC_PER_WEEK11 = 7 * 86400;
10987
+ var MS_PER_WEEK10 = SEC_PER_WEEK11 * 1e3;
10741
10988
  var GPS_PI2 = 3.1415926535898;
10742
10989
  var BDS_WEEK_OFFSET = 1356;
10743
10990
  var BDS_LEAP_SEC = 14;
10744
- var gpsMs3 = (week, sec) => GPS_EPOCH_MS18 + week * MS_PER_WEEK10 + sec * 1e3;
10745
- var sowOf4 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK10;
10991
+ var gpsMs3 = (week, sec) => GPS_EPOCH_MS19 + week * MS_PER_WEEK10 + sec * 1e3;
10992
+ var sowOf4 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK11;
10746
10993
  var SUB_GPS_EPHEMERIS = 1;
10747
10994
  var SUB_ION_UTC = 3;
10748
10995
  var SUB_GLO_EPHEMERIS = 9;
@@ -10894,7 +11141,7 @@ function decodeGloEphemeris2(view, base) {
10894
11141
  // RINEX stores −τn as the clock bias; the record carries τn (ICD sign).
10895
11142
  tauN: -view.getFloat64(base + 113, false),
10896
11143
  gammaN: view.getFloat64(base + 121, false),
10897
- messageFrameTime: (tocMs - GPS_EPOCH_MS18) / 1e3 % SEC_PER_WEEK10,
11144
+ messageFrameTime: (tocMs - GPS_EPOCH_MS19) / 1e3 % SEC_PER_WEEK11,
10898
11145
  // State vector: metres / m·s⁻¹ / m·s⁻² → km / km·s⁻¹ / km·s⁻².
10899
11146
  x: view.getFloat64(base + 33, false) / 1e3,
10900
11147
  xDot: view.getFloat64(base + 41, false) / 1e3,
@@ -10965,7 +11212,7 @@ function parseTrimbleNav(data) {
10965
11212
  }
10966
11213
 
10967
11214
  // src/trimble/index.ts
10968
- var GPS_EPOCH_MS19 = Date.UTC(1980, 0, 6);
11215
+ var GPS_EPOCH_MS20 = Date.UTC(1980, 0, 6);
10969
11216
  var MS_PER_WEEK11 = 7 * 864e5;
10970
11217
  var REC_TYPE_27 = 6;
10971
11218
  var SYS = {
@@ -11163,7 +11410,7 @@ function decodeEpoch(epoch, epochs, obsCodes) {
11163
11410
  o += c.length;
11164
11411
  }
11165
11412
  const dv = new DataView(M.buffer, M.byteOffset, M.byteLength);
11166
- const timeMs = GPS_EPOCH_MS19 + epoch.week * MS_PER_WEEK11 + epoch.towMs;
11413
+ const timeMs = GPS_EPOCH_MS20 + epoch.week * MS_PER_WEEK11 + epoch.towMs;
11167
11414
  const meas = [];
11168
11415
  let q = 0;
11169
11416
  for (let sv = 0; sv < epoch.nsv && q + 8 <= M.length; sv++) {
@@ -11344,14 +11591,14 @@ function* binexRecords(data, view, stats) {
11344
11591
  }
11345
11592
 
11346
11593
  // src/binex/nav.ts
11347
- var GPS_EPOCH_MS20 = Date.UTC(1980, 0, 6);
11594
+ var GPS_EPOCH_MS21 = Date.UTC(1980, 0, 6);
11348
11595
  var GLO_DAY0_MS = Date.UTC(1980, 0, 1);
11349
11596
  var BDT_EPOCH_MS5 = Date.UTC(2006, 0, 1);
11350
- var SEC_PER_WEEK11 = 7 * 86400;
11351
- var MS_PER_WEEK12 = SEC_PER_WEEK11 * 1e3;
11597
+ var SEC_PER_WEEK12 = 7 * 86400;
11598
+ var MS_PER_WEEK12 = SEC_PER_WEEK12 * 1e3;
11352
11599
  var SC2RAD = GPS_PI;
11353
- var gpsMs4 = (week, sec) => GPS_EPOCH_MS20 + week * MS_PER_WEEK12 + sec * 1e3;
11354
- var sowOf5 = (dateMs) => (dateMs / 1e3 % SEC_PER_WEEK11 + SEC_PER_WEEK11) % SEC_PER_WEEK11;
11600
+ var gpsMs4 = (week, sec) => GPS_EPOCH_MS21 + week * MS_PER_WEEK12 + sec * 1e3;
11601
+ var sowOf5 = (dateMs) => (dateMs / 1e3 % SEC_PER_WEEK12 + SEC_PER_WEEK12) % SEC_PER_WEEK12;
11355
11602
  var two9 = (n) => String(n).padStart(2, "0");
11356
11603
  var Reader = class {
11357
11604
  constructor(view, start, le) {
@@ -11638,7 +11885,7 @@ function decodeBinexEph(view, body, len, le) {
11638
11885
  }
11639
11886
 
11640
11887
  // src/binex/index.ts
11641
- var GPS_EPOCH_MS21 = Date.UTC(1980, 0, 6);
11888
+ var GPS_EPOCH_MS22 = Date.UTC(1980, 0, 6);
11642
11889
  var CLIGHT2 = 299792458;
11643
11890
  var OBS_CODES = {
11644
11891
  0: [
@@ -11896,7 +12143,7 @@ function decodeObsRecord(data, view, body, len, obsCodes) {
11896
12143
  p += 4;
11897
12144
  const msec = view.getUint16(p);
11898
12145
  p += 2;
11899
- const timeMs = GPS_EPOCH_MS21 + min * 6e4 + msec;
12146
+ const timeMs = GPS_EPOCH_MS22 + min * 6e4 + msec;
11900
12147
  const flag = data[p++];
11901
12148
  const nsat = (flag & 63) + 1;
11902
12149
  if (flag & 128) p += 3;
@@ -11982,7 +12229,7 @@ function parseBinex(data) {
11982
12229
  var GM_GPS = 3986005e8;
11983
12230
  var GM_GAL = 3986004418e5;
11984
12231
  var GM_BDS = 3986004418e5;
11985
- var GPS_EPOCH_MS22 = Date.UTC(1980, 0, 6);
12232
+ var GPS_EPOCH_MS23 = Date.UTC(1980, 0, 6);
11986
12233
  var BDT_EPOCH_MS6 = Date.UTC(2006, 0, 1);
11987
12234
  var WEEK_MS = 6048e5;
11988
12235
  var BDT_MINUS_GPST_MS = 14e3;
@@ -11991,7 +12238,7 @@ function almanacEpochMs(alm) {
11991
12238
  if (alm.system === "C") {
11992
12239
  return BDT_EPOCH_MS6 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3 + BDT_MINUS_GPST_MS;
11993
12240
  }
11994
- return GPS_EPOCH_MS22 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
12241
+ return GPS_EPOCH_MS23 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
11995
12242
  }
11996
12243
  function almanacSatPosition(alm, timeMs) {
11997
12244
  const GM = alm.system === "E" ? GM_GAL : alm.system === "C" ? GM_BDS : GM_GPS;
@@ -12500,10 +12747,10 @@ function selectBest(ephs, timeMs) {
12500
12747
  }
12501
12748
  return best;
12502
12749
  }
12503
- var GPS_EPOCH_MS23 = START_GPS_TIME.getTime();
12750
+ var GPS_EPOCH_MS24 = START_GPS_TIME.getTime();
12504
12751
  var MS_PER_WEEK13 = 7 * 864e5;
12505
12752
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
12506
- var GAL_EPOCH_MS = GPS_EPOCH_MS23 + 1024 * MS_PER_WEEK13;
12753
+ var GAL_EPOCH_MS = GPS_EPOCH_MS24 + 1024 * MS_PER_WEEK13;
12507
12754
  function ephInfoToEphemeris(info) {
12508
12755
  const sys = info.prn.charAt(0);
12509
12756
  if (sys === "R") {
@@ -12554,8 +12801,8 @@ function ephInfoToEphemeris(info) {
12554
12801
  } else if (sys === "E") {
12555
12802
  epochMs = GAL_EPOCH_MS;
12556
12803
  } else {
12557
- epochMs = GPS_EPOCH_MS23;
12558
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS23) / MS_PER_WEEK13;
12804
+ epochMs = GPS_EPOCH_MS24;
12805
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS24) / MS_PER_WEEK13;
12559
12806
  week += 1024 * Math.round((weeksNow - week) / 1024);
12560
12807
  }
12561
12808
  const tocDate = new Date(epochMs + week * MS_PER_WEEK13 + tocSec * 1e3);
@@ -14390,13 +14637,169 @@ function postProcessRtk(base, rover, ephemerides, baseEcef, opts = {}) {
14390
14637
  return { track, solved, unmatched, fixRate: solved ? fixed / solved : 0 };
14391
14638
  }
14392
14639
 
14640
+ // src/positioning/zero-baseline.ts
14641
+ function carrierFreqHz2(sys, band, gloChannel) {
14642
+ if (sys === "R") {
14643
+ if (band === "1") return 1602e6 + (gloChannel ?? 0) * 562500;
14644
+ if (band === "2") return 1246e6 + (gloChannel ?? 0) * 437500;
14645
+ return FREQ.R?.[band] ?? 0;
14646
+ }
14647
+ return FREQ[sys]?.[band] ?? 0;
14648
+ }
14649
+ function median(a) {
14650
+ if (a.length === 0) return 0;
14651
+ const s = [...a].sort((x, y) => x - y);
14652
+ const m = s.length >> 1;
14653
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
14654
+ }
14655
+ var ZeroBaselineEngine = class {
14656
+ ref;
14657
+ refSys;
14658
+ slip;
14659
+ /** Per (receiver|prn|code) continuous phase-SD arc. */
14660
+ arcs = /* @__PURE__ */ new Map();
14661
+ /** Per receiver: integrated phase-smoothed clock (m). */
14662
+ phaseClock = /* @__PURE__ */ new Map();
14663
+ /** Per (receiver|system): accumulated ISB (m) + count. */
14664
+ isb = /* @__PURE__ */ new Map();
14665
+ constructor(opts) {
14666
+ this.ref = opts.reference;
14667
+ this.refSys = opts.referenceSystem ?? "G";
14668
+ this.slip = opts.slipThreshM ?? 0.05;
14669
+ }
14670
+ reset() {
14671
+ this.arcs.clear();
14672
+ this.phaseClock.clear();
14673
+ this.isb.clear();
14674
+ }
14675
+ /** ISB/IFB estimates accumulated so far, per receiver × system. */
14676
+ biases() {
14677
+ const out = [];
14678
+ for (const [key, { sum, n }] of this.isb) {
14679
+ const [receiver, system] = key.split("|");
14680
+ out.push({ receiver, system, biasM: sum / n, n });
14681
+ }
14682
+ return out;
14683
+ }
14684
+ /**
14685
+ * Ingest one time-aligned instant: observations per receiver id. Returns one
14686
+ * {@link ReceiverClockSample} per non-reference receiver present this epoch.
14687
+ */
14688
+ process(epoch) {
14689
+ const refObs = epoch.obs[this.ref];
14690
+ if (!refObs) return [];
14691
+ const refByKey = /* @__PURE__ */ new Map();
14692
+ for (const o of refObs)
14693
+ if (o.pr != null && Number.isFinite(o.pr))
14694
+ refByKey.set(`${o.prn}|${o.code}`, o);
14695
+ const out = [];
14696
+ for (const rx of Object.keys(epoch.obs)) {
14697
+ if (rx === this.ref) continue;
14698
+ out.push(
14699
+ this.processReceiver(rx, epoch.obs[rx], refByKey, epoch.timeMs)
14700
+ );
14701
+ }
14702
+ return out;
14703
+ }
14704
+ processReceiver(rx, obs, refByKey, t) {
14705
+ const codeBySys = /* @__PURE__ */ new Map();
14706
+ const phase = [];
14707
+ for (const o of obs) {
14708
+ if (o.pr == null || !Number.isFinite(o.pr)) continue;
14709
+ const r = refByKey.get(`${o.prn}|${o.code}`);
14710
+ if (!r || r.pr == null) continue;
14711
+ const sys = o.prn[0];
14712
+ (codeBySys.get(sys) ?? codeBySys.set(sys, []).get(sys)).push(
14713
+ o.pr - r.pr
14714
+ );
14715
+ if (o.cp != null && r.cp != null) {
14716
+ const f = carrierFreqHz2(
14717
+ sys,
14718
+ o.code[0],
14719
+ o.gloChannel ?? r.gloChannel ?? null
14720
+ );
14721
+ if (f > 0)
14722
+ phase.push({
14723
+ key: `${rx}|${o.prn}|${o.code}`,
14724
+ sdM: (o.cp - r.cp) * (C_LIGHT / f),
14725
+ lock: o.lockTimeS
14726
+ });
14727
+ }
14728
+ }
14729
+ if (codeBySys.size === 0)
14730
+ return {
14731
+ t,
14732
+ receiver: rx,
14733
+ clockOffsetM: null,
14734
+ clockOffsetPhaseM: this.phaseClock.get(rx) ?? null,
14735
+ used: 0,
14736
+ rejected: 0,
14737
+ residualRmsM: 0
14738
+ };
14739
+ const sysMedian = /* @__PURE__ */ new Map();
14740
+ for (const [sys, v] of codeBySys) sysMedian.set(sys, median(v));
14741
+ const allSd = [];
14742
+ for (const v of codeBySys.values()) allSd.push(...v);
14743
+ const clk = sysMedian.get(this.refSys) ?? median(allSd);
14744
+ for (const [sys, med] of sysMedian) {
14745
+ if (sys === this.refSys) continue;
14746
+ const cur = this.isb.get(`${rx}|${sys}`) ?? { sum: 0, n: 0 };
14747
+ cur.sum += med - clk;
14748
+ cur.n += 1;
14749
+ this.isb.set(`${rx}|${sys}`, cur);
14750
+ }
14751
+ let ss = 0, used = 0, rejected = 0;
14752
+ for (const [sys, v] of codeBySys) {
14753
+ const isb = sys === this.refSys ? 0 : sysMedian.get(sys) - clk;
14754
+ for (const sd2 of v) {
14755
+ const res = sd2 - clk - isb;
14756
+ if (Math.abs(res) > 5) {
14757
+ rejected++;
14758
+ continue;
14759
+ }
14760
+ ss += res * res;
14761
+ used++;
14762
+ }
14763
+ }
14764
+ const residualRmsM = used ? Math.sqrt(ss / used) : 0;
14765
+ const deltas = [];
14766
+ for (const p of phase) {
14767
+ const arc = this.arcs.get(p.key);
14768
+ this.arcs.set(p.key, { lastPhaseSdM: p.sdM, lastLock: p.lock });
14769
+ if (!arc) continue;
14770
+ const lockReset = p.lock != null && arc.lastLock != null && p.lock < arc.lastLock;
14771
+ if (lockReset) continue;
14772
+ deltas.push(p.sdM - arc.lastPhaseSdM);
14773
+ }
14774
+ let phaseClockM = this.phaseClock.get(rx) ?? null;
14775
+ if (phaseClockM == null) {
14776
+ phaseClockM = clk;
14777
+ } else if (deltas.length) {
14778
+ const md = median(deltas);
14779
+ const good = deltas.filter((d) => Math.abs(d - md) < this.slip);
14780
+ const step = good.length ? good.reduce((a, b) => a + b, 0) / good.length : md;
14781
+ phaseClockM += step;
14782
+ }
14783
+ this.phaseClock.set(rx, phaseClockM);
14784
+ return {
14785
+ t,
14786
+ receiver: rx,
14787
+ clockOffsetM: clk,
14788
+ clockOffsetPhaseM: phaseClockM,
14789
+ used,
14790
+ rejected,
14791
+ residualRmsM
14792
+ };
14793
+ }
14794
+ };
14795
+
14393
14796
  // src/positioning/sbas.ts
14394
14797
  var P2_11 = 2 ** -11;
14395
14798
  var P2_31 = 2 ** -31;
14396
14799
  var P2_39 = 2 ** -39;
14397
14800
  var D2R = Math.PI / 180;
14398
14801
  var R2D = 180 / Math.PI;
14399
- var SEC_PER_WEEK12 = 604800;
14802
+ var SEC_PER_WEEK13 = 604800;
14400
14803
  var MAXSBSAGEF = 30;
14401
14804
  var MAXSBSAGEL = 360;
14402
14805
  var MAXBAND = 10;
@@ -14544,7 +14947,7 @@ var igpband2 = [
14544
14947
  [{ coord: 60, arr: x5, bits: 1, bite: 72 }, { coord: 65, arr: x6, bits: 73, bite: 108 }, { coord: 70, arr: x6, bits: 109, bite: 144 }, { coord: 75, arr: x6, bits: 145, bite: 180 }, { coord: 85, arr: x7, bits: 181, bite: 192 }],
14545
14948
  [{ coord: -60, arr: x5, bits: 1, bite: 72 }, { coord: -65, arr: x6, bits: 73, bite: 108 }, { coord: -70, arr: x6, bits: 109, bite: 144 }, { coord: -75, arr: x6, bits: 145, bite: 180 }, { coord: -85, arr: x8, bits: 181, bite: 192 }]
14546
14949
  ];
14547
- var gpsSeconds = (week, tow) => week * SEC_PER_WEEK12 + tow;
14950
+ var gpsSeconds = (week, tow) => week * SEC_PER_WEEK13 + tow;
14548
14951
  function maskPrn(i) {
14549
14952
  if (i >= 1 && i <= 37) return `G${two11(i)}`;
14550
14953
  if (i >= 38 && i <= 61) return `R${two11(i - 37)}`;
@@ -16937,7 +17340,7 @@ async function connectToMountpoint(proxyUrl, info, signal) {
16937
17340
 
16938
17341
  // src/analysis/stats-util.ts
16939
17342
  var MIN_ARC_LENGTH = 10;
16940
- function median(values) {
17343
+ function median2(values) {
16941
17344
  const s = [...values].sort((a, b) => a - b);
16942
17345
  const m = s.length >> 1;
16943
17346
  return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
@@ -17016,7 +17419,7 @@ var MultipathAccumulator = class {
17016
17419
  this.closeArc(prn, band, refBand, bandState);
17017
17420
  } else if (bandState.arc.rawMp.length > 0) {
17018
17421
  const recent = bandState.arc.rawMp.slice(-MP_JUMP_WINDOW);
17019
- if (Math.abs(mp - median(recent)) > MP_JUMP_M) {
17422
+ if (Math.abs(mp - median2(recent)) > MP_JUMP_M) {
17020
17423
  this.closeArc(prn, band, refBand, bandState);
17021
17424
  }
17022
17425
  }
@@ -17537,7 +17940,7 @@ var IonoAccumulator = class {
17537
17940
  closeArc(prn, pairKey, ps) {
17538
17941
  const arc = ps.arc;
17539
17942
  if (arc.times.length >= MIN_ARC_LENGTH) {
17540
- const level = median(arc.l4.map((v, k) => v - arc.p4[k]));
17943
+ const level = median2(arc.l4.map((v, k) => v - arc.p4[k]));
17541
17944
  const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
17542
17945
  let satArcs = this.closed.get(prn);
17543
17946
  if (!satArcs) {
@@ -19141,6 +19544,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19141
19544
  WGS84_SEMI_MAJOR_AXIS,
19142
19545
  WGS84_SEMI_MINOR_AXIS,
19143
19546
  WarningAccumulator,
19547
+ ZeroBaselineEngine,
19144
19548
  almanacEpochMs,
19145
19549
  almanacSatPosition,
19146
19550
  analyzeQuality,
@@ -19197,9 +19601,11 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19197
19601
  estimateNetworkFcbs,
19198
19602
  estimateWidelaneFcb,
19199
19603
  euclidean3D,
19604
+ extractSbfNavicSubframe,
19200
19605
  extractWidelaneArcs,
19201
19606
  feedGeoBlock,
19202
19607
  feedGeoL5Block,
19608
+ feedNavicBlock,
19203
19609
  fetchSourcetable,
19204
19610
  findSatBias,
19205
19611
  fixPppPosition,
@@ -19319,6 +19725,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19319
19725
  parseSbfIonoUtc,
19320
19726
  parseSbfMeas,
19321
19727
  parseSbfNav,
19728
+ parseSbfNavic,
19322
19729
  parseSbfPvt,
19323
19730
  parseSbfReceiverSetup,
19324
19731
  parseSinexBiasDcb,