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.
package/dist/index.cjs CHANGED
@@ -67,6 +67,7 @@ __export(src_exports, {
67
67
  GLO_F2_BASE: () => GLO_F2_BASE,
68
68
  GLO_F2_STEP: () => GLO_F2_STEP,
69
69
  GLO_F3: () => GLO_F3,
70
+ IONO_L1_M_PER_TECU: () => IONO_L1_M_PER_TECU,
70
71
  IonoAccumulator: () => IonoAccumulator,
71
72
  MILLISECONDS_GPS_TAI: () => MILLISECONDS_GPS_TAI,
72
73
  MILLISECONDS_IN_DAY: () => MILLISECONDS_IN_DAY,
@@ -202,6 +203,8 @@ __export(src_exports, {
202
203
  getUtcDate: () => getUtcDate,
203
204
  getWeekNumber: () => getWeekNumber,
204
205
  getWeekOfYear: () => getWeekOfYear,
206
+ gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
207
+ gimVerticalTec: () => gimVerticalTec,
205
208
  gloFreq: () => gloFreq,
206
209
  glonassAlmanacEpochMs: () => glonassAlmanacEpochMs,
207
210
  glonassAlmanacPosition: () => glonassAlmanacPosition,
@@ -212,6 +215,8 @@ __export(src_exports, {
212
215
  ionoFree: () => ionoFree,
213
216
  keplerPosition: () => keplerPosition,
214
217
  klobucharDelay: () => klobucharDelay,
218
+ lambdaReduction: () => lambdaReduction,
219
+ lambdaSearch: () => lambdaSearch,
215
220
  maskRadForAzimuth: () => maskRadForAzimuth,
216
221
  msmEpochToDate: () => msmEpochToDate,
217
222
  navTimesFromEph: () => navTimesFromEph,
@@ -9680,6 +9685,252 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
9680
9685
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
9681
9686
  }
9682
9687
 
9688
+ // src/positioning/gim.ts
9689
+ var R_E = 6371e3;
9690
+ var H_ION = 45e4;
9691
+ var F_L1 = 157542e4;
9692
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
9693
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
9694
+ const m = grid.maps[mapIdx];
9695
+ const { lats, lons } = grid;
9696
+ const w = lons.length;
9697
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
9698
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
9699
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
9700
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
9701
+ const di = Math.max(0, Math.min(1, fi - i0));
9702
+ const dj = Math.max(0, Math.min(1, fj - j0));
9703
+ 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;
9704
+ }
9705
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
9706
+ const e = grid.epochs;
9707
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
9708
+ let i = 0;
9709
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
9710
+ const span = e[i + 1] - e[i];
9711
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
9712
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
9713
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
9714
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
9715
+ return a * (1 - f) + b * f;
9716
+ }
9717
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
9718
+ if (elRad <= 0) return null;
9719
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
9720
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
9721
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
9722
+ const latI = Math.asin(
9723
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
9724
+ );
9725
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
9726
+ const latDeg = latI * 180 / Math.PI;
9727
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
9728
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
9729
+ if (vtec === null) return null;
9730
+ return IONO_L1_M_PER_TECU * vtec * mapping;
9731
+ }
9732
+
9733
+ // src/positioning/lambda.ts
9734
+ var LOOPMAX = 1e4;
9735
+ var ROUND = (x) => Math.floor(x + 0.5);
9736
+ var SGN = (x) => x <= 0 ? -1 : 1;
9737
+ function ldFactor(n, Q, L, D) {
9738
+ const A = Float64Array.from(Q);
9739
+ for (let i = n - 1; i >= 0; i--) {
9740
+ D[i] = A[i + i * n];
9741
+ if (D[i] <= 0) return false;
9742
+ const a = Math.sqrt(D[i]);
9743
+ for (let j = 0; j <= i; j++) L[i + j * n] = A[i + j * n] / a;
9744
+ for (let j = 0; j <= i - 1; j++)
9745
+ for (let k = 0; k <= j; k++) A[j + k * n] -= L[i + k * n] * L[i + j * n];
9746
+ for (let j = 0; j <= i; j++) L[i + j * n] /= L[i + i * n];
9747
+ }
9748
+ return true;
9749
+ }
9750
+ function gauss(n, L, Z2, i, j) {
9751
+ const mu = ROUND(L[i + j * n]);
9752
+ if (mu !== 0) {
9753
+ for (let k = i; k < n; k++) L[k + n * j] -= mu * L[k + i * n];
9754
+ for (let k = 0; k < n; k++) Z2[k + n * j] -= mu * Z2[k + i * n];
9755
+ }
9756
+ }
9757
+ function perm(n, L, D, j, del, Z2) {
9758
+ const eta = D[j] / del;
9759
+ const lam = D[j + 1] * L[j + 1 + j * n] / del;
9760
+ D[j] = eta * D[j + 1];
9761
+ D[j + 1] = del;
9762
+ for (let k = 0; k <= j - 1; k++) {
9763
+ const a0 = L[j + k * n];
9764
+ const a1 = L[j + 1 + k * n];
9765
+ L[j + k * n] = -L[j + 1 + j * n] * a0 + a1;
9766
+ L[j + 1 + k * n] = eta * a0 + lam * a1;
9767
+ }
9768
+ L[j + 1 + j * n] = lam;
9769
+ for (let k = j + 2; k < n; k++) {
9770
+ const t = L[k + j * n];
9771
+ L[k + j * n] = L[k + (j + 1) * n];
9772
+ L[k + (j + 1) * n] = t;
9773
+ }
9774
+ for (let k = 0; k < n; k++) {
9775
+ const t = Z2[k + j * n];
9776
+ Z2[k + j * n] = Z2[k + (j + 1) * n];
9777
+ Z2[k + (j + 1) * n] = t;
9778
+ }
9779
+ }
9780
+ function reduction(n, L, D, Z2) {
9781
+ let j = n - 2;
9782
+ let k = n - 2;
9783
+ while (j >= 0) {
9784
+ if (j <= k) for (let i = j + 1; i < n; i++) gauss(n, L, Z2, i, j);
9785
+ const del = D[j] + L[j + 1 + j * n] * L[j + 1 + j * n] * D[j + 1];
9786
+ if (del + 1e-6 < D[j + 1]) {
9787
+ perm(n, L, D, j, del, Z2);
9788
+ k = j;
9789
+ j = n - 2;
9790
+ } else j--;
9791
+ }
9792
+ }
9793
+ function search(n, m, L, D, zs, zn, s) {
9794
+ let nn = 0;
9795
+ let imax = 0;
9796
+ let maxdist = 1e99;
9797
+ const S = new Float64Array(n * n);
9798
+ const dist = new Float64Array(n);
9799
+ const zb = new Float64Array(n);
9800
+ const z = new Float64Array(n);
9801
+ const step = new Float64Array(n);
9802
+ let k = n - 1;
9803
+ dist[k] = 0;
9804
+ zb[k] = zs[k];
9805
+ z[k] = ROUND(zb[k]);
9806
+ let y = zb[k] - z[k];
9807
+ step[k] = SGN(y);
9808
+ let c = 0;
9809
+ for (; c < LOOPMAX; c++) {
9810
+ const newdist = dist[k] + y * y / D[k];
9811
+ if (newdist < maxdist) {
9812
+ if (k !== 0) {
9813
+ dist[--k] = newdist;
9814
+ for (let i = 0; i <= k; i++)
9815
+ S[k + i * n] = S[k + 1 + i * n] + (z[k + 1] - zb[k + 1]) * L[k + 1 + i * n];
9816
+ zb[k] = zs[k] + S[k + k * n];
9817
+ z[k] = ROUND(zb[k]);
9818
+ y = zb[k] - z[k];
9819
+ step[k] = SGN(y);
9820
+ } else {
9821
+ if (nn < m) {
9822
+ if (nn === 0 || newdist > s[imax]) imax = nn;
9823
+ for (let i = 0; i < n; i++) zn[i + nn * n] = z[i];
9824
+ s[nn++] = newdist;
9825
+ } else {
9826
+ if (newdist < s[imax]) {
9827
+ for (let i = 0; i < n; i++) zn[i + imax * n] = z[i];
9828
+ s[imax] = newdist;
9829
+ imax = 0;
9830
+ for (let i = 0; i < m; i++) if (s[imax] < s[i]) imax = i;
9831
+ }
9832
+ maxdist = s[imax];
9833
+ }
9834
+ z[0] += step[0];
9835
+ y = zb[0] - z[0];
9836
+ step[0] = -step[0] - SGN(step[0]);
9837
+ }
9838
+ } else {
9839
+ if (k === n - 1) break;
9840
+ k++;
9841
+ z[k] += step[k];
9842
+ y = zb[k] - z[k];
9843
+ step[k] = -step[k] - SGN(step[k]);
9844
+ }
9845
+ }
9846
+ for (let i = 0; i < m - 1; i++) {
9847
+ for (let j = i + 1; j < m; j++) {
9848
+ if (s[i] < s[j]) continue;
9849
+ const t = s[i];
9850
+ s[i] = s[j];
9851
+ s[j] = t;
9852
+ for (let l = 0; l < n; l++) {
9853
+ const u = zn[l + i * n];
9854
+ zn[l + i * n] = zn[l + j * n];
9855
+ zn[l + j * n] = u;
9856
+ }
9857
+ }
9858
+ }
9859
+ return c < LOOPMAX;
9860
+ }
9861
+ function solveZt(n, m, Z2, E) {
9862
+ const w = n + m;
9863
+ const M = new Float64Array(n * w);
9864
+ for (let r = 0; r < n; r++) {
9865
+ for (let cIdx = 0; cIdx < n; cIdx++) M[r * w + cIdx] = Z2[cIdx + r * n];
9866
+ for (let cIdx = 0; cIdx < m; cIdx++) M[r * w + n + cIdx] = E[r + cIdx * n];
9867
+ }
9868
+ for (let col = 0; col < n; col++) {
9869
+ let piv = col;
9870
+ for (let r = col + 1; r < n; r++)
9871
+ if (Math.abs(M[r * w + col]) > Math.abs(M[piv * w + col])) piv = r;
9872
+ if (Math.abs(M[piv * w + col]) < 1e-12) return null;
9873
+ if (piv !== col) {
9874
+ for (let cIdx = col; cIdx < w; cIdx++) {
9875
+ const t = M[col * w + cIdx];
9876
+ M[col * w + cIdx] = M[piv * w + cIdx];
9877
+ M[piv * w + cIdx] = t;
9878
+ }
9879
+ }
9880
+ const d = M[col * w + col];
9881
+ for (let cIdx = col; cIdx < w; cIdx++) M[col * w + cIdx] /= d;
9882
+ for (let r = 0; r < n; r++) {
9883
+ if (r === col) continue;
9884
+ const f = M[r * w + col];
9885
+ if (f === 0) continue;
9886
+ for (let cIdx = col; cIdx < w; cIdx++)
9887
+ M[r * w + cIdx] -= f * M[col * w + cIdx];
9888
+ }
9889
+ }
9890
+ const X = new Float64Array(n * m);
9891
+ for (let r = 0; r < n; r++)
9892
+ for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
9893
+ return X;
9894
+ }
9895
+ function lambdaSearch(a, Q, n, m = 2) {
9896
+ if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
9897
+ const L = new Float64Array(n * n);
9898
+ const D = new Float64Array(n);
9899
+ if (!ldFactor(n, Q, L, D)) return null;
9900
+ const Z2 = new Float64Array(n * n);
9901
+ for (let i = 0; i < n; i++) Z2[i + i * n] = 1;
9902
+ reduction(n, L, D, Z2);
9903
+ const z = new Float64Array(n);
9904
+ for (let j = 0; j < n; j++) {
9905
+ let sum = 0;
9906
+ for (let k = 0; k < n; k++) sum += Z2[k + j * n] * a[k];
9907
+ z[j] = sum;
9908
+ }
9909
+ const E = new Float64Array(n * m);
9910
+ const s = new Float64Array(m);
9911
+ if (!search(n, m, L, D, z, E, s)) return null;
9912
+ const F = solveZt(n, m, Z2, E);
9913
+ if (!F) return null;
9914
+ const candidates = [];
9915
+ for (let c = 0; c < m; c++) {
9916
+ const v = new Float64Array(n);
9917
+ for (let i = 0; i < n; i++) v[i] = ROUND(F[i + c * n]);
9918
+ candidates.push(v);
9919
+ }
9920
+ const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
9921
+ return { candidates, residuals: s, ratio };
9922
+ }
9923
+ function lambdaReduction(Q, n) {
9924
+ if (n <= 0 || Q.length < n * n) return null;
9925
+ const L = new Float64Array(n * n);
9926
+ const D = new Float64Array(n);
9927
+ if (!ldFactor(n, Q, L, D)) return null;
9928
+ const Z2 = new Float64Array(n * n);
9929
+ for (let i = 0; i < n; i++) Z2[i + i * n] = 1;
9930
+ reduction(n, L, D, Z2);
9931
+ return Z2;
9932
+ }
9933
+
9683
9934
  // src/positioning/rtk.ts
9684
9935
  var L1_CODES = {
9685
9936
  G: ["1C"],
@@ -10040,7 +10291,13 @@ var RtkFloatEngine = class {
10040
10291
  maxGapMs: opts.maxGapMs ?? 1e4,
10041
10292
  codeGateM: opts.codeGateM ?? 30,
10042
10293
  slipGateM: opts.slipGateM ?? 5,
10043
- updateIterations: opts.updateIterations ?? 2
10294
+ updateIterations: opts.updateIterations ?? 2,
10295
+ ambiguityResolution: opts.ambiguityResolution ?? "off",
10296
+ ratioThreshold: opts.ratioThreshold ?? 3,
10297
+ arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
10298
+ glonassAr: opts.glonassAr ?? false,
10299
+ partialFixing: opts.partialFixing ?? true,
10300
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4
10044
10301
  };
10045
10302
  }
10046
10303
  /** Clear all filter state (position, ambiguities, lock history). */
@@ -10098,6 +10355,56 @@ var RtkFloatEngine = class {
10098
10355
  const entry = this.amb[newRefIdx];
10099
10356
  entry.prn = oldRef;
10100
10357
  }
10358
+ /**
10359
+ * Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
10360
+ * extract the float subvector b and covariance Q_b from the filter,
10361
+ * run MLAMBDA and, without touching the filter state, condition the
10362
+ * position on the best integer candidate,
10363
+ * xa = x − P_xb·Q_b⁻¹·(b − ǎ)
10364
+ * (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
10365
+ * positive definite / invertible or the search fails; ratio
10366
+ * validation is the caller's job.
10367
+ */
10368
+ tryFix(idxs) {
10369
+ const k = idxs.length;
10370
+ const b = new Float64Array(k);
10371
+ const Qb = new Float64Array(k * k);
10372
+ const QbRows = [];
10373
+ for (let i = 0; i < k; i++) {
10374
+ const si = 3 + idxs[i];
10375
+ b[i] = this.x[si];
10376
+ const row = [];
10377
+ for (let j = 0; j < k; j++) {
10378
+ const sj = 3 + idxs[j];
10379
+ const q = (this.P[si][sj] + this.P[sj][si]) / 2;
10380
+ Qb[i + j * k] = q;
10381
+ row.push(q);
10382
+ }
10383
+ QbRows.push(row);
10384
+ }
10385
+ const res = lambdaSearch(b, Qb, k, 2);
10386
+ if (!res) return null;
10387
+ const best = res.candidates[0];
10388
+ const QbInv = matInv(QbRows);
10389
+ if (!QbInv) return null;
10390
+ const db = [];
10391
+ for (let i = 0; i < k; i++) db.push(b[i] - best[i]);
10392
+ const position = [
10393
+ this.x[0],
10394
+ this.x[1],
10395
+ this.x[2]
10396
+ ];
10397
+ for (let r = 0; r < 3; r++) {
10398
+ let dx = 0;
10399
+ for (let i = 0; i < k; i++) {
10400
+ let yi = 0;
10401
+ for (let j = 0; j < k; j++) yi += QbInv[i][j] * db[j];
10402
+ dx += this.P[r][3 + idxs[i]] * yi;
10403
+ }
10404
+ position[r] -= dx;
10405
+ }
10406
+ return { ratio: res.ratio, position, intAmb: [...best] };
10407
+ }
10101
10408
  /**
10102
10409
  * Process one synchronized epoch (same nominal `timeMs` for both
10103
10410
  * receivers). Returns null until a first position can be
@@ -10348,6 +10655,47 @@ var RtkFloatEngine = class {
10348
10655
  Pnew[j][i] = s;
10349
10656
  }
10350
10657
  this.P = Pnew;
10658
+ let status = rows.some(
10659
+ (r) => r.kind === "phase"
10660
+ ) ? "float" : "dgnss";
10661
+ let ratio;
10662
+ let nFixed;
10663
+ let fixedPos = null;
10664
+ let fixedAmbiguities;
10665
+ if (o.ambiguityResolution === "instant") {
10666
+ const cands = [];
10667
+ for (const row of rows) {
10668
+ if (row.kind !== "phase") continue;
10669
+ if (row.g.prn[0] === "R" && !o.glonassAr) continue;
10670
+ if (row.g.elB < o.arElevationMaskDeg * Math.PI / 180) continue;
10671
+ cands.push({ idx: row.ambIdx, el: row.g.elB });
10672
+ }
10673
+ cands.sort((a, b) => b.el - a.el);
10674
+ const floor = Math.max(
10675
+ o.minFixAmbiguities,
10676
+ Math.ceil(cands.length / 2),
10677
+ 1
10678
+ );
10679
+ while (cands.length >= floor) {
10680
+ const fix = this.tryFix(cands.map((c) => c.idx));
10681
+ if (!fix) break;
10682
+ const capped = Math.min(fix.ratio, 999.9);
10683
+ ratio ??= capped;
10684
+ if (fix.ratio >= o.ratioThreshold) {
10685
+ ratio = capped;
10686
+ status = "fixed";
10687
+ nFixed = cands.length;
10688
+ fixedPos = fix.position;
10689
+ fixedAmbiguities = {};
10690
+ cands.forEach((c, i) => {
10691
+ fixedAmbiguities[this.amb[c.idx].prn] = fix.intAmb[i];
10692
+ });
10693
+ break;
10694
+ }
10695
+ if (!o.partialFixing) break;
10696
+ cands.pop();
10697
+ }
10698
+ }
10351
10699
  for (const g of geom) {
10352
10700
  if (g.cpR === null || g.cpB === null) continue;
10353
10701
  this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
@@ -10361,14 +10709,22 @@ var RtkFloatEngine = class {
10361
10709
  const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
10362
10710
  return {
10363
10711
  timeMs,
10364
- position: [this.x[0], this.x[1], this.x[2]],
10712
+ position: fixedPos ?? [this.x[0], this.x[1], this.x[2]],
10365
10713
  floatBaseline: [
10366
10714
  this.x[0] - this.basePos[0],
10367
10715
  this.x[1] - this.basePos[1],
10368
10716
  this.x[2] - this.basePos[2]
10369
10717
  ],
10718
+ status,
10370
10719
  nSats,
10371
- ratio: void 0,
10720
+ ratio,
10721
+ nFixed,
10722
+ fixedBaseline: fixedPos ? [
10723
+ fixedPos[0] - this.basePos[0],
10724
+ fixedPos[1] - this.basePos[1],
10725
+ fixedPos[2] - this.basePos[2]
10726
+ ] : void 0,
10727
+ fixedAmbiguities,
10372
10728
  sigmas: [
10373
10729
  Math.sqrt(Math.max(this.P[0][0], 0)),
10374
10730
  Math.sqrt(Math.max(this.P[1][1], 0)),
@@ -10415,10 +10771,17 @@ var PRIMARY_FREQ_HZ = {
10415
10771
  C: 1561098e3,
10416
10772
  R: 1602e6
10417
10773
  };
10418
- var F_L1 = 157542e4;
10419
- function tropoDelay2(elevationRad) {
10420
- const sinEl = Math.sin(elevationRad);
10421
- return 2.47 / (sinEl + 0.0121);
10774
+ var F_L12 = 157542e4;
10775
+ function tropoDelay2(elevationRad, latRad, heightM) {
10776
+ if (elevationRad <= 0) return 0;
10777
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
10778
+ const humi = 0.7;
10779
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
10780
+ const temp = 15 - 65e-4 * h + 273.16;
10781
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
10782
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
10783
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
10784
+ return (zhd + zwd) / Math.sin(elevationRad);
10422
10785
  }
10423
10786
  function sagnac2(pos, travelTimeS) {
10424
10787
  const theta = OMEGA_E * travelTimeS;
@@ -10451,7 +10814,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
10451
10814
  maxIterations = 15,
10452
10815
  convergenceM = 1e-4,
10453
10816
  tgd = true,
10454
- iono
10817
+ iono,
10818
+ gim
10455
10819
  } = opts;
10456
10820
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
10457
10821
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -10478,7 +10842,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
10478
10842
  let rows = 0;
10479
10843
  for (const k of Object.keys(residuals2)) delete residuals2[k];
10480
10844
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
10481
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
10845
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
10482
10846
  for (const prn of prns) {
10483
10847
  const psr = pseudoranges.get(prn);
10484
10848
  const eph = ephemerides.get(prn);
@@ -10508,11 +10872,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
10508
10872
  const sinEl = Math.max(Math.sin(elev), 0.1);
10509
10873
  weight = sinEl * sinEl;
10510
10874
  }
10511
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
10875
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
10512
10876
  let ionoM = 0;
10513
- if (iono && positionSane) {
10514
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
10515
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
10877
+ if ((gim || iono) && positionSane) {
10878
+ let l1M = null;
10879
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
10880
+ if (l1M === null && iono) {
10881
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
10882
+ }
10883
+ if (l1M !== null) {
10884
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
10885
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
10886
+ }
10516
10887
  }
10517
10888
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
10518
10889
  const v = psr - predicted;
@@ -12899,6 +13270,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12899
13270
  GLO_F2_BASE,
12900
13271
  GLO_F2_STEP,
12901
13272
  GLO_F3,
13273
+ IONO_L1_M_PER_TECU,
12902
13274
  IonoAccumulator,
12903
13275
  MILLISECONDS_GPS_TAI,
12904
13276
  MILLISECONDS_IN_DAY,
@@ -13034,6 +13406,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
13034
13406
  getUtcDate,
13035
13407
  getWeekNumber,
13036
13408
  getWeekOfYear,
13409
+ gimSlantIonoDelayL1,
13410
+ gimVerticalTec,
13037
13411
  gloFreq,
13038
13412
  glonassAlmanacEpochMs,
13039
13413
  glonassAlmanacPosition,
@@ -13044,6 +13418,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
13044
13418
  ionoFree,
13045
13419
  keplerPosition,
13046
13420
  klobucharDelay,
13421
+ lambdaReduction,
13422
+ lambdaSearch,
13047
13423
  maskRadForAzimuth,
13048
13424
  msmEpochToDate,
13049
13425
  navTimesFromEph,
package/dist/index.d.cts CHANGED
@@ -4,9 +4,10 @@ export { A as ARC_GAP_FACTOR, B as BAND_LABELS, a as BDS_SATELLITES, b as BdsSat
4
4
  export { Scale, getBdsTime, getDateFromBdsTime, getDateFromDayOfWeek, getDateFromDayOfYear, getDateFromGalTime, getDateFromGloN, getDateFromGpsData, getDateFromGpsTime, getDateFromHourCode, getDateFromJulianDate, getDateFromMJD, getDateFromMJD2000, getDateFromRINEX, getDateFromTai, getDateFromTimeOfDay, getDateFromTt, getDateFromUnixTime, getDateFromUtc, getDayOfWeek, getDayOfYear, getGalTime, getGloN4, getGloNA, getGpsLeap, getGpsTime, getHourCode, getHoursFromTimeDifference, getJulianDate, getLeap, getMJD, getMJD2000, getMinutesFromTimeDifference, getNtpTime, getRINEX, getSecondsFromTimeDifference, getTaiDate, getTimeDifference, getTimeDifferenceFromDays, getTimeDifferenceFromHours, getTimeDifferenceFromMinutes, getTimeDifferenceFromObject, getTimeDifferenceFromSeconds, getTimeOfDay, getTimeOfWeek, getTotalDaysFromTimeDifference, getTtDate, getUnixTime, getUtcDate, getWeekNumber, getWeekOfYear } from './time.cjs';
5
5
  export { deg2dms, deg2rad, euclidean3D, geodeticToGeohash, geodeticToMaidenhead, geodeticToUtm, greatCircleMidpoint, horizonDistance, rad2deg, rhumbLine, vincenty } from './coordinates.cjs';
6
6
  export { c as clampUnit, e as ecefToGeodetic, g as geodeticToEcef, a as getAer, b as getEnuDifference } from './ecef-CF0uAysr.cjs';
7
- export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, IonexGrid, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.cjs';
7
+ export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.cjs';
8
8
  export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResult, b as RinexStats, S as SYSTEM_ORDER, c as SatObsCallback, p as parseRinexStream, s as systemCmp, d as systemName } from './parser-JPjjFgeP.cjs';
9
9
  export { C as CnavEphemeris, E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, R as RinexCnavEphemeris, p as parseNavFile } from './nav-DImXUdbp.cjs';
10
+ export { I as IonexGrid, p as parseIonex } from './ionex-DaW5x9nq.cjs';
10
11
  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
12
  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
13
  export { UbxCnavResult, UbxFrame, UbxIonoUtcResult, UbxMeasurement, UbxNavOptions, UbxNavResult, UbxParseResult, UbxRawNavOptions, UbxRawNavResult, UbxRawxEpoch, parseUbxCnav, parseUbxIonoUtc, parseUbxNav, parseUbxRawNav, parseUbxRawx, ubxFrames } from './ubx.cjs';
@@ -14,7 +15,7 @@ export { HasMessage, SbfBdsNavResult, SbfCnavEphemeris, SbfCnavResult, SbfGalEph
14
15
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelRange } from './novatel.cjs';
15
16
  export { AllPositionsData, AlmanacPosition, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.cjs';
16
17
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
17
- export { DgnssOptions, DgnssSolution, EphemerisSource, KlobucharCoeffs, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.cjs';
18
+ export { DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, gimSlantIonoDelayL1, gimVerticalTec, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.cjs';
18
19
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
19
20
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.cjs';
20
21
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';
package/dist/index.d.ts CHANGED
@@ -4,9 +4,10 @@ export { A as ARC_GAP_FACTOR, B as BAND_LABELS, a as BDS_SATELLITES, b as BdsSat
4
4
  export { Scale, getBdsTime, getDateFromBdsTime, getDateFromDayOfWeek, getDateFromDayOfYear, getDateFromGalTime, getDateFromGloN, getDateFromGpsData, getDateFromGpsTime, getDateFromHourCode, getDateFromJulianDate, getDateFromMJD, getDateFromMJD2000, getDateFromRINEX, getDateFromTai, getDateFromTimeOfDay, getDateFromTt, getDateFromUnixTime, getDateFromUtc, getDayOfWeek, getDayOfYear, getGalTime, getGloN4, getGloNA, getGpsLeap, getGpsTime, getHourCode, getHoursFromTimeDifference, getJulianDate, getLeap, getMJD, getMJD2000, getMinutesFromTimeDifference, getNtpTime, getRINEX, getSecondsFromTimeDifference, getTaiDate, getTimeDifference, getTimeDifferenceFromDays, getTimeDifferenceFromHours, getTimeDifferenceFromMinutes, getTimeDifferenceFromObject, getTimeDifferenceFromSeconds, getTimeOfDay, getTimeOfWeek, getTotalDaysFromTimeDifference, getTtDate, getUnixTime, getUtcDate, getWeekNumber, getWeekOfYear } from './time.js';
5
5
  export { deg2dms, deg2rad, euclidean3D, geodeticToGeohash, geodeticToMaidenhead, geodeticToUtm, greatCircleMidpoint, horizonDistance, rad2deg, rhumbLine, vincenty } from './coordinates.js';
6
6
  export { c as clampUnit, e as ecefToGeodetic, g as geodeticToEcef, a as getAer, b as getEnuDifference } from './ecef-CF0uAysr.js';
7
- export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, IonexGrid, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.js';
7
+ export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.js';
8
8
  export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResult, b as RinexStats, S as SYSTEM_ORDER, c as SatObsCallback, p as parseRinexStream, s as systemCmp, d as systemName } from './parser-JPjjFgeP.js';
9
9
  export { C as CnavEphemeris, E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, R as RinexCnavEphemeris, p as parseNavFile } from './nav-DImXUdbp.js';
10
+ export { I as IonexGrid, p as parseIonex } from './ionex-DaW5x9nq.js';
10
11
  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
12
  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
13
  export { UbxCnavResult, UbxFrame, UbxIonoUtcResult, UbxMeasurement, UbxNavOptions, UbxNavResult, UbxParseResult, UbxRawNavOptions, UbxRawNavResult, UbxRawxEpoch, parseUbxCnav, parseUbxIonoUtc, parseUbxNav, parseUbxRawNav, parseUbxRawx, ubxFrames } from './ubx.js';
@@ -14,7 +15,7 @@ export { HasMessage, SbfBdsNavResult, SbfCnavEphemeris, SbfCnavResult, SbfGalEph
14
15
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelRange } from './novatel.js';
15
16
  export { AllPositionsData, AlmanacPosition, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.js';
16
17
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
17
- export { DgnssOptions, DgnssSolution, EphemerisSource, KlobucharCoeffs, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.js';
18
+ export { DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, gimSlantIonoDelayL1, gimVerticalTec, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.js';
18
19
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
19
20
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.js';
20
21
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.js';
package/dist/index.js CHANGED
@@ -128,14 +128,19 @@ import {
128
128
  transformFrame
129
129
  } from "./chunk-4KOXMHUW.js";
130
130
  import {
131
+ IONO_L1_M_PER_TECU,
131
132
  RtkFloatEngine,
133
+ gimSlantIonoDelayL1,
134
+ gimVerticalTec,
132
135
  ionoFree,
133
136
  klobucharDelay,
137
+ lambdaReduction,
138
+ lambdaSearch,
134
139
  satClockCorrection,
135
140
  solveDgnss,
136
141
  solveSpp,
137
142
  toRtkEpoch
138
- } from "./chunk-CUWF56ST.js";
143
+ } from "./chunk-4XGKCVE3.js";
139
144
  import {
140
145
  almanacEpochMs,
141
146
  almanacSatPosition,
@@ -351,6 +356,7 @@ export {
351
356
  GLO_F2_BASE,
352
357
  GLO_F2_STEP,
353
358
  GLO_F3,
359
+ IONO_L1_M_PER_TECU,
354
360
  IonoAccumulator,
355
361
  MILLISECONDS_GPS_TAI,
356
362
  MILLISECONDS_IN_DAY,
@@ -486,6 +492,8 @@ export {
486
492
  getUtcDate,
487
493
  getWeekNumber,
488
494
  getWeekOfYear,
495
+ gimSlantIonoDelayL1,
496
+ gimVerticalTec,
489
497
  gloFreq,
490
498
  glonassAlmanacEpochMs,
491
499
  glonassAlmanacPosition,
@@ -496,6 +504,8 @@ export {
496
504
  ionoFree,
497
505
  keplerPosition,
498
506
  klobucharDelay,
507
+ lambdaReduction,
508
+ lambdaSearch,
499
509
  maskRadForAzimuth,
500
510
  msmEpochToDate,
501
511
  navTimesFromEph,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
3
+ * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
4
+ *
5
+ * Returns the TEC maps in TECU on a regular lat/lon grid, one map per
6
+ * epoch. RMS maps and the DCB auxiliary block are skipped.
7
+ */
8
+ interface IonexGrid {
9
+ /** Map epochs (Unix ms, UTC). */
10
+ epochs: number[];
11
+ /** Grid latitudes (degrees), in file order (typically 87.5 → −87.5). */
12
+ lats: number[];
13
+ /** Grid longitudes (degrees), in file order (typically −180 → 180). */
14
+ lons: number[];
15
+ /**
16
+ * TEC maps in TECU: maps[epochIdx][latIdx * lons.length + lonIdx].
17
+ * NaN where the file marks no value (9999).
18
+ */
19
+ maps: Float32Array[];
20
+ }
21
+ declare function parseIonex(text: string): IonexGrid;
22
+
23
+ export { type IonexGrid as I, parseIonex as p };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
3
+ * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
4
+ *
5
+ * Returns the TEC maps in TECU on a regular lat/lon grid, one map per
6
+ * epoch. RMS maps and the DCB auxiliary block are skipped.
7
+ */
8
+ interface IonexGrid {
9
+ /** Map epochs (Unix ms, UTC). */
10
+ epochs: number[];
11
+ /** Grid latitudes (degrees), in file order (typically 87.5 → −87.5). */
12
+ lats: number[];
13
+ /** Grid longitudes (degrees), in file order (typically −180 → 180). */
14
+ lons: number[];
15
+ /**
16
+ * TEC maps in TECU: maps[epochIdx][latIdx * lons.length + lonIdx].
17
+ * NaN where the file marks no value (9999).
18
+ */
19
+ maps: Float32Array[];
20
+ }
21
+ declare function parseIonex(text: string): IonexGrid;
22
+
23
+ export { type IonexGrid as I, parseIonex as p };