gnss-js 1.23.0 → 1.25.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.
@@ -20,9 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/positioning/index.ts
21
21
  var positioning_exports = {};
22
22
  __export(positioning_exports, {
23
+ IONO_L1_M_PER_TECU: () => IONO_L1_M_PER_TECU,
23
24
  RtkFloatEngine: () => RtkFloatEngine,
25
+ gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
26
+ gimVerticalTec: () => gimVerticalTec,
24
27
  ionoFree: () => ionoFree,
25
28
  klobucharDelay: () => klobucharDelay,
29
+ lambdaReduction: () => lambdaReduction,
30
+ lambdaSearch: () => lambdaSearch,
26
31
  satClockCorrection: () => satClockCorrection,
27
32
  solveDgnss: () => solveDgnss,
28
33
  solveSpp: () => solveSpp,
@@ -462,6 +467,252 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
462
467
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
463
468
  }
464
469
 
470
+ // src/positioning/gim.ts
471
+ var R_E = 6371e3;
472
+ var H_ION = 45e4;
473
+ var F_L1 = 157542e4;
474
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
475
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
476
+ const m = grid.maps[mapIdx];
477
+ const { lats, lons } = grid;
478
+ const w = lons.length;
479
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
480
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
481
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
482
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
483
+ const di = Math.max(0, Math.min(1, fi - i0));
484
+ const dj = Math.max(0, Math.min(1, fj - j0));
485
+ return m[i0 * w + j0] * (1 - di) * (1 - dj) + m[i0 * w + j0 + 1] * (1 - di) * dj + m[(i0 + 1) * w + j0] * di * (1 - dj) + m[(i0 + 1) * w + j0 + 1] * di * dj;
486
+ }
487
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
488
+ const e = grid.epochs;
489
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
490
+ let i = 0;
491
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
492
+ const span = e[i + 1] - e[i];
493
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
494
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
495
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
496
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
497
+ return a * (1 - f) + b * f;
498
+ }
499
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
500
+ if (elRad <= 0) return null;
501
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
502
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
503
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
504
+ const latI = Math.asin(
505
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
506
+ );
507
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
508
+ const latDeg = latI * 180 / Math.PI;
509
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
510
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
511
+ if (vtec === null) return null;
512
+ return IONO_L1_M_PER_TECU * vtec * mapping;
513
+ }
514
+
515
+ // src/positioning/lambda.ts
516
+ var LOOPMAX = 1e4;
517
+ var ROUND = (x) => Math.floor(x + 0.5);
518
+ var SGN = (x) => x <= 0 ? -1 : 1;
519
+ function ldFactor(n, Q, L, D) {
520
+ const A = Float64Array.from(Q);
521
+ for (let i = n - 1; i >= 0; i--) {
522
+ D[i] = A[i + i * n];
523
+ if (D[i] <= 0) return false;
524
+ const a = Math.sqrt(D[i]);
525
+ for (let j = 0; j <= i; j++) L[i + j * n] = A[i + j * n] / a;
526
+ for (let j = 0; j <= i - 1; j++)
527
+ for (let k = 0; k <= j; k++) A[j + k * n] -= L[i + k * n] * L[i + j * n];
528
+ for (let j = 0; j <= i; j++) L[i + j * n] /= L[i + i * n];
529
+ }
530
+ return true;
531
+ }
532
+ function gauss(n, L, Z, i, j) {
533
+ const mu = ROUND(L[i + j * n]);
534
+ if (mu !== 0) {
535
+ for (let k = i; k < n; k++) L[k + n * j] -= mu * L[k + i * n];
536
+ for (let k = 0; k < n; k++) Z[k + n * j] -= mu * Z[k + i * n];
537
+ }
538
+ }
539
+ function perm(n, L, D, j, del, Z) {
540
+ const eta = D[j] / del;
541
+ const lam = D[j + 1] * L[j + 1 + j * n] / del;
542
+ D[j] = eta * D[j + 1];
543
+ D[j + 1] = del;
544
+ for (let k = 0; k <= j - 1; k++) {
545
+ const a0 = L[j + k * n];
546
+ const a1 = L[j + 1 + k * n];
547
+ L[j + k * n] = -L[j + 1 + j * n] * a0 + a1;
548
+ L[j + 1 + k * n] = eta * a0 + lam * a1;
549
+ }
550
+ L[j + 1 + j * n] = lam;
551
+ for (let k = j + 2; k < n; k++) {
552
+ const t = L[k + j * n];
553
+ L[k + j * n] = L[k + (j + 1) * n];
554
+ L[k + (j + 1) * n] = t;
555
+ }
556
+ for (let k = 0; k < n; k++) {
557
+ const t = Z[k + j * n];
558
+ Z[k + j * n] = Z[k + (j + 1) * n];
559
+ Z[k + (j + 1) * n] = t;
560
+ }
561
+ }
562
+ function reduction(n, L, D, Z) {
563
+ let j = n - 2;
564
+ let k = n - 2;
565
+ while (j >= 0) {
566
+ if (j <= k) for (let i = j + 1; i < n; i++) gauss(n, L, Z, i, j);
567
+ const del = D[j] + L[j + 1 + j * n] * L[j + 1 + j * n] * D[j + 1];
568
+ if (del + 1e-6 < D[j + 1]) {
569
+ perm(n, L, D, j, del, Z);
570
+ k = j;
571
+ j = n - 2;
572
+ } else j--;
573
+ }
574
+ }
575
+ function search(n, m, L, D, zs, zn, s) {
576
+ let nn = 0;
577
+ let imax = 0;
578
+ let maxdist = 1e99;
579
+ const S = new Float64Array(n * n);
580
+ const dist = new Float64Array(n);
581
+ const zb = new Float64Array(n);
582
+ const z = new Float64Array(n);
583
+ const step = new Float64Array(n);
584
+ let k = n - 1;
585
+ dist[k] = 0;
586
+ zb[k] = zs[k];
587
+ z[k] = ROUND(zb[k]);
588
+ let y = zb[k] - z[k];
589
+ step[k] = SGN(y);
590
+ let c = 0;
591
+ for (; c < LOOPMAX; c++) {
592
+ const newdist = dist[k] + y * y / D[k];
593
+ if (newdist < maxdist) {
594
+ if (k !== 0) {
595
+ dist[--k] = newdist;
596
+ for (let i = 0; i <= k; i++)
597
+ S[k + i * n] = S[k + 1 + i * n] + (z[k + 1] - zb[k + 1]) * L[k + 1 + i * n];
598
+ zb[k] = zs[k] + S[k + k * n];
599
+ z[k] = ROUND(zb[k]);
600
+ y = zb[k] - z[k];
601
+ step[k] = SGN(y);
602
+ } else {
603
+ if (nn < m) {
604
+ if (nn === 0 || newdist > s[imax]) imax = nn;
605
+ for (let i = 0; i < n; i++) zn[i + nn * n] = z[i];
606
+ s[nn++] = newdist;
607
+ } else {
608
+ if (newdist < s[imax]) {
609
+ for (let i = 0; i < n; i++) zn[i + imax * n] = z[i];
610
+ s[imax] = newdist;
611
+ imax = 0;
612
+ for (let i = 0; i < m; i++) if (s[imax] < s[i]) imax = i;
613
+ }
614
+ maxdist = s[imax];
615
+ }
616
+ z[0] += step[0];
617
+ y = zb[0] - z[0];
618
+ step[0] = -step[0] - SGN(step[0]);
619
+ }
620
+ } else {
621
+ if (k === n - 1) break;
622
+ k++;
623
+ z[k] += step[k];
624
+ y = zb[k] - z[k];
625
+ step[k] = -step[k] - SGN(step[k]);
626
+ }
627
+ }
628
+ for (let i = 0; i < m - 1; i++) {
629
+ for (let j = i + 1; j < m; j++) {
630
+ if (s[i] < s[j]) continue;
631
+ const t = s[i];
632
+ s[i] = s[j];
633
+ s[j] = t;
634
+ for (let l = 0; l < n; l++) {
635
+ const u = zn[l + i * n];
636
+ zn[l + i * n] = zn[l + j * n];
637
+ zn[l + j * n] = u;
638
+ }
639
+ }
640
+ }
641
+ return c < LOOPMAX;
642
+ }
643
+ function solveZt(n, m, Z, E) {
644
+ const w = n + m;
645
+ const M = new Float64Array(n * w);
646
+ for (let r = 0; r < n; r++) {
647
+ for (let cIdx = 0; cIdx < n; cIdx++) M[r * w + cIdx] = Z[cIdx + r * n];
648
+ for (let cIdx = 0; cIdx < m; cIdx++) M[r * w + n + cIdx] = E[r + cIdx * n];
649
+ }
650
+ for (let col = 0; col < n; col++) {
651
+ let piv = col;
652
+ for (let r = col + 1; r < n; r++)
653
+ if (Math.abs(M[r * w + col]) > Math.abs(M[piv * w + col])) piv = r;
654
+ if (Math.abs(M[piv * w + col]) < 1e-12) return null;
655
+ if (piv !== col) {
656
+ for (let cIdx = col; cIdx < w; cIdx++) {
657
+ const t = M[col * w + cIdx];
658
+ M[col * w + cIdx] = M[piv * w + cIdx];
659
+ M[piv * w + cIdx] = t;
660
+ }
661
+ }
662
+ const d = M[col * w + col];
663
+ for (let cIdx = col; cIdx < w; cIdx++) M[col * w + cIdx] /= d;
664
+ for (let r = 0; r < n; r++) {
665
+ if (r === col) continue;
666
+ const f = M[r * w + col];
667
+ if (f === 0) continue;
668
+ for (let cIdx = col; cIdx < w; cIdx++)
669
+ M[r * w + cIdx] -= f * M[col * w + cIdx];
670
+ }
671
+ }
672
+ const X = new Float64Array(n * m);
673
+ for (let r = 0; r < n; r++)
674
+ for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
675
+ return X;
676
+ }
677
+ function lambdaSearch(a, Q, n, m = 2) {
678
+ if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
679
+ const L = new Float64Array(n * n);
680
+ const D = new Float64Array(n);
681
+ if (!ldFactor(n, Q, L, D)) return null;
682
+ const Z = new Float64Array(n * n);
683
+ for (let i = 0; i < n; i++) Z[i + i * n] = 1;
684
+ reduction(n, L, D, Z);
685
+ const z = new Float64Array(n);
686
+ for (let j = 0; j < n; j++) {
687
+ let sum = 0;
688
+ for (let k = 0; k < n; k++) sum += Z[k + j * n] * a[k];
689
+ z[j] = sum;
690
+ }
691
+ const E = new Float64Array(n * m);
692
+ const s = new Float64Array(m);
693
+ if (!search(n, m, L, D, z, E, s)) return null;
694
+ const F = solveZt(n, m, Z, E);
695
+ if (!F) return null;
696
+ const candidates = [];
697
+ for (let c = 0; c < m; c++) {
698
+ const v = new Float64Array(n);
699
+ for (let i = 0; i < n; i++) v[i] = ROUND(F[i + c * n]);
700
+ candidates.push(v);
701
+ }
702
+ const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
703
+ return { candidates, residuals: s, ratio };
704
+ }
705
+ function lambdaReduction(Q, n) {
706
+ if (n <= 0 || Q.length < n * n) return null;
707
+ const L = new Float64Array(n * n);
708
+ const D = new Float64Array(n);
709
+ if (!ldFactor(n, Q, L, D)) return null;
710
+ const Z = new Float64Array(n * n);
711
+ for (let i = 0; i < n; i++) Z[i + i * n] = 1;
712
+ reduction(n, L, D, Z);
713
+ return Z;
714
+ }
715
+
465
716
  // src/positioning/rtk.ts
466
717
  var L1_CODES = {
467
718
  G: ["1C"],
@@ -822,7 +1073,13 @@ var RtkFloatEngine = class {
822
1073
  maxGapMs: opts.maxGapMs ?? 1e4,
823
1074
  codeGateM: opts.codeGateM ?? 30,
824
1075
  slipGateM: opts.slipGateM ?? 5,
825
- updateIterations: opts.updateIterations ?? 2
1076
+ updateIterations: opts.updateIterations ?? 2,
1077
+ ambiguityResolution: opts.ambiguityResolution ?? "off",
1078
+ ratioThreshold: opts.ratioThreshold ?? 3,
1079
+ arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
1080
+ glonassAr: opts.glonassAr ?? false,
1081
+ partialFixing: opts.partialFixing ?? true,
1082
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4
826
1083
  };
827
1084
  }
828
1085
  /** Clear all filter state (position, ambiguities, lock history). */
@@ -880,6 +1137,56 @@ var RtkFloatEngine = class {
880
1137
  const entry = this.amb[newRefIdx];
881
1138
  entry.prn = oldRef;
882
1139
  }
1140
+ /**
1141
+ * Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
1142
+ * extract the float subvector b and covariance Q_b from the filter,
1143
+ * run MLAMBDA and, without touching the filter state, condition the
1144
+ * position on the best integer candidate,
1145
+ * xa = x − P_xb·Q_b⁻¹·(b − ǎ)
1146
+ * (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
1147
+ * positive definite / invertible or the search fails; ratio
1148
+ * validation is the caller's job.
1149
+ */
1150
+ tryFix(idxs) {
1151
+ const k = idxs.length;
1152
+ const b = new Float64Array(k);
1153
+ const Qb = new Float64Array(k * k);
1154
+ const QbRows = [];
1155
+ for (let i = 0; i < k; i++) {
1156
+ const si = 3 + idxs[i];
1157
+ b[i] = this.x[si];
1158
+ const row = [];
1159
+ for (let j = 0; j < k; j++) {
1160
+ const sj = 3 + idxs[j];
1161
+ const q = (this.P[si][sj] + this.P[sj][si]) / 2;
1162
+ Qb[i + j * k] = q;
1163
+ row.push(q);
1164
+ }
1165
+ QbRows.push(row);
1166
+ }
1167
+ const res = lambdaSearch(b, Qb, k, 2);
1168
+ if (!res) return null;
1169
+ const best = res.candidates[0];
1170
+ const QbInv = matInv(QbRows);
1171
+ if (!QbInv) return null;
1172
+ const db = [];
1173
+ for (let i = 0; i < k; i++) db.push(b[i] - best[i]);
1174
+ const position = [
1175
+ this.x[0],
1176
+ this.x[1],
1177
+ this.x[2]
1178
+ ];
1179
+ for (let r = 0; r < 3; r++) {
1180
+ let dx = 0;
1181
+ for (let i = 0; i < k; i++) {
1182
+ let yi = 0;
1183
+ for (let j = 0; j < k; j++) yi += QbInv[i][j] * db[j];
1184
+ dx += this.P[r][3 + idxs[i]] * yi;
1185
+ }
1186
+ position[r] -= dx;
1187
+ }
1188
+ return { ratio: res.ratio, position, intAmb: [...best] };
1189
+ }
883
1190
  /**
884
1191
  * Process one synchronized epoch (same nominal `timeMs` for both
885
1192
  * receivers). Returns null until a first position can be
@@ -1130,6 +1437,47 @@ var RtkFloatEngine = class {
1130
1437
  Pnew[j][i] = s;
1131
1438
  }
1132
1439
  this.P = Pnew;
1440
+ let status = rows.some(
1441
+ (r) => r.kind === "phase"
1442
+ ) ? "float" : "dgnss";
1443
+ let ratio;
1444
+ let nFixed;
1445
+ let fixedPos = null;
1446
+ let fixedAmbiguities;
1447
+ if (o.ambiguityResolution === "instant") {
1448
+ const cands = [];
1449
+ for (const row of rows) {
1450
+ if (row.kind !== "phase") continue;
1451
+ if (row.g.prn[0] === "R" && !o.glonassAr) continue;
1452
+ if (row.g.elB < o.arElevationMaskDeg * Math.PI / 180) continue;
1453
+ cands.push({ idx: row.ambIdx, el: row.g.elB });
1454
+ }
1455
+ cands.sort((a, b) => b.el - a.el);
1456
+ const floor = Math.max(
1457
+ o.minFixAmbiguities,
1458
+ Math.ceil(cands.length / 2),
1459
+ 1
1460
+ );
1461
+ while (cands.length >= floor) {
1462
+ const fix = this.tryFix(cands.map((c) => c.idx));
1463
+ if (!fix) break;
1464
+ const capped = Math.min(fix.ratio, 999.9);
1465
+ ratio ??= capped;
1466
+ if (fix.ratio >= o.ratioThreshold) {
1467
+ ratio = capped;
1468
+ status = "fixed";
1469
+ nFixed = cands.length;
1470
+ fixedPos = fix.position;
1471
+ fixedAmbiguities = {};
1472
+ cands.forEach((c, i) => {
1473
+ fixedAmbiguities[this.amb[c.idx].prn] = fix.intAmb[i];
1474
+ });
1475
+ break;
1476
+ }
1477
+ if (!o.partialFixing) break;
1478
+ cands.pop();
1479
+ }
1480
+ }
1133
1481
  for (const g of geom) {
1134
1482
  if (g.cpR === null || g.cpB === null) continue;
1135
1483
  this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
@@ -1143,14 +1491,22 @@ var RtkFloatEngine = class {
1143
1491
  const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
1144
1492
  return {
1145
1493
  timeMs,
1146
- position: [this.x[0], this.x[1], this.x[2]],
1494
+ position: fixedPos ?? [this.x[0], this.x[1], this.x[2]],
1147
1495
  floatBaseline: [
1148
1496
  this.x[0] - this.basePos[0],
1149
1497
  this.x[1] - this.basePos[1],
1150
1498
  this.x[2] - this.basePos[2]
1151
1499
  ],
1500
+ status,
1152
1501
  nSats,
1153
- ratio: void 0,
1502
+ ratio,
1503
+ nFixed,
1504
+ fixedBaseline: fixedPos ? [
1505
+ fixedPos[0] - this.basePos[0],
1506
+ fixedPos[1] - this.basePos[1],
1507
+ fixedPos[2] - this.basePos[2]
1508
+ ] : void 0,
1509
+ fixedAmbiguities,
1154
1510
  sigmas: [
1155
1511
  Math.sqrt(Math.max(this.P[0][0], 0)),
1156
1512
  Math.sqrt(Math.max(this.P[1][1], 0)),
@@ -1197,10 +1553,17 @@ var PRIMARY_FREQ_HZ = {
1197
1553
  C: 1561098e3,
1198
1554
  R: 1602e6
1199
1555
  };
1200
- var F_L1 = 157542e4;
1201
- function tropoDelay2(elevationRad) {
1202
- const sinEl = Math.sin(elevationRad);
1203
- return 2.47 / (sinEl + 0.0121);
1556
+ var F_L12 = 157542e4;
1557
+ function tropoDelay2(elevationRad, latRad, heightM) {
1558
+ if (elevationRad <= 0) return 0;
1559
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
1560
+ const humi = 0.7;
1561
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
1562
+ const temp = 15 - 65e-4 * h + 273.16;
1563
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
1564
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
1565
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
1566
+ return (zhd + zwd) / Math.sin(elevationRad);
1204
1567
  }
1205
1568
  function sagnac2(pos, travelTimeS) {
1206
1569
  const theta = OMEGA_E * travelTimeS;
@@ -1233,7 +1596,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1233
1596
  maxIterations = 15,
1234
1597
  convergenceM = 1e-4,
1235
1598
  tgd = true,
1236
- iono
1599
+ iono,
1600
+ gim
1237
1601
  } = opts;
1238
1602
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
1239
1603
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -1260,7 +1624,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1260
1624
  let rows = 0;
1261
1625
  for (const k of Object.keys(residuals2)) delete residuals2[k];
1262
1626
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
1263
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
1627
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
1264
1628
  for (const prn of prns) {
1265
1629
  const psr = pseudoranges.get(prn);
1266
1630
  const eph = ephemerides.get(prn);
@@ -1290,11 +1654,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1290
1654
  const sinEl = Math.max(Math.sin(elev), 0.1);
1291
1655
  weight = sinEl * sinEl;
1292
1656
  }
1293
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
1657
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
1294
1658
  let ionoM = 0;
1295
- if (iono && positionSane) {
1296
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
1297
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
1659
+ if ((gim || iono) && positionSane) {
1660
+ let l1M = null;
1661
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
1662
+ if (l1M === null && iono) {
1663
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
1664
+ }
1665
+ if (l1M !== null) {
1666
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
1667
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
1668
+ }
1298
1669
  }
1299
1670
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
1300
1671
  const v = psr - predicted;
@@ -1375,9 +1746,14 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1375
1746
  }
1376
1747
  // Annotate the CommonJS export names for ESM import in node:
1377
1748
  0 && (module.exports = {
1749
+ IONO_L1_M_PER_TECU,
1378
1750
  RtkFloatEngine,
1751
+ gimSlantIonoDelayL1,
1752
+ gimVerticalTec,
1379
1753
  ionoFree,
1380
1754
  klobucharDelay,
1755
+ lambdaReduction,
1756
+ lambdaSearch,
1381
1757
  satClockCorrection,
1382
1758
  solveDgnss,
1383
1759
  solveSpp,