gnss-js 1.22.0 → 1.24.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
@@ -82,6 +82,7 @@ __export(src_exports, {
82
82
  RINEX_CODES: () => RINEX_CODES,
83
83
  RTCM3_MESSAGE_NAMES: () => RTCM3_MESSAGE_NAMES,
84
84
  Rtcm3Decoder: () => Rtcm3Decoder,
85
+ RtkFloatEngine: () => RtkFloatEngine,
85
86
  SECONDS_IN_DAY: () => SECONDS_IN_DAY,
86
87
  SECONDS_IN_HOUR: () => SECONDS_IN_HOUR,
87
88
  SECONDS_IN_MINUTE: () => SECONDS_IN_MINUTE,
@@ -211,6 +212,8 @@ __export(src_exports, {
211
212
  ionoFree: () => ionoFree,
212
213
  keplerPosition: () => keplerPosition,
213
214
  klobucharDelay: () => klobucharDelay,
215
+ lambdaReduction: () => lambdaReduction,
216
+ lambdaSearch: () => lambdaSearch,
214
217
  maskRadForAzimuth: () => maskRadForAzimuth,
215
218
  msmEpochToDate: () => msmEpochToDate,
216
219
  navTimesFromEph: () => navTimesFromEph,
@@ -260,11 +263,13 @@ __export(src_exports, {
260
263
  selectEphemeris: () => selectEphemeris,
261
264
  setGloFreqNumber: () => setGloFreqNumber,
262
265
  setRtcm3DebugHandler: () => setRtcm3DebugHandler,
266
+ solveDgnss: () => solveDgnss,
263
267
  solveSpp: () => solveSpp,
264
268
  sp3Position: () => sp3Position,
265
269
  stationHeaderLines: () => stationHeaderLines,
266
270
  systemCmp: () => systemCmp,
267
271
  systemName: () => systemName,
272
+ toRtkEpoch: () => toRtkEpoch,
268
273
  transformFrame: () => transformFrame,
269
274
  ubxFrames: () => ubxFrames,
270
275
  updateStationMeta: () => updateStationMeta,
@@ -8851,8 +8856,7 @@ var GM_GPS2 = 3986005e8;
8851
8856
  var GM_GAL2 = 3986004418e5;
8852
8857
  var GM_BDS2 = 3986004418e5;
8853
8858
  var GM_GLO = 39860044e7;
8854
- var BDS_GEO_MAX_INCLINATION_RAD = 0.1;
8855
- var BDS_GEO_MIN_SEMIMAJOR_AXIS_M = 4e7;
8859
+ var BDS_GEO_PRNS2 = /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 59, 60, 61, 62, 63]);
8856
8860
  var AE_GLO = 6378136;
8857
8861
  var J2_GLO = 108263e-8;
8858
8862
  var TWO_PI = 2 * Math.PI;
@@ -8899,7 +8903,7 @@ function keplerPosition(eph, t) {
8899
8903
  const yp = rk * Math.sin(uk);
8900
8904
  const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
8901
8905
  const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
8902
- const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
8906
+ const isBdsGeo = eph.system === "C" && BDS_GEO_PRNS2.has(Number(eph.prn.slice(1)));
8903
8907
  if (isBdsGeo) {
8904
8908
  const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
8905
8909
  const cosO2 = Math.cos(omegak2);
@@ -9678,6 +9682,1012 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
9678
9682
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
9679
9683
  }
9680
9684
 
9685
+ // src/positioning/lambda.ts
9686
+ var LOOPMAX = 1e4;
9687
+ var ROUND = (x) => Math.floor(x + 0.5);
9688
+ var SGN = (x) => x <= 0 ? -1 : 1;
9689
+ function ldFactor(n, Q, L, D) {
9690
+ const A = Float64Array.from(Q);
9691
+ for (let i = n - 1; i >= 0; i--) {
9692
+ D[i] = A[i + i * n];
9693
+ if (D[i] <= 0) return false;
9694
+ const a = Math.sqrt(D[i]);
9695
+ for (let j = 0; j <= i; j++) L[i + j * n] = A[i + j * n] / a;
9696
+ for (let j = 0; j <= i - 1; j++)
9697
+ for (let k = 0; k <= j; k++) A[j + k * n] -= L[i + k * n] * L[i + j * n];
9698
+ for (let j = 0; j <= i; j++) L[i + j * n] /= L[i + i * n];
9699
+ }
9700
+ return true;
9701
+ }
9702
+ function gauss(n, L, Z2, i, j) {
9703
+ const mu = ROUND(L[i + j * n]);
9704
+ if (mu !== 0) {
9705
+ for (let k = i; k < n; k++) L[k + n * j] -= mu * L[k + i * n];
9706
+ for (let k = 0; k < n; k++) Z2[k + n * j] -= mu * Z2[k + i * n];
9707
+ }
9708
+ }
9709
+ function perm(n, L, D, j, del, Z2) {
9710
+ const eta = D[j] / del;
9711
+ const lam = D[j + 1] * L[j + 1 + j * n] / del;
9712
+ D[j] = eta * D[j + 1];
9713
+ D[j + 1] = del;
9714
+ for (let k = 0; k <= j - 1; k++) {
9715
+ const a0 = L[j + k * n];
9716
+ const a1 = L[j + 1 + k * n];
9717
+ L[j + k * n] = -L[j + 1 + j * n] * a0 + a1;
9718
+ L[j + 1 + k * n] = eta * a0 + lam * a1;
9719
+ }
9720
+ L[j + 1 + j * n] = lam;
9721
+ for (let k = j + 2; k < n; k++) {
9722
+ const t = L[k + j * n];
9723
+ L[k + j * n] = L[k + (j + 1) * n];
9724
+ L[k + (j + 1) * n] = t;
9725
+ }
9726
+ for (let k = 0; k < n; k++) {
9727
+ const t = Z2[k + j * n];
9728
+ Z2[k + j * n] = Z2[k + (j + 1) * n];
9729
+ Z2[k + (j + 1) * n] = t;
9730
+ }
9731
+ }
9732
+ function reduction(n, L, D, Z2) {
9733
+ let j = n - 2;
9734
+ let k = n - 2;
9735
+ while (j >= 0) {
9736
+ if (j <= k) for (let i = j + 1; i < n; i++) gauss(n, L, Z2, i, j);
9737
+ const del = D[j] + L[j + 1 + j * n] * L[j + 1 + j * n] * D[j + 1];
9738
+ if (del + 1e-6 < D[j + 1]) {
9739
+ perm(n, L, D, j, del, Z2);
9740
+ k = j;
9741
+ j = n - 2;
9742
+ } else j--;
9743
+ }
9744
+ }
9745
+ function search(n, m, L, D, zs, zn, s) {
9746
+ let nn = 0;
9747
+ let imax = 0;
9748
+ let maxdist = 1e99;
9749
+ const S = new Float64Array(n * n);
9750
+ const dist = new Float64Array(n);
9751
+ const zb = new Float64Array(n);
9752
+ const z = new Float64Array(n);
9753
+ const step = new Float64Array(n);
9754
+ let k = n - 1;
9755
+ dist[k] = 0;
9756
+ zb[k] = zs[k];
9757
+ z[k] = ROUND(zb[k]);
9758
+ let y = zb[k] - z[k];
9759
+ step[k] = SGN(y);
9760
+ let c = 0;
9761
+ for (; c < LOOPMAX; c++) {
9762
+ const newdist = dist[k] + y * y / D[k];
9763
+ if (newdist < maxdist) {
9764
+ if (k !== 0) {
9765
+ dist[--k] = newdist;
9766
+ for (let i = 0; i <= k; i++)
9767
+ S[k + i * n] = S[k + 1 + i * n] + (z[k + 1] - zb[k + 1]) * L[k + 1 + i * n];
9768
+ zb[k] = zs[k] + S[k + k * n];
9769
+ z[k] = ROUND(zb[k]);
9770
+ y = zb[k] - z[k];
9771
+ step[k] = SGN(y);
9772
+ } else {
9773
+ if (nn < m) {
9774
+ if (nn === 0 || newdist > s[imax]) imax = nn;
9775
+ for (let i = 0; i < n; i++) zn[i + nn * n] = z[i];
9776
+ s[nn++] = newdist;
9777
+ } else {
9778
+ if (newdist < s[imax]) {
9779
+ for (let i = 0; i < n; i++) zn[i + imax * n] = z[i];
9780
+ s[imax] = newdist;
9781
+ imax = 0;
9782
+ for (let i = 0; i < m; i++) if (s[imax] < s[i]) imax = i;
9783
+ }
9784
+ maxdist = s[imax];
9785
+ }
9786
+ z[0] += step[0];
9787
+ y = zb[0] - z[0];
9788
+ step[0] = -step[0] - SGN(step[0]);
9789
+ }
9790
+ } else {
9791
+ if (k === n - 1) break;
9792
+ k++;
9793
+ z[k] += step[k];
9794
+ y = zb[k] - z[k];
9795
+ step[k] = -step[k] - SGN(step[k]);
9796
+ }
9797
+ }
9798
+ for (let i = 0; i < m - 1; i++) {
9799
+ for (let j = i + 1; j < m; j++) {
9800
+ if (s[i] < s[j]) continue;
9801
+ const t = s[i];
9802
+ s[i] = s[j];
9803
+ s[j] = t;
9804
+ for (let l = 0; l < n; l++) {
9805
+ const u = zn[l + i * n];
9806
+ zn[l + i * n] = zn[l + j * n];
9807
+ zn[l + j * n] = u;
9808
+ }
9809
+ }
9810
+ }
9811
+ return c < LOOPMAX;
9812
+ }
9813
+ function solveZt(n, m, Z2, E) {
9814
+ const w = n + m;
9815
+ const M = new Float64Array(n * w);
9816
+ for (let r = 0; r < n; r++) {
9817
+ for (let cIdx = 0; cIdx < n; cIdx++) M[r * w + cIdx] = Z2[cIdx + r * n];
9818
+ for (let cIdx = 0; cIdx < m; cIdx++) M[r * w + n + cIdx] = E[r + cIdx * n];
9819
+ }
9820
+ for (let col = 0; col < n; col++) {
9821
+ let piv = col;
9822
+ for (let r = col + 1; r < n; r++)
9823
+ if (Math.abs(M[r * w + col]) > Math.abs(M[piv * w + col])) piv = r;
9824
+ if (Math.abs(M[piv * w + col]) < 1e-12) return null;
9825
+ if (piv !== col) {
9826
+ for (let cIdx = col; cIdx < w; cIdx++) {
9827
+ const t = M[col * w + cIdx];
9828
+ M[col * w + cIdx] = M[piv * w + cIdx];
9829
+ M[piv * w + cIdx] = t;
9830
+ }
9831
+ }
9832
+ const d = M[col * w + col];
9833
+ for (let cIdx = col; cIdx < w; cIdx++) M[col * w + cIdx] /= d;
9834
+ for (let r = 0; r < n; r++) {
9835
+ if (r === col) continue;
9836
+ const f = M[r * w + col];
9837
+ if (f === 0) continue;
9838
+ for (let cIdx = col; cIdx < w; cIdx++)
9839
+ M[r * w + cIdx] -= f * M[col * w + cIdx];
9840
+ }
9841
+ }
9842
+ const X = new Float64Array(n * m);
9843
+ for (let r = 0; r < n; r++)
9844
+ for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
9845
+ return X;
9846
+ }
9847
+ function lambdaSearch(a, Q, n, m = 2) {
9848
+ if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
9849
+ const L = new Float64Array(n * n);
9850
+ const D = new Float64Array(n);
9851
+ if (!ldFactor(n, Q, L, D)) return null;
9852
+ const Z2 = new Float64Array(n * n);
9853
+ for (let i = 0; i < n; i++) Z2[i + i * n] = 1;
9854
+ reduction(n, L, D, Z2);
9855
+ const z = new Float64Array(n);
9856
+ for (let j = 0; j < n; j++) {
9857
+ let sum = 0;
9858
+ for (let k = 0; k < n; k++) sum += Z2[k + j * n] * a[k];
9859
+ z[j] = sum;
9860
+ }
9861
+ const E = new Float64Array(n * m);
9862
+ const s = new Float64Array(m);
9863
+ if (!search(n, m, L, D, z, E, s)) return null;
9864
+ const F = solveZt(n, m, Z2, E);
9865
+ if (!F) return null;
9866
+ const candidates = [];
9867
+ for (let c = 0; c < m; c++) {
9868
+ const v = new Float64Array(n);
9869
+ for (let i = 0; i < n; i++) v[i] = ROUND(F[i + c * n]);
9870
+ candidates.push(v);
9871
+ }
9872
+ const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
9873
+ return { candidates, residuals: s, ratio };
9874
+ }
9875
+ function lambdaReduction(Q, n) {
9876
+ if (n <= 0 || Q.length < n * n) return null;
9877
+ const L = new Float64Array(n * n);
9878
+ const D = new Float64Array(n);
9879
+ if (!ldFactor(n, Q, L, D)) return null;
9880
+ const Z2 = new Float64Array(n * n);
9881
+ for (let i = 0; i < n; i++) Z2[i + i * n] = 1;
9882
+ reduction(n, L, D, Z2);
9883
+ return Z2;
9884
+ }
9885
+
9886
+ // src/positioning/rtk.ts
9887
+ var L1_CODES = {
9888
+ G: ["1C"],
9889
+ E: ["1C"],
9890
+ R: ["1C"],
9891
+ J: ["1C"],
9892
+ C: ["2I", "1P"]
9893
+ // B1I first (classic B1), then B1C
9894
+ };
9895
+ function toRtkEpoch(meas) {
9896
+ const out = /* @__PURE__ */ new Map();
9897
+ const rank = /* @__PURE__ */ new Map();
9898
+ for (const m of meas) {
9899
+ const prefs = L1_CODES[m.prn[0]];
9900
+ if (!prefs || m.pr === null || !Number.isFinite(m.pr)) continue;
9901
+ const r = prefs.indexOf(m.code);
9902
+ if (r < 0) continue;
9903
+ const prev = rank.get(m.prn);
9904
+ if (prev !== void 0 && prev <= r) continue;
9905
+ rank.set(m.prn, r);
9906
+ out.set(m.prn, {
9907
+ code: m.code,
9908
+ pr: m.pr,
9909
+ cp: m.cp ?? null,
9910
+ lockTimeMs: m.lockTimeS !== void 0 ? Math.round(m.lockTimeS * 1e3) : void 0,
9911
+ gloChannel: m.gloChannel ?? null
9912
+ });
9913
+ }
9914
+ return out;
9915
+ }
9916
+ function carrierFreqHz(sys, code, gloChannel) {
9917
+ const band = code[0];
9918
+ switch (sys) {
9919
+ case "G":
9920
+ case "J":
9921
+ return band === "1" ? 157542e4 : band === "2" ? 12276e5 : 117645e4;
9922
+ case "E":
9923
+ return band === "1" ? 157542e4 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : band === "8" ? 1191795e3 : 127875e4;
9924
+ case "C":
9925
+ return band === "1" ? 157542e4 : band === "2" ? 1561098e3 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : 126852e4;
9926
+ case "R": {
9927
+ if (gloChannel === null) return 0;
9928
+ if (band === "1") return 1602e6 + gloChannel * 562500;
9929
+ if (band === "2") return 1246e6 + gloChannel * 437500;
9930
+ return 1202025e3;
9931
+ }
9932
+ default:
9933
+ return 0;
9934
+ }
9935
+ }
9936
+ function sagnac(pos, travelTimeS) {
9937
+ const theta = OMEGA_E * travelTimeS;
9938
+ const c = Math.cos(theta);
9939
+ const s = Math.sin(theta);
9940
+ return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
9941
+ }
9942
+ function tropoDelay(elevationRad) {
9943
+ const sinEl = Math.sin(elevationRad);
9944
+ return 2.47 / (sinEl + 0.0121);
9945
+ }
9946
+ function resolveEphemeris(src, prn, timeMs) {
9947
+ if (Array.isArray(src))
9948
+ return selectEphemeris(src, prn, timeMs);
9949
+ return src.get(prn) ?? null;
9950
+ }
9951
+ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere) {
9952
+ const out = [];
9953
+ for (const [prn, mR] of rover) {
9954
+ const sys = prn[0];
9955
+ if (!"GERCJ".includes(sys)) continue;
9956
+ const mB = base.get(prn);
9957
+ if (!mB || mB.code !== mR.code) continue;
9958
+ if (mR.pr === null || mB.pr === null || !Number.isFinite(mR.pr) || !Number.isFinite(mB.pr))
9959
+ continue;
9960
+ const eph = resolveEphemeris(ephemerides, prn, timeMs);
9961
+ if (!eph) continue;
9962
+ const satAt = (pr) => {
9963
+ const tTx = timeMs - pr / C_LIGHT * 1e3;
9964
+ const dts = satClockCorrection(eph, tTx);
9965
+ const sat = computeSatPosition(eph, tTx - dts * 1e3);
9966
+ return Number.isFinite(sat.x) ? sat : null;
9967
+ };
9968
+ const satB = satAt(mB.pr);
9969
+ const satR = satAt(mR.pr);
9970
+ if (!satB || !satR) continue;
9971
+ const travelB = Math.hypot(
9972
+ satB.x - basePos[0],
9973
+ satB.y - basePos[1],
9974
+ satB.z - basePos[2]
9975
+ ) / C_LIGHT;
9976
+ const [bx, by, bz] = sagnac(satB, travelB);
9977
+ const rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
9978
+ const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
9979
+ if (elB < maskRad) continue;
9980
+ const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
9981
+ const freq = carrierFreqHz(sys, mR.code, gloK);
9982
+ out.push({
9983
+ prn,
9984
+ group: sys + mR.code,
9985
+ lambda: freq > 0 ? C_LIGHT / freq : 0,
9986
+ satR,
9987
+ rhoB,
9988
+ elB,
9989
+ tropoB: troposphere ? tropoDelay(elB) : 0,
9990
+ prR: mR.pr,
9991
+ prB: mB.pr,
9992
+ cpR: mR.cp ?? null,
9993
+ cpB: mB.cp ?? null,
9994
+ lockR: mR.lockTimeMs,
9995
+ lockB: mB.lockTimeMs
9996
+ });
9997
+ }
9998
+ return out;
9999
+ }
10000
+ function roverTerms(g, x, y, z, troposphere) {
10001
+ const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
10002
+ const [sx, sy, sz] = sagnac(g.satR, travel);
10003
+ const rho = Math.hypot(sx - x, sy - y, sz - z);
10004
+ const u = [
10005
+ (x - sx) / rho,
10006
+ (y - sy) / rho,
10007
+ (z - sz) / rho
10008
+ ];
10009
+ let dTropo = 0;
10010
+ if (troposphere) {
10011
+ const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
10012
+ dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
10013
+ }
10014
+ return { rho, u, dTropo };
10015
+ }
10016
+ function sdVariance(sigmaM, elRad) {
10017
+ const sinEl = Math.max(Math.sin(elRad), 0.05);
10018
+ return 2 * sigmaM * sigmaM * (1 + 1 / (sinEl * sinEl));
10019
+ }
10020
+ function zeros(n, m) {
10021
+ return Array.from({ length: n }, () => new Array(m).fill(0));
10022
+ }
10023
+ function matInv(A) {
10024
+ const n = A.length;
10025
+ const M = A.map((row, i) => {
10026
+ const r = [...row, ...new Array(n).fill(0)];
10027
+ r[n + i] = 1;
10028
+ return r;
10029
+ });
10030
+ for (let col = 0; col < n; col++) {
10031
+ let pivot = col;
10032
+ for (let r = col + 1; r < n; r++) {
10033
+ if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
10034
+ }
10035
+ if (Math.abs(M[pivot][col]) < 1e-13) return null;
10036
+ [M[col], M[pivot]] = [M[pivot], M[col]];
10037
+ const d = M[col][col];
10038
+ for (let c = 0; c < 2 * n; c++) M[col][c] /= d;
10039
+ for (let r = 0; r < n; r++) {
10040
+ if (r === col) continue;
10041
+ const f = M[r][col];
10042
+ if (f === 0) continue;
10043
+ for (let c = 0; c < 2 * n; c++) M[r][c] -= f * M[col][c];
10044
+ }
10045
+ }
10046
+ return M.map((row) => row.slice(n));
10047
+ }
10048
+ function matMul(A, B) {
10049
+ const n = A.length;
10050
+ const k = B.length;
10051
+ const m = B[0]?.length ?? 0;
10052
+ const C = zeros(n, m);
10053
+ for (let i = 0; i < n; i++) {
10054
+ for (let l = 0; l < k; l++) {
10055
+ const a = A[i][l];
10056
+ if (a === 0) continue;
10057
+ for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
10058
+ }
10059
+ }
10060
+ return C;
10061
+ }
10062
+ function transpose(A) {
10063
+ const n = A.length;
10064
+ const m = A[0]?.length ?? 0;
10065
+ const T = zeros(m, n);
10066
+ for (let i = 0; i < n; i++) for (let j = 0; j < m; j++) T[j][i] = A[i][j];
10067
+ return T;
10068
+ }
10069
+ function groupEntries(geom) {
10070
+ const groups = /* @__PURE__ */ new Map();
10071
+ for (const g of geom) {
10072
+ const list = groups.get(g.group);
10073
+ if (list) list.push(g);
10074
+ else groups.set(g.group, [g]);
10075
+ }
10076
+ return groups;
10077
+ }
10078
+ function ddCovariance(rows, sigmaCode, sigmaPhase) {
10079
+ const n = rows.length;
10080
+ const R = zeros(n, n);
10081
+ const sd2 = (r, g) => sdVariance(r.kind === "code" ? sigmaCode : sigmaPhase, g.elB);
10082
+ for (let i = 0; i < n; i++) {
10083
+ const ri = rows[i];
10084
+ R[i][i] = sd2(ri, ri.g) + sd2(ri, ri.ref);
10085
+ for (let j = i + 1; j < n; j++) {
10086
+ const rj = rows[j];
10087
+ if (ri.ref.prn === rj.ref.prn && ri.kind === rj.kind && ri.g.group === rj.g.group) {
10088
+ R[i][j] = R[j][i] = sd2(ri, ri.ref);
10089
+ }
10090
+ }
10091
+ }
10092
+ return R;
10093
+ }
10094
+ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
10095
+ const {
10096
+ elevationMaskDeg = 10,
10097
+ troposphere = true,
10098
+ maxIterations = 15,
10099
+ convergenceM = 1e-4,
10100
+ codeSigmaM = 0.3,
10101
+ rejectThresholdM = 20,
10102
+ initialPosition
10103
+ } = opts;
10104
+ let geom = buildGeometry(
10105
+ rover,
10106
+ base,
10107
+ basePos,
10108
+ ephemerides,
10109
+ timeMs,
10110
+ elevationMaskDeg * Math.PI / 180,
10111
+ troposphere
10112
+ );
10113
+ const rejected = [];
10114
+ for (; ; ) {
10115
+ const groups = groupEntries(geom);
10116
+ const rows = [];
10117
+ const refSatellites = {};
10118
+ for (const [group, list] of groups) {
10119
+ if (list.length < 2) continue;
10120
+ const ref = list.reduce((a, b) => b.elB > a.elB ? b : a);
10121
+ refSatellites[group] = ref.prn;
10122
+ for (const g of list) {
10123
+ if (g === ref) continue;
10124
+ rows.push({
10125
+ g,
10126
+ ref,
10127
+ kind: "code",
10128
+ z: g.prR - g.prB - (ref.prR - ref.prB)
10129
+ });
10130
+ }
10131
+ }
10132
+ if (rows.length < 3) return null;
10133
+ const W = matInv(ddCovariance(rows, codeSigmaM, codeSigmaM));
10134
+ if (!W) return null;
10135
+ let [x, y, z] = initialPosition ?? basePos;
10136
+ let iterations = 0;
10137
+ let converged = false;
10138
+ const residuals = {};
10139
+ let cov = null;
10140
+ for (let iter = 0; iter < maxIterations; iter++) {
10141
+ iterations = iter + 1;
10142
+ const H2 = [];
10143
+ const v = [];
10144
+ const terms = /* @__PURE__ */ new Map();
10145
+ const termOf = (g) => {
10146
+ let t = terms.get(g.prn);
10147
+ if (!t) {
10148
+ t = roverTerms(g, x, y, z, troposphere);
10149
+ terms.set(g.prn, t);
10150
+ }
10151
+ return t;
10152
+ };
10153
+ for (const row of rows) {
10154
+ const ts = termOf(row.g);
10155
+ const tr = termOf(row.ref);
10156
+ const pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
10157
+ v.push(row.z - pred);
10158
+ H2.push([ts.u[0] - tr.u[0], ts.u[1] - tr.u[1], ts.u[2] - tr.u[2]]);
10159
+ }
10160
+ const Ht = transpose(H2);
10161
+ const HtW = matMul(Ht, W);
10162
+ const N = matMul(HtW, H2);
10163
+ const b = matMul(
10164
+ HtW,
10165
+ v.map((s) => [s])
10166
+ );
10167
+ const Ninv = matInv(N);
10168
+ if (!Ninv) return null;
10169
+ const dx = matMul(
10170
+ Ninv,
10171
+ b.map((r) => [r[0]])
10172
+ );
10173
+ x += dx[0][0];
10174
+ y += dx[1][0];
10175
+ z += dx[2][0];
10176
+ cov = Ninv;
10177
+ rows.forEach((row, i) => {
10178
+ residuals[row.g.prn] = v[i] - (H2[i][0] * dx[0][0] + H2[i][1] * dx[1][0] + H2[i][2] * dx[2][0]);
10179
+ });
10180
+ if (Math.hypot(dx[0][0], dx[1][0], dx[2][0]) < convergenceM) {
10181
+ converged = true;
10182
+ break;
10183
+ }
10184
+ }
10185
+ let worst = null;
10186
+ let worstAbs = rejectThresholdM;
10187
+ for (const [prn, r] of Object.entries(residuals)) {
10188
+ if (Math.abs(r) > worstAbs) {
10189
+ worstAbs = Math.abs(r);
10190
+ worst = prn;
10191
+ }
10192
+ }
10193
+ if (worst && geom.length > 4) {
10194
+ rejected.push(worst);
10195
+ geom = geom.filter((g) => g.prn !== worst);
10196
+ continue;
10197
+ }
10198
+ const usedSatellites = [
10199
+ ...new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn]))
10200
+ ].sort();
10201
+ return {
10202
+ position: [x, y, z],
10203
+ baseline: [x - basePos[0], y - basePos[1], z - basePos[2]],
10204
+ sigmas: cov ? [
10205
+ Math.sqrt(Math.max(cov[0][0], 0)),
10206
+ Math.sqrt(Math.max(cov[1][1], 0)),
10207
+ Math.sqrt(Math.max(cov[2][2], 0))
10208
+ ] : [0, 0, 0],
10209
+ usedSatellites,
10210
+ refSatellites,
10211
+ residuals,
10212
+ rejectedSatellites: rejected,
10213
+ nSats: usedSatellites.length,
10214
+ iterations,
10215
+ converged
10216
+ };
10217
+ }
10218
+ }
10219
+ var RtkFloatEngine = class {
10220
+ basePos;
10221
+ ephemerides;
10222
+ o;
10223
+ /** State vector [x, y, z, N₁…] and covariance; null before init. */
10224
+ x = null;
10225
+ P = [];
10226
+ amb = [];
10227
+ refs = /* @__PURE__ */ new Map();
10228
+ track = /* @__PURE__ */ new Map();
10229
+ lastMs = null;
10230
+ constructor(basePos, ephemerides, opts = {}) {
10231
+ this.basePos = basePos;
10232
+ this.ephemerides = ephemerides;
10233
+ this.o = {
10234
+ mode: opts.mode ?? "kinematic",
10235
+ elevationMaskDeg: opts.elevationMaskDeg ?? 10,
10236
+ troposphere: opts.troposphere ?? true,
10237
+ codeSigmaM: opts.codeSigmaM ?? 0.3,
10238
+ phaseSigmaM: opts.phaseSigmaM ?? 3e-3,
10239
+ processNoisePosM: opts.processNoisePosM ?? 0,
10240
+ kinematicSigmaM: opts.kinematicSigmaM ?? 30,
10241
+ ambProcessNoiseCycles: opts.ambProcessNoiseCycles ?? 1e-4,
10242
+ ambInitSigmaCycles: opts.ambInitSigmaCycles ?? 30,
10243
+ maxGapMs: opts.maxGapMs ?? 1e4,
10244
+ codeGateM: opts.codeGateM ?? 30,
10245
+ slipGateM: opts.slipGateM ?? 5,
10246
+ updateIterations: opts.updateIterations ?? 2,
10247
+ ambiguityResolution: opts.ambiguityResolution ?? "off",
10248
+ ratioThreshold: opts.ratioThreshold ?? 3,
10249
+ arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
10250
+ glonassAr: opts.glonassAr ?? false,
10251
+ partialFixing: opts.partialFixing ?? true,
10252
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4
10253
+ };
10254
+ }
10255
+ /** Clear all filter state (position, ambiguities, lock history). */
10256
+ reset() {
10257
+ this.x = null;
10258
+ this.P = [];
10259
+ this.amb = [];
10260
+ this.refs.clear();
10261
+ this.track.clear();
10262
+ this.lastMs = null;
10263
+ }
10264
+ ambIndex(prn) {
10265
+ return this.amb.findIndex((a) => a.prn === prn);
10266
+ }
10267
+ /** Remove one ambiguity state (row/col) from x and P. */
10268
+ dropAmb(idx) {
10269
+ const s = 3 + idx;
10270
+ this.amb.splice(idx, 1);
10271
+ this.x.splice(s, 1);
10272
+ this.P.splice(s, 1);
10273
+ for (const row of this.P) row.splice(s, 1);
10274
+ }
10275
+ /** Append an ambiguity state with the given value and variance. */
10276
+ addAmb(entry, value, variance) {
10277
+ this.amb.push(entry);
10278
+ this.x.push(value);
10279
+ const n = this.x.length;
10280
+ for (const row of this.P) row.push(0);
10281
+ const newRow = new Array(n).fill(0);
10282
+ newRow[n - 1] = variance;
10283
+ this.P.push(newRow);
10284
+ }
10285
+ /**
10286
+ * Re-map a group's DD ambiguities from the old reference r to the
10287
+ * new reference r' (which must hold a state): N_i' = N_i − N_r' and
10288
+ * the old reference's slot becomes N_r = −N_r'. Exact linear
10289
+ * transform of state and covariance (P' = T P Tᵀ).
10290
+ */
10291
+ retarget(group, oldRef, newRefIdx) {
10292
+ const n = this.x.length;
10293
+ const j = 3 + newRefIdx;
10294
+ const T = zeros(n, n);
10295
+ for (let i = 0; i < n; i++) T[i][i] = 1;
10296
+ for (let k = 0; k < this.amb.length; k++) {
10297
+ if (this.amb[k].group !== group) continue;
10298
+ const s = 3 + k;
10299
+ if (s === j) T[s][s] = -1;
10300
+ else T[s][j] -= 1;
10301
+ }
10302
+ this.x = matMul(
10303
+ T,
10304
+ this.x.map((v) => [v])
10305
+ ).map((r) => r[0]);
10306
+ this.P = matMul(matMul(T, this.P), transpose(T));
10307
+ const entry = this.amb[newRefIdx];
10308
+ entry.prn = oldRef;
10309
+ }
10310
+ /**
10311
+ * Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
10312
+ * extract the float subvector b and covariance Q_b from the filter,
10313
+ * run MLAMBDA and, without touching the filter state, condition the
10314
+ * position on the best integer candidate,
10315
+ * xa = x − P_xb·Q_b⁻¹·(b − ǎ)
10316
+ * (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
10317
+ * positive definite / invertible or the search fails; ratio
10318
+ * validation is the caller's job.
10319
+ */
10320
+ tryFix(idxs) {
10321
+ const k = idxs.length;
10322
+ const b = new Float64Array(k);
10323
+ const Qb = new Float64Array(k * k);
10324
+ const QbRows = [];
10325
+ for (let i = 0; i < k; i++) {
10326
+ const si = 3 + idxs[i];
10327
+ b[i] = this.x[si];
10328
+ const row = [];
10329
+ for (let j = 0; j < k; j++) {
10330
+ const sj = 3 + idxs[j];
10331
+ const q = (this.P[si][sj] + this.P[sj][si]) / 2;
10332
+ Qb[i + j * k] = q;
10333
+ row.push(q);
10334
+ }
10335
+ QbRows.push(row);
10336
+ }
10337
+ const res = lambdaSearch(b, Qb, k, 2);
10338
+ if (!res) return null;
10339
+ const best = res.candidates[0];
10340
+ const QbInv = matInv(QbRows);
10341
+ if (!QbInv) return null;
10342
+ const db = [];
10343
+ for (let i = 0; i < k; i++) db.push(b[i] - best[i]);
10344
+ const position = [
10345
+ this.x[0],
10346
+ this.x[1],
10347
+ this.x[2]
10348
+ ];
10349
+ for (let r = 0; r < 3; r++) {
10350
+ let dx = 0;
10351
+ for (let i = 0; i < k; i++) {
10352
+ let yi = 0;
10353
+ for (let j = 0; j < k; j++) yi += QbInv[i][j] * db[j];
10354
+ dx += this.P[r][3 + idxs[i]] * yi;
10355
+ }
10356
+ position[r] -= dx;
10357
+ }
10358
+ return { ratio: res.ratio, position, intAmb: [...best] };
10359
+ }
10360
+ /**
10361
+ * Process one synchronized epoch (same nominal `timeMs` for both
10362
+ * receivers). Returns null until a first position can be
10363
+ * initialised (via an internal DGNSS solve) or when the epoch has
10364
+ * fewer than 3 usable DD rows.
10365
+ */
10366
+ process(rover, base, timeMs) {
10367
+ const o = this.o;
10368
+ const geom = buildGeometry(
10369
+ rover,
10370
+ base,
10371
+ this.basePos,
10372
+ this.ephemerides,
10373
+ timeMs,
10374
+ o.elevationMaskDeg * Math.PI / 180,
10375
+ o.troposphere
10376
+ );
10377
+ const byPrn = new Map(geom.map((g) => [g.prn, g]));
10378
+ const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
10379
+ if (this.x) {
10380
+ for (let i = this.amb.length - 1; i >= 0; i--) {
10381
+ const prn = this.amb[i].prn;
10382
+ const tr = this.track.get(prn);
10383
+ const seen = byPrn.get(prn);
10384
+ const hasPhase = seen && seen.cpR !== null && seen.cpB !== null;
10385
+ const staleMs = tr ? timeMs - tr.lastMs : Infinity;
10386
+ if (!hasPhase && staleMs > o.maxGapMs) this.dropAmb(i);
10387
+ }
10388
+ }
10389
+ const slipped = /* @__PURE__ */ new Set();
10390
+ for (const g of geom) {
10391
+ if (g.cpR === null || g.cpB === null) continue;
10392
+ const tr = this.track.get(g.prn);
10393
+ if (!tr) continue;
10394
+ if (timeMs - tr.lastMs > o.maxGapMs) slipped.add(g.prn);
10395
+ else if (g.lockR !== void 0 && tr.lockR !== void 0 && g.lockR < tr.lockR || g.lockB !== void 0 && tr.lockB !== void 0 && g.lockB < tr.lockB)
10396
+ slipped.add(g.prn);
10397
+ }
10398
+ if (!this.x) {
10399
+ const dg = solveDgnss(
10400
+ rover,
10401
+ base,
10402
+ this.basePos,
10403
+ this.ephemerides,
10404
+ timeMs,
10405
+ {
10406
+ elevationMaskDeg: o.elevationMaskDeg,
10407
+ troposphere: o.troposphere,
10408
+ codeSigmaM: o.codeSigmaM
10409
+ }
10410
+ );
10411
+ if (!dg) return null;
10412
+ this.x = [...dg.position];
10413
+ this.P = zeros(3, 3);
10414
+ for (let i = 0; i < 3; i++) {
10415
+ const s = Math.max(dg.sigmas[i], 1);
10416
+ this.P[i][i] = 25 * s * s;
10417
+ }
10418
+ this.amb = [];
10419
+ } else if (o.mode === "kinematic") {
10420
+ for (let i = 0; i < 3; i++) {
10421
+ for (let j = 0; j < this.x.length; j++) {
10422
+ this.P[i][j] = 0;
10423
+ this.P[j][i] = 0;
10424
+ }
10425
+ this.P[i][i] = o.kinematicSigmaM * o.kinematicSigmaM;
10426
+ }
10427
+ } else {
10428
+ const q = o.processNoisePosM * o.processNoisePosM * dtS;
10429
+ for (let i = 0; i < 3; i++) this.P[i][i] += q;
10430
+ }
10431
+ const qAmb = o.ambProcessNoiseCycles * o.ambProcessNoiseCycles * dtS;
10432
+ for (let k = 0; k < this.amb.length; k++) this.P[3 + k][3 + k] += qAmb;
10433
+ const groups = groupEntries(geom);
10434
+ const newRefs = /* @__PURE__ */ new Map();
10435
+ for (const [group, list] of groups) {
10436
+ if (list.length < 2) continue;
10437
+ const phase = list.filter(
10438
+ (g) => g.cpR !== null && g.cpB !== null && !slipped.has(g.prn)
10439
+ );
10440
+ const pool = phase.length >= 2 ? phase : list;
10441
+ const prevRef = this.refs.get(group);
10442
+ const best = pool.reduce((a, b) => b.elB > a.elB ? b : a);
10443
+ let ref = best;
10444
+ if (best.prn !== prevRef) {
10445
+ const anyState = this.amb.some((a) => a.group === group);
10446
+ if (anyState) {
10447
+ if (this.ambIndex(best.prn) >= 0) {
10448
+ this.retarget(group, prevRef ?? best.prn, this.ambIndex(best.prn));
10449
+ } else if (prevRef && pool.some((g) => g.prn === prevRef)) {
10450
+ ref = pool.find((g) => g.prn === prevRef);
10451
+ } else {
10452
+ for (let i = this.amb.length - 1; i >= 0; i--)
10453
+ if (this.amb[i].group === group) this.dropAmb(i);
10454
+ }
10455
+ }
10456
+ }
10457
+ newRefs.set(group, ref);
10458
+ this.refs.set(group, ref.prn);
10459
+ }
10460
+ for (const group of [...this.refs.keys()])
10461
+ if (!newRefs.has(group)) this.refs.delete(group);
10462
+ for (const prn of slipped) {
10463
+ const i = this.ambIndex(prn);
10464
+ if (i >= 0) this.dropAmb(i);
10465
+ }
10466
+ const initAmb = (g, ref) => {
10467
+ if (g.lambda <= 0 || ref.lambda <= 0) return;
10468
+ const zPhi = g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB);
10469
+ const zP = g.prR - g.prB - (ref.prR - ref.prB);
10470
+ const n0 = (zPhi - zP) / g.lambda;
10471
+ this.addAmb(
10472
+ { prn: g.prn, group: g.group, lambda: g.lambda },
10473
+ n0,
10474
+ o.ambInitSigmaCycles * o.ambInitSigmaCycles
10475
+ );
10476
+ };
10477
+ for (const [group, ref] of newRefs) {
10478
+ for (const g of groups.get(group)) {
10479
+ if (g === ref || g.cpR === null || g.cpB === null) continue;
10480
+ if (ref.cpR === null || ref.cpB === null) continue;
10481
+ const i = this.ambIndex(g.prn);
10482
+ if (i >= 0) {
10483
+ this.amb[i].lambda = g.lambda;
10484
+ this.amb[i].group = g.group;
10485
+ continue;
10486
+ }
10487
+ initAmb(g, ref);
10488
+ }
10489
+ }
10490
+ for (let i = this.amb.length - 1; i >= 0; i--) {
10491
+ const a = this.amb[i];
10492
+ if (newRefs.get(a.group)?.prn === a.prn) this.dropAmb(i);
10493
+ }
10494
+ let rows = [];
10495
+ for (const [group, ref] of newRefs) {
10496
+ for (const g of groups.get(group)) {
10497
+ if (g === ref) continue;
10498
+ rows.push({
10499
+ g,
10500
+ ref,
10501
+ kind: "code",
10502
+ z: g.prR - g.prB - (ref.prR - ref.prB),
10503
+ ambIdx: -1
10504
+ });
10505
+ const i = this.ambIndex(g.prn);
10506
+ if (i >= 0 && g.cpR !== null && g.cpB !== null && ref.cpR !== null && ref.cpB !== null && g.lambda > 0 && ref.lambda > 0) {
10507
+ rows.push({
10508
+ g,
10509
+ ref,
10510
+ kind: "phase",
10511
+ z: g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB),
10512
+ ambIdx: i
10513
+ });
10514
+ }
10515
+ }
10516
+ }
10517
+ if (rows.length < 3) {
10518
+ this.lastMs = timeMs;
10519
+ return null;
10520
+ }
10521
+ const predict = (row, xs) => {
10522
+ const ts = roverTerms(row.g, xs[0], xs[1], xs[2], o.troposphere);
10523
+ const tr = roverTerms(row.ref, xs[0], xs[1], xs[2], o.troposphere);
10524
+ let pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
10525
+ const h = new Array(this.x.length).fill(0);
10526
+ h[0] = ts.u[0] - tr.u[0];
10527
+ h[1] = ts.u[1] - tr.u[1];
10528
+ h[2] = ts.u[2] - tr.u[2];
10529
+ if (row.ambIdx >= 0) {
10530
+ pred += this.amb[row.ambIdx].lambda * this.x[3 + row.ambIdx];
10531
+ h[3 + row.ambIdx] = this.amb[row.ambIdx].lambda;
10532
+ }
10533
+ return { pred, h };
10534
+ };
10535
+ const dropPrns = /* @__PURE__ */ new Set();
10536
+ const codeInn = /* @__PURE__ */ new Map();
10537
+ for (const row of rows) {
10538
+ if (row.kind !== "code") continue;
10539
+ const v = row.z - predict(row, this.x).pred;
10540
+ codeInn.set(row.g.prn, v);
10541
+ if (Math.abs(v) > o.codeGateM) dropPrns.add(row.g.prn);
10542
+ }
10543
+ for (const row of rows) {
10544
+ if (row.kind !== "phase" || dropPrns.has(row.g.prn)) continue;
10545
+ const v = row.z - predict(row, this.x).pred;
10546
+ const vc = codeInn.get(row.g.prn) ?? 0;
10547
+ if (Math.abs(v - vc) > o.slipGateM) {
10548
+ const zP = row.g.prR - row.g.prB - (row.ref.prR - row.ref.prB);
10549
+ const lam = this.amb[row.ambIdx].lambda;
10550
+ this.x[3 + row.ambIdx] = (row.z - zP) / lam;
10551
+ const s = 3 + row.ambIdx;
10552
+ for (let j = 0; j < this.x.length; j++) {
10553
+ this.P[s][j] = 0;
10554
+ this.P[j][s] = 0;
10555
+ }
10556
+ this.P[s][s] = o.ambInitSigmaCycles * o.ambInitSigmaCycles;
10557
+ }
10558
+ }
10559
+ if (dropPrns.size) rows = rows.filter((r) => !dropPrns.has(r.g.prn));
10560
+ if (rows.length < 3) {
10561
+ this.lastMs = timeMs;
10562
+ return null;
10563
+ }
10564
+ const n = this.x.length;
10565
+ const R = ddCovariance(rows, o.codeSigmaM, o.phaseSigmaM);
10566
+ const xPrior = [...this.x];
10567
+ const PPrior = this.P;
10568
+ let xi = [...this.x];
10569
+ let K = [];
10570
+ let H2 = [];
10571
+ for (let it = 0; it < Math.max(o.updateIterations, 1); it++) {
10572
+ H2 = [];
10573
+ const v = [];
10574
+ for (const row of rows) {
10575
+ const { pred, h } = predict(row, xi);
10576
+ let corr = 0;
10577
+ for (let j = 0; j < n; j++) corr += h[j] * (xPrior[j] - xi[j]);
10578
+ v.push(row.z - pred - corr);
10579
+ H2.push(h);
10580
+ }
10581
+ const Ht = transpose(H2);
10582
+ const S = matMul(matMul(H2, PPrior), Ht);
10583
+ for (let i = 0; i < rows.length; i++)
10584
+ for (let j = 0; j < rows.length; j++) S[i][j] += R[i][j];
10585
+ const Sinv = matInv(S);
10586
+ if (!Sinv) {
10587
+ this.lastMs = timeMs;
10588
+ return null;
10589
+ }
10590
+ K = matMul(matMul(PPrior, Ht), Sinv);
10591
+ const dx = matMul(
10592
+ K,
10593
+ v.map((s) => [s])
10594
+ );
10595
+ xi = xPrior.map((s, i) => s + dx[i][0]);
10596
+ }
10597
+ this.x = xi;
10598
+ const KH = matMul(K, H2);
10599
+ const IKH = zeros(n, n);
10600
+ for (let i = 0; i < n; i++)
10601
+ for (let j = 0; j < n; j++) IKH[i][j] = (i === j ? 1 : 0) - KH[i][j];
10602
+ const Pnew = matMul(IKH, PPrior);
10603
+ for (let i = 0; i < n; i++)
10604
+ for (let j = i; j < n; j++) {
10605
+ const s = (Pnew[i][j] + Pnew[j][i]) / 2;
10606
+ Pnew[i][j] = s;
10607
+ Pnew[j][i] = s;
10608
+ }
10609
+ this.P = Pnew;
10610
+ let status = rows.some(
10611
+ (r) => r.kind === "phase"
10612
+ ) ? "float" : "dgnss";
10613
+ let ratio;
10614
+ let nFixed;
10615
+ let fixedPos = null;
10616
+ let fixedAmbiguities;
10617
+ if (o.ambiguityResolution === "instant") {
10618
+ const cands = [];
10619
+ for (const row of rows) {
10620
+ if (row.kind !== "phase") continue;
10621
+ if (row.g.prn[0] === "R" && !o.glonassAr) continue;
10622
+ if (row.g.elB < o.arElevationMaskDeg * Math.PI / 180) continue;
10623
+ cands.push({ idx: row.ambIdx, el: row.g.elB });
10624
+ }
10625
+ cands.sort((a, b) => b.el - a.el);
10626
+ const floor = Math.max(
10627
+ o.minFixAmbiguities,
10628
+ Math.ceil(cands.length / 2),
10629
+ 1
10630
+ );
10631
+ while (cands.length >= floor) {
10632
+ const fix = this.tryFix(cands.map((c) => c.idx));
10633
+ if (!fix) break;
10634
+ const capped = Math.min(fix.ratio, 999.9);
10635
+ ratio ??= capped;
10636
+ if (fix.ratio >= o.ratioThreshold) {
10637
+ ratio = capped;
10638
+ status = "fixed";
10639
+ nFixed = cands.length;
10640
+ fixedPos = fix.position;
10641
+ fixedAmbiguities = {};
10642
+ cands.forEach((c, i) => {
10643
+ fixedAmbiguities[this.amb[c.idx].prn] = fix.intAmb[i];
10644
+ });
10645
+ break;
10646
+ }
10647
+ if (!o.partialFixing) break;
10648
+ cands.pop();
10649
+ }
10650
+ }
10651
+ for (const g of geom) {
10652
+ if (g.cpR === null || g.cpB === null) continue;
10653
+ this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
10654
+ }
10655
+ this.lastMs = timeMs;
10656
+ const ambiguities = {};
10657
+ for (let k = 0; k < this.amb.length; k++)
10658
+ ambiguities[this.amb[k].prn] = this.x[3 + k];
10659
+ const refSatellites = {};
10660
+ for (const [group, ref] of newRefs) refSatellites[group] = ref.prn;
10661
+ const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
10662
+ return {
10663
+ timeMs,
10664
+ position: fixedPos ?? [this.x[0], this.x[1], this.x[2]],
10665
+ floatBaseline: [
10666
+ this.x[0] - this.basePos[0],
10667
+ this.x[1] - this.basePos[1],
10668
+ this.x[2] - this.basePos[2]
10669
+ ],
10670
+ status,
10671
+ nSats,
10672
+ ratio,
10673
+ nFixed,
10674
+ fixedBaseline: fixedPos ? [
10675
+ fixedPos[0] - this.basePos[0],
10676
+ fixedPos[1] - this.basePos[1],
10677
+ fixedPos[2] - this.basePos[2]
10678
+ ] : void 0,
10679
+ fixedAmbiguities,
10680
+ sigmas: [
10681
+ Math.sqrt(Math.max(this.P[0][0], 0)),
10682
+ Math.sqrt(Math.max(this.P[1][1], 0)),
10683
+ Math.sqrt(Math.max(this.P[2][2], 0))
10684
+ ],
10685
+ ambiguities,
10686
+ refSatellites
10687
+ };
10688
+ }
10689
+ };
10690
+
9681
10691
  // src/positioning/index.ts
9682
10692
  var GM_GPS3 = 3986005e8;
9683
10693
  var F_REL = -4442807633e-19;
@@ -9714,11 +10724,11 @@ var PRIMARY_FREQ_HZ = {
9714
10724
  R: 1602e6
9715
10725
  };
9716
10726
  var F_L1 = 157542e4;
9717
- function tropoDelay(elevationRad) {
10727
+ function tropoDelay2(elevationRad) {
9718
10728
  const sinEl = Math.sin(elevationRad);
9719
10729
  return 2.47 / (sinEl + 0.0121);
9720
10730
  }
9721
- function sagnac(pos, travelTimeS) {
10731
+ function sagnac2(pos, travelTimeS) {
9722
10732
  const theta = OMEGA_E * travelTimeS;
9723
10733
  const c = Math.cos(theta);
9724
10734
  const s = Math.sin(theta);
@@ -9789,7 +10799,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
9789
10799
  const isKepler3 = eph.system !== "R" && eph.system !== "S";
9790
10800
  const dts = dtsClock - (tgd && isKepler3 ? eph.tgd : 0);
9791
10801
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
9792
- const [sx, sy, sz] = sagnac(sat, travel);
10802
+ const [sx, sy, sz] = sagnac2(sat, travel);
9793
10803
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
9794
10804
  const ux = (x2 - sx) / rho;
9795
10805
  const uy = (y2 - sy) / rho;
@@ -9806,7 +10816,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
9806
10816
  const sinEl = Math.max(Math.sin(elev), 0.1);
9807
10817
  weight = sinEl * sinEl;
9808
10818
  }
9809
- const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
10819
+ const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
9810
10820
  let ionoM = 0;
9811
10821
  if (iono && positionSane) {
9812
10822
  const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
@@ -12212,6 +13222,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12212
13222
  RINEX_CODES,
12213
13223
  RTCM3_MESSAGE_NAMES,
12214
13224
  Rtcm3Decoder,
13225
+ RtkFloatEngine,
12215
13226
  SECONDS_IN_DAY,
12216
13227
  SECONDS_IN_HOUR,
12217
13228
  SECONDS_IN_MINUTE,
@@ -12341,6 +13352,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12341
13352
  ionoFree,
12342
13353
  keplerPosition,
12343
13354
  klobucharDelay,
13355
+ lambdaReduction,
13356
+ lambdaSearch,
12344
13357
  maskRadForAzimuth,
12345
13358
  msmEpochToDate,
12346
13359
  navTimesFromEph,
@@ -12390,11 +13403,13 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12390
13403
  selectEphemeris,
12391
13404
  setGloFreqNumber,
12392
13405
  setRtcm3DebugHandler,
13406
+ solveDgnss,
12393
13407
  solveSpp,
12394
13408
  sp3Position,
12395
13409
  stationHeaderLines,
12396
13410
  systemCmp,
12397
13411
  systemName,
13412
+ toRtkEpoch,
12398
13413
  transformFrame,
12399
13414
  ubxFrames,
12400
13415
  updateStationMeta,