gnss-js 1.22.0 → 1.23.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,
@@ -260,11 +261,13 @@ __export(src_exports, {
260
261
  selectEphemeris: () => selectEphemeris,
261
262
  setGloFreqNumber: () => setGloFreqNumber,
262
263
  setRtcm3DebugHandler: () => setRtcm3DebugHandler,
264
+ solveDgnss: () => solveDgnss,
263
265
  solveSpp: () => solveSpp,
264
266
  sp3Position: () => sp3Position,
265
267
  stationHeaderLines: () => stationHeaderLines,
266
268
  systemCmp: () => systemCmp,
267
269
  systemName: () => systemName,
270
+ toRtkEpoch: () => toRtkEpoch,
268
271
  transformFrame: () => transformFrame,
269
272
  ubxFrames: () => ubxFrames,
270
273
  updateStationMeta: () => updateStationMeta,
@@ -8851,8 +8854,7 @@ var GM_GPS2 = 3986005e8;
8851
8854
  var GM_GAL2 = 3986004418e5;
8852
8855
  var GM_BDS2 = 3986004418e5;
8853
8856
  var GM_GLO = 39860044e7;
8854
- var BDS_GEO_MAX_INCLINATION_RAD = 0.1;
8855
- var BDS_GEO_MIN_SEMIMAJOR_AXIS_M = 4e7;
8857
+ var BDS_GEO_PRNS2 = /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 59, 60, 61, 62, 63]);
8856
8858
  var AE_GLO = 6378136;
8857
8859
  var J2_GLO = 108263e-8;
8858
8860
  var TWO_PI = 2 * Math.PI;
@@ -8899,7 +8901,7 @@ function keplerPosition(eph, t) {
8899
8901
  const yp = rk * Math.sin(uk);
8900
8902
  const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
8901
8903
  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;
8904
+ const isBdsGeo = eph.system === "C" && BDS_GEO_PRNS2.has(Number(eph.prn.slice(1)));
8903
8905
  if (isBdsGeo) {
8904
8906
  const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
8905
8907
  const cosO2 = Math.cos(omegak2);
@@ -9678,6 +9680,706 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
9678
9680
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
9679
9681
  }
9680
9682
 
9683
+ // src/positioning/rtk.ts
9684
+ var L1_CODES = {
9685
+ G: ["1C"],
9686
+ E: ["1C"],
9687
+ R: ["1C"],
9688
+ J: ["1C"],
9689
+ C: ["2I", "1P"]
9690
+ // B1I first (classic B1), then B1C
9691
+ };
9692
+ function toRtkEpoch(meas) {
9693
+ const out = /* @__PURE__ */ new Map();
9694
+ const rank = /* @__PURE__ */ new Map();
9695
+ for (const m of meas) {
9696
+ const prefs = L1_CODES[m.prn[0]];
9697
+ if (!prefs || m.pr === null || !Number.isFinite(m.pr)) continue;
9698
+ const r = prefs.indexOf(m.code);
9699
+ if (r < 0) continue;
9700
+ const prev = rank.get(m.prn);
9701
+ if (prev !== void 0 && prev <= r) continue;
9702
+ rank.set(m.prn, r);
9703
+ out.set(m.prn, {
9704
+ code: m.code,
9705
+ pr: m.pr,
9706
+ cp: m.cp ?? null,
9707
+ lockTimeMs: m.lockTimeS !== void 0 ? Math.round(m.lockTimeS * 1e3) : void 0,
9708
+ gloChannel: m.gloChannel ?? null
9709
+ });
9710
+ }
9711
+ return out;
9712
+ }
9713
+ function carrierFreqHz(sys, code, gloChannel) {
9714
+ const band = code[0];
9715
+ switch (sys) {
9716
+ case "G":
9717
+ case "J":
9718
+ return band === "1" ? 157542e4 : band === "2" ? 12276e5 : 117645e4;
9719
+ case "E":
9720
+ return band === "1" ? 157542e4 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : band === "8" ? 1191795e3 : 127875e4;
9721
+ case "C":
9722
+ return band === "1" ? 157542e4 : band === "2" ? 1561098e3 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : 126852e4;
9723
+ case "R": {
9724
+ if (gloChannel === null) return 0;
9725
+ if (band === "1") return 1602e6 + gloChannel * 562500;
9726
+ if (band === "2") return 1246e6 + gloChannel * 437500;
9727
+ return 1202025e3;
9728
+ }
9729
+ default:
9730
+ return 0;
9731
+ }
9732
+ }
9733
+ function sagnac(pos, travelTimeS) {
9734
+ const theta = OMEGA_E * travelTimeS;
9735
+ const c = Math.cos(theta);
9736
+ const s = Math.sin(theta);
9737
+ return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
9738
+ }
9739
+ function tropoDelay(elevationRad) {
9740
+ const sinEl = Math.sin(elevationRad);
9741
+ return 2.47 / (sinEl + 0.0121);
9742
+ }
9743
+ function resolveEphemeris(src, prn, timeMs) {
9744
+ if (Array.isArray(src))
9745
+ return selectEphemeris(src, prn, timeMs);
9746
+ return src.get(prn) ?? null;
9747
+ }
9748
+ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere) {
9749
+ const out = [];
9750
+ for (const [prn, mR] of rover) {
9751
+ const sys = prn[0];
9752
+ if (!"GERCJ".includes(sys)) continue;
9753
+ const mB = base.get(prn);
9754
+ if (!mB || mB.code !== mR.code) continue;
9755
+ if (mR.pr === null || mB.pr === null || !Number.isFinite(mR.pr) || !Number.isFinite(mB.pr))
9756
+ continue;
9757
+ const eph = resolveEphemeris(ephemerides, prn, timeMs);
9758
+ if (!eph) continue;
9759
+ const satAt = (pr) => {
9760
+ const tTx = timeMs - pr / C_LIGHT * 1e3;
9761
+ const dts = satClockCorrection(eph, tTx);
9762
+ const sat = computeSatPosition(eph, tTx - dts * 1e3);
9763
+ return Number.isFinite(sat.x) ? sat : null;
9764
+ };
9765
+ const satB = satAt(mB.pr);
9766
+ const satR = satAt(mR.pr);
9767
+ if (!satB || !satR) continue;
9768
+ const travelB = Math.hypot(
9769
+ satB.x - basePos[0],
9770
+ satB.y - basePos[1],
9771
+ satB.z - basePos[2]
9772
+ ) / C_LIGHT;
9773
+ const [bx, by, bz] = sagnac(satB, travelB);
9774
+ const rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
9775
+ const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
9776
+ if (elB < maskRad) continue;
9777
+ const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
9778
+ const freq = carrierFreqHz(sys, mR.code, gloK);
9779
+ out.push({
9780
+ prn,
9781
+ group: sys + mR.code,
9782
+ lambda: freq > 0 ? C_LIGHT / freq : 0,
9783
+ satR,
9784
+ rhoB,
9785
+ elB,
9786
+ tropoB: troposphere ? tropoDelay(elB) : 0,
9787
+ prR: mR.pr,
9788
+ prB: mB.pr,
9789
+ cpR: mR.cp ?? null,
9790
+ cpB: mB.cp ?? null,
9791
+ lockR: mR.lockTimeMs,
9792
+ lockB: mB.lockTimeMs
9793
+ });
9794
+ }
9795
+ return out;
9796
+ }
9797
+ function roverTerms(g, x, y, z, troposphere) {
9798
+ const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
9799
+ const [sx, sy, sz] = sagnac(g.satR, travel);
9800
+ const rho = Math.hypot(sx - x, sy - y, sz - z);
9801
+ const u = [
9802
+ (x - sx) / rho,
9803
+ (y - sy) / rho,
9804
+ (z - sz) / rho
9805
+ ];
9806
+ let dTropo = 0;
9807
+ if (troposphere) {
9808
+ const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
9809
+ dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
9810
+ }
9811
+ return { rho, u, dTropo };
9812
+ }
9813
+ function sdVariance(sigmaM, elRad) {
9814
+ const sinEl = Math.max(Math.sin(elRad), 0.05);
9815
+ return 2 * sigmaM * sigmaM * (1 + 1 / (sinEl * sinEl));
9816
+ }
9817
+ function zeros(n, m) {
9818
+ return Array.from({ length: n }, () => new Array(m).fill(0));
9819
+ }
9820
+ function matInv(A) {
9821
+ const n = A.length;
9822
+ const M = A.map((row, i) => {
9823
+ const r = [...row, ...new Array(n).fill(0)];
9824
+ r[n + i] = 1;
9825
+ return r;
9826
+ });
9827
+ for (let col = 0; col < n; col++) {
9828
+ let pivot = col;
9829
+ for (let r = col + 1; r < n; r++) {
9830
+ if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
9831
+ }
9832
+ if (Math.abs(M[pivot][col]) < 1e-13) return null;
9833
+ [M[col], M[pivot]] = [M[pivot], M[col]];
9834
+ const d = M[col][col];
9835
+ for (let c = 0; c < 2 * n; c++) M[col][c] /= d;
9836
+ for (let r = 0; r < n; r++) {
9837
+ if (r === col) continue;
9838
+ const f = M[r][col];
9839
+ if (f === 0) continue;
9840
+ for (let c = 0; c < 2 * n; c++) M[r][c] -= f * M[col][c];
9841
+ }
9842
+ }
9843
+ return M.map((row) => row.slice(n));
9844
+ }
9845
+ function matMul(A, B) {
9846
+ const n = A.length;
9847
+ const k = B.length;
9848
+ const m = B[0]?.length ?? 0;
9849
+ const C = zeros(n, m);
9850
+ for (let i = 0; i < n; i++) {
9851
+ for (let l = 0; l < k; l++) {
9852
+ const a = A[i][l];
9853
+ if (a === 0) continue;
9854
+ for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
9855
+ }
9856
+ }
9857
+ return C;
9858
+ }
9859
+ function transpose(A) {
9860
+ const n = A.length;
9861
+ const m = A[0]?.length ?? 0;
9862
+ const T = zeros(m, n);
9863
+ for (let i = 0; i < n; i++) for (let j = 0; j < m; j++) T[j][i] = A[i][j];
9864
+ return T;
9865
+ }
9866
+ function groupEntries(geom) {
9867
+ const groups = /* @__PURE__ */ new Map();
9868
+ for (const g of geom) {
9869
+ const list = groups.get(g.group);
9870
+ if (list) list.push(g);
9871
+ else groups.set(g.group, [g]);
9872
+ }
9873
+ return groups;
9874
+ }
9875
+ function ddCovariance(rows, sigmaCode, sigmaPhase) {
9876
+ const n = rows.length;
9877
+ const R = zeros(n, n);
9878
+ const sd2 = (r, g) => sdVariance(r.kind === "code" ? sigmaCode : sigmaPhase, g.elB);
9879
+ for (let i = 0; i < n; i++) {
9880
+ const ri = rows[i];
9881
+ R[i][i] = sd2(ri, ri.g) + sd2(ri, ri.ref);
9882
+ for (let j = i + 1; j < n; j++) {
9883
+ const rj = rows[j];
9884
+ if (ri.ref.prn === rj.ref.prn && ri.kind === rj.kind && ri.g.group === rj.g.group) {
9885
+ R[i][j] = R[j][i] = sd2(ri, ri.ref);
9886
+ }
9887
+ }
9888
+ }
9889
+ return R;
9890
+ }
9891
+ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
9892
+ const {
9893
+ elevationMaskDeg = 10,
9894
+ troposphere = true,
9895
+ maxIterations = 15,
9896
+ convergenceM = 1e-4,
9897
+ codeSigmaM = 0.3,
9898
+ rejectThresholdM = 20,
9899
+ initialPosition
9900
+ } = opts;
9901
+ let geom = buildGeometry(
9902
+ rover,
9903
+ base,
9904
+ basePos,
9905
+ ephemerides,
9906
+ timeMs,
9907
+ elevationMaskDeg * Math.PI / 180,
9908
+ troposphere
9909
+ );
9910
+ const rejected = [];
9911
+ for (; ; ) {
9912
+ const groups = groupEntries(geom);
9913
+ const rows = [];
9914
+ const refSatellites = {};
9915
+ for (const [group, list] of groups) {
9916
+ if (list.length < 2) continue;
9917
+ const ref = list.reduce((a, b) => b.elB > a.elB ? b : a);
9918
+ refSatellites[group] = ref.prn;
9919
+ for (const g of list) {
9920
+ if (g === ref) continue;
9921
+ rows.push({
9922
+ g,
9923
+ ref,
9924
+ kind: "code",
9925
+ z: g.prR - g.prB - (ref.prR - ref.prB)
9926
+ });
9927
+ }
9928
+ }
9929
+ if (rows.length < 3) return null;
9930
+ const W = matInv(ddCovariance(rows, codeSigmaM, codeSigmaM));
9931
+ if (!W) return null;
9932
+ let [x, y, z] = initialPosition ?? basePos;
9933
+ let iterations = 0;
9934
+ let converged = false;
9935
+ const residuals = {};
9936
+ let cov = null;
9937
+ for (let iter = 0; iter < maxIterations; iter++) {
9938
+ iterations = iter + 1;
9939
+ const H2 = [];
9940
+ const v = [];
9941
+ const terms = /* @__PURE__ */ new Map();
9942
+ const termOf = (g) => {
9943
+ let t = terms.get(g.prn);
9944
+ if (!t) {
9945
+ t = roverTerms(g, x, y, z, troposphere);
9946
+ terms.set(g.prn, t);
9947
+ }
9948
+ return t;
9949
+ };
9950
+ for (const row of rows) {
9951
+ const ts = termOf(row.g);
9952
+ const tr = termOf(row.ref);
9953
+ const pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
9954
+ v.push(row.z - pred);
9955
+ H2.push([ts.u[0] - tr.u[0], ts.u[1] - tr.u[1], ts.u[2] - tr.u[2]]);
9956
+ }
9957
+ const Ht = transpose(H2);
9958
+ const HtW = matMul(Ht, W);
9959
+ const N = matMul(HtW, H2);
9960
+ const b = matMul(
9961
+ HtW,
9962
+ v.map((s) => [s])
9963
+ );
9964
+ const Ninv = matInv(N);
9965
+ if (!Ninv) return null;
9966
+ const dx = matMul(
9967
+ Ninv,
9968
+ b.map((r) => [r[0]])
9969
+ );
9970
+ x += dx[0][0];
9971
+ y += dx[1][0];
9972
+ z += dx[2][0];
9973
+ cov = Ninv;
9974
+ rows.forEach((row, i) => {
9975
+ residuals[row.g.prn] = v[i] - (H2[i][0] * dx[0][0] + H2[i][1] * dx[1][0] + H2[i][2] * dx[2][0]);
9976
+ });
9977
+ if (Math.hypot(dx[0][0], dx[1][0], dx[2][0]) < convergenceM) {
9978
+ converged = true;
9979
+ break;
9980
+ }
9981
+ }
9982
+ let worst = null;
9983
+ let worstAbs = rejectThresholdM;
9984
+ for (const [prn, r] of Object.entries(residuals)) {
9985
+ if (Math.abs(r) > worstAbs) {
9986
+ worstAbs = Math.abs(r);
9987
+ worst = prn;
9988
+ }
9989
+ }
9990
+ if (worst && geom.length > 4) {
9991
+ rejected.push(worst);
9992
+ geom = geom.filter((g) => g.prn !== worst);
9993
+ continue;
9994
+ }
9995
+ const usedSatellites = [
9996
+ ...new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn]))
9997
+ ].sort();
9998
+ return {
9999
+ position: [x, y, z],
10000
+ baseline: [x - basePos[0], y - basePos[1], z - basePos[2]],
10001
+ sigmas: cov ? [
10002
+ Math.sqrt(Math.max(cov[0][0], 0)),
10003
+ Math.sqrt(Math.max(cov[1][1], 0)),
10004
+ Math.sqrt(Math.max(cov[2][2], 0))
10005
+ ] : [0, 0, 0],
10006
+ usedSatellites,
10007
+ refSatellites,
10008
+ residuals,
10009
+ rejectedSatellites: rejected,
10010
+ nSats: usedSatellites.length,
10011
+ iterations,
10012
+ converged
10013
+ };
10014
+ }
10015
+ }
10016
+ var RtkFloatEngine = class {
10017
+ basePos;
10018
+ ephemerides;
10019
+ o;
10020
+ /** State vector [x, y, z, N₁…] and covariance; null before init. */
10021
+ x = null;
10022
+ P = [];
10023
+ amb = [];
10024
+ refs = /* @__PURE__ */ new Map();
10025
+ track = /* @__PURE__ */ new Map();
10026
+ lastMs = null;
10027
+ constructor(basePos, ephemerides, opts = {}) {
10028
+ this.basePos = basePos;
10029
+ this.ephemerides = ephemerides;
10030
+ this.o = {
10031
+ mode: opts.mode ?? "kinematic",
10032
+ elevationMaskDeg: opts.elevationMaskDeg ?? 10,
10033
+ troposphere: opts.troposphere ?? true,
10034
+ codeSigmaM: opts.codeSigmaM ?? 0.3,
10035
+ phaseSigmaM: opts.phaseSigmaM ?? 3e-3,
10036
+ processNoisePosM: opts.processNoisePosM ?? 0,
10037
+ kinematicSigmaM: opts.kinematicSigmaM ?? 30,
10038
+ ambProcessNoiseCycles: opts.ambProcessNoiseCycles ?? 1e-4,
10039
+ ambInitSigmaCycles: opts.ambInitSigmaCycles ?? 30,
10040
+ maxGapMs: opts.maxGapMs ?? 1e4,
10041
+ codeGateM: opts.codeGateM ?? 30,
10042
+ slipGateM: opts.slipGateM ?? 5,
10043
+ updateIterations: opts.updateIterations ?? 2
10044
+ };
10045
+ }
10046
+ /** Clear all filter state (position, ambiguities, lock history). */
10047
+ reset() {
10048
+ this.x = null;
10049
+ this.P = [];
10050
+ this.amb = [];
10051
+ this.refs.clear();
10052
+ this.track.clear();
10053
+ this.lastMs = null;
10054
+ }
10055
+ ambIndex(prn) {
10056
+ return this.amb.findIndex((a) => a.prn === prn);
10057
+ }
10058
+ /** Remove one ambiguity state (row/col) from x and P. */
10059
+ dropAmb(idx) {
10060
+ const s = 3 + idx;
10061
+ this.amb.splice(idx, 1);
10062
+ this.x.splice(s, 1);
10063
+ this.P.splice(s, 1);
10064
+ for (const row of this.P) row.splice(s, 1);
10065
+ }
10066
+ /** Append an ambiguity state with the given value and variance. */
10067
+ addAmb(entry, value, variance) {
10068
+ this.amb.push(entry);
10069
+ this.x.push(value);
10070
+ const n = this.x.length;
10071
+ for (const row of this.P) row.push(0);
10072
+ const newRow = new Array(n).fill(0);
10073
+ newRow[n - 1] = variance;
10074
+ this.P.push(newRow);
10075
+ }
10076
+ /**
10077
+ * Re-map a group's DD ambiguities from the old reference r to the
10078
+ * new reference r' (which must hold a state): N_i' = N_i − N_r' and
10079
+ * the old reference's slot becomes N_r = −N_r'. Exact linear
10080
+ * transform of state and covariance (P' = T P Tᵀ).
10081
+ */
10082
+ retarget(group, oldRef, newRefIdx) {
10083
+ const n = this.x.length;
10084
+ const j = 3 + newRefIdx;
10085
+ const T = zeros(n, n);
10086
+ for (let i = 0; i < n; i++) T[i][i] = 1;
10087
+ for (let k = 0; k < this.amb.length; k++) {
10088
+ if (this.amb[k].group !== group) continue;
10089
+ const s = 3 + k;
10090
+ if (s === j) T[s][s] = -1;
10091
+ else T[s][j] -= 1;
10092
+ }
10093
+ this.x = matMul(
10094
+ T,
10095
+ this.x.map((v) => [v])
10096
+ ).map((r) => r[0]);
10097
+ this.P = matMul(matMul(T, this.P), transpose(T));
10098
+ const entry = this.amb[newRefIdx];
10099
+ entry.prn = oldRef;
10100
+ }
10101
+ /**
10102
+ * Process one synchronized epoch (same nominal `timeMs` for both
10103
+ * receivers). Returns null until a first position can be
10104
+ * initialised (via an internal DGNSS solve) or when the epoch has
10105
+ * fewer than 3 usable DD rows.
10106
+ */
10107
+ process(rover, base, timeMs) {
10108
+ const o = this.o;
10109
+ const geom = buildGeometry(
10110
+ rover,
10111
+ base,
10112
+ this.basePos,
10113
+ this.ephemerides,
10114
+ timeMs,
10115
+ o.elevationMaskDeg * Math.PI / 180,
10116
+ o.troposphere
10117
+ );
10118
+ const byPrn = new Map(geom.map((g) => [g.prn, g]));
10119
+ const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
10120
+ if (this.x) {
10121
+ for (let i = this.amb.length - 1; i >= 0; i--) {
10122
+ const prn = this.amb[i].prn;
10123
+ const tr = this.track.get(prn);
10124
+ const seen = byPrn.get(prn);
10125
+ const hasPhase = seen && seen.cpR !== null && seen.cpB !== null;
10126
+ const staleMs = tr ? timeMs - tr.lastMs : Infinity;
10127
+ if (!hasPhase && staleMs > o.maxGapMs) this.dropAmb(i);
10128
+ }
10129
+ }
10130
+ const slipped = /* @__PURE__ */ new Set();
10131
+ for (const g of geom) {
10132
+ if (g.cpR === null || g.cpB === null) continue;
10133
+ const tr = this.track.get(g.prn);
10134
+ if (!tr) continue;
10135
+ if (timeMs - tr.lastMs > o.maxGapMs) slipped.add(g.prn);
10136
+ 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)
10137
+ slipped.add(g.prn);
10138
+ }
10139
+ if (!this.x) {
10140
+ const dg = solveDgnss(
10141
+ rover,
10142
+ base,
10143
+ this.basePos,
10144
+ this.ephemerides,
10145
+ timeMs,
10146
+ {
10147
+ elevationMaskDeg: o.elevationMaskDeg,
10148
+ troposphere: o.troposphere,
10149
+ codeSigmaM: o.codeSigmaM
10150
+ }
10151
+ );
10152
+ if (!dg) return null;
10153
+ this.x = [...dg.position];
10154
+ this.P = zeros(3, 3);
10155
+ for (let i = 0; i < 3; i++) {
10156
+ const s = Math.max(dg.sigmas[i], 1);
10157
+ this.P[i][i] = 25 * s * s;
10158
+ }
10159
+ this.amb = [];
10160
+ } else if (o.mode === "kinematic") {
10161
+ for (let i = 0; i < 3; i++) {
10162
+ for (let j = 0; j < this.x.length; j++) {
10163
+ this.P[i][j] = 0;
10164
+ this.P[j][i] = 0;
10165
+ }
10166
+ this.P[i][i] = o.kinematicSigmaM * o.kinematicSigmaM;
10167
+ }
10168
+ } else {
10169
+ const q = o.processNoisePosM * o.processNoisePosM * dtS;
10170
+ for (let i = 0; i < 3; i++) this.P[i][i] += q;
10171
+ }
10172
+ const qAmb = o.ambProcessNoiseCycles * o.ambProcessNoiseCycles * dtS;
10173
+ for (let k = 0; k < this.amb.length; k++) this.P[3 + k][3 + k] += qAmb;
10174
+ const groups = groupEntries(geom);
10175
+ const newRefs = /* @__PURE__ */ new Map();
10176
+ for (const [group, list] of groups) {
10177
+ if (list.length < 2) continue;
10178
+ const phase = list.filter(
10179
+ (g) => g.cpR !== null && g.cpB !== null && !slipped.has(g.prn)
10180
+ );
10181
+ const pool = phase.length >= 2 ? phase : list;
10182
+ const prevRef = this.refs.get(group);
10183
+ const best = pool.reduce((a, b) => b.elB > a.elB ? b : a);
10184
+ let ref = best;
10185
+ if (best.prn !== prevRef) {
10186
+ const anyState = this.amb.some((a) => a.group === group);
10187
+ if (anyState) {
10188
+ if (this.ambIndex(best.prn) >= 0) {
10189
+ this.retarget(group, prevRef ?? best.prn, this.ambIndex(best.prn));
10190
+ } else if (prevRef && pool.some((g) => g.prn === prevRef)) {
10191
+ ref = pool.find((g) => g.prn === prevRef);
10192
+ } else {
10193
+ for (let i = this.amb.length - 1; i >= 0; i--)
10194
+ if (this.amb[i].group === group) this.dropAmb(i);
10195
+ }
10196
+ }
10197
+ }
10198
+ newRefs.set(group, ref);
10199
+ this.refs.set(group, ref.prn);
10200
+ }
10201
+ for (const group of [...this.refs.keys()])
10202
+ if (!newRefs.has(group)) this.refs.delete(group);
10203
+ for (const prn of slipped) {
10204
+ const i = this.ambIndex(prn);
10205
+ if (i >= 0) this.dropAmb(i);
10206
+ }
10207
+ const initAmb = (g, ref) => {
10208
+ if (g.lambda <= 0 || ref.lambda <= 0) return;
10209
+ const zPhi = g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB);
10210
+ const zP = g.prR - g.prB - (ref.prR - ref.prB);
10211
+ const n0 = (zPhi - zP) / g.lambda;
10212
+ this.addAmb(
10213
+ { prn: g.prn, group: g.group, lambda: g.lambda },
10214
+ n0,
10215
+ o.ambInitSigmaCycles * o.ambInitSigmaCycles
10216
+ );
10217
+ };
10218
+ for (const [group, ref] of newRefs) {
10219
+ for (const g of groups.get(group)) {
10220
+ if (g === ref || g.cpR === null || g.cpB === null) continue;
10221
+ if (ref.cpR === null || ref.cpB === null) continue;
10222
+ const i = this.ambIndex(g.prn);
10223
+ if (i >= 0) {
10224
+ this.amb[i].lambda = g.lambda;
10225
+ this.amb[i].group = g.group;
10226
+ continue;
10227
+ }
10228
+ initAmb(g, ref);
10229
+ }
10230
+ }
10231
+ for (let i = this.amb.length - 1; i >= 0; i--) {
10232
+ const a = this.amb[i];
10233
+ if (newRefs.get(a.group)?.prn === a.prn) this.dropAmb(i);
10234
+ }
10235
+ let rows = [];
10236
+ for (const [group, ref] of newRefs) {
10237
+ for (const g of groups.get(group)) {
10238
+ if (g === ref) continue;
10239
+ rows.push({
10240
+ g,
10241
+ ref,
10242
+ kind: "code",
10243
+ z: g.prR - g.prB - (ref.prR - ref.prB),
10244
+ ambIdx: -1
10245
+ });
10246
+ const i = this.ambIndex(g.prn);
10247
+ if (i >= 0 && g.cpR !== null && g.cpB !== null && ref.cpR !== null && ref.cpB !== null && g.lambda > 0 && ref.lambda > 0) {
10248
+ rows.push({
10249
+ g,
10250
+ ref,
10251
+ kind: "phase",
10252
+ z: g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB),
10253
+ ambIdx: i
10254
+ });
10255
+ }
10256
+ }
10257
+ }
10258
+ if (rows.length < 3) {
10259
+ this.lastMs = timeMs;
10260
+ return null;
10261
+ }
10262
+ const predict = (row, xs) => {
10263
+ const ts = roverTerms(row.g, xs[0], xs[1], xs[2], o.troposphere);
10264
+ const tr = roverTerms(row.ref, xs[0], xs[1], xs[2], o.troposphere);
10265
+ let pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
10266
+ const h = new Array(this.x.length).fill(0);
10267
+ h[0] = ts.u[0] - tr.u[0];
10268
+ h[1] = ts.u[1] - tr.u[1];
10269
+ h[2] = ts.u[2] - tr.u[2];
10270
+ if (row.ambIdx >= 0) {
10271
+ pred += this.amb[row.ambIdx].lambda * this.x[3 + row.ambIdx];
10272
+ h[3 + row.ambIdx] = this.amb[row.ambIdx].lambda;
10273
+ }
10274
+ return { pred, h };
10275
+ };
10276
+ const dropPrns = /* @__PURE__ */ new Set();
10277
+ const codeInn = /* @__PURE__ */ new Map();
10278
+ for (const row of rows) {
10279
+ if (row.kind !== "code") continue;
10280
+ const v = row.z - predict(row, this.x).pred;
10281
+ codeInn.set(row.g.prn, v);
10282
+ if (Math.abs(v) > o.codeGateM) dropPrns.add(row.g.prn);
10283
+ }
10284
+ for (const row of rows) {
10285
+ if (row.kind !== "phase" || dropPrns.has(row.g.prn)) continue;
10286
+ const v = row.z - predict(row, this.x).pred;
10287
+ const vc = codeInn.get(row.g.prn) ?? 0;
10288
+ if (Math.abs(v - vc) > o.slipGateM) {
10289
+ const zP = row.g.prR - row.g.prB - (row.ref.prR - row.ref.prB);
10290
+ const lam = this.amb[row.ambIdx].lambda;
10291
+ this.x[3 + row.ambIdx] = (row.z - zP) / lam;
10292
+ const s = 3 + row.ambIdx;
10293
+ for (let j = 0; j < this.x.length; j++) {
10294
+ this.P[s][j] = 0;
10295
+ this.P[j][s] = 0;
10296
+ }
10297
+ this.P[s][s] = o.ambInitSigmaCycles * o.ambInitSigmaCycles;
10298
+ }
10299
+ }
10300
+ if (dropPrns.size) rows = rows.filter((r) => !dropPrns.has(r.g.prn));
10301
+ if (rows.length < 3) {
10302
+ this.lastMs = timeMs;
10303
+ return null;
10304
+ }
10305
+ const n = this.x.length;
10306
+ const R = ddCovariance(rows, o.codeSigmaM, o.phaseSigmaM);
10307
+ const xPrior = [...this.x];
10308
+ const PPrior = this.P;
10309
+ let xi = [...this.x];
10310
+ let K = [];
10311
+ let H2 = [];
10312
+ for (let it = 0; it < Math.max(o.updateIterations, 1); it++) {
10313
+ H2 = [];
10314
+ const v = [];
10315
+ for (const row of rows) {
10316
+ const { pred, h } = predict(row, xi);
10317
+ let corr = 0;
10318
+ for (let j = 0; j < n; j++) corr += h[j] * (xPrior[j] - xi[j]);
10319
+ v.push(row.z - pred - corr);
10320
+ H2.push(h);
10321
+ }
10322
+ const Ht = transpose(H2);
10323
+ const S = matMul(matMul(H2, PPrior), Ht);
10324
+ for (let i = 0; i < rows.length; i++)
10325
+ for (let j = 0; j < rows.length; j++) S[i][j] += R[i][j];
10326
+ const Sinv = matInv(S);
10327
+ if (!Sinv) {
10328
+ this.lastMs = timeMs;
10329
+ return null;
10330
+ }
10331
+ K = matMul(matMul(PPrior, Ht), Sinv);
10332
+ const dx = matMul(
10333
+ K,
10334
+ v.map((s) => [s])
10335
+ );
10336
+ xi = xPrior.map((s, i) => s + dx[i][0]);
10337
+ }
10338
+ this.x = xi;
10339
+ const KH = matMul(K, H2);
10340
+ const IKH = zeros(n, n);
10341
+ for (let i = 0; i < n; i++)
10342
+ for (let j = 0; j < n; j++) IKH[i][j] = (i === j ? 1 : 0) - KH[i][j];
10343
+ const Pnew = matMul(IKH, PPrior);
10344
+ for (let i = 0; i < n; i++)
10345
+ for (let j = i; j < n; j++) {
10346
+ const s = (Pnew[i][j] + Pnew[j][i]) / 2;
10347
+ Pnew[i][j] = s;
10348
+ Pnew[j][i] = s;
10349
+ }
10350
+ this.P = Pnew;
10351
+ for (const g of geom) {
10352
+ if (g.cpR === null || g.cpB === null) continue;
10353
+ this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
10354
+ }
10355
+ this.lastMs = timeMs;
10356
+ const ambiguities = {};
10357
+ for (let k = 0; k < this.amb.length; k++)
10358
+ ambiguities[this.amb[k].prn] = this.x[3 + k];
10359
+ const refSatellites = {};
10360
+ for (const [group, ref] of newRefs) refSatellites[group] = ref.prn;
10361
+ const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
10362
+ return {
10363
+ timeMs,
10364
+ position: [this.x[0], this.x[1], this.x[2]],
10365
+ floatBaseline: [
10366
+ this.x[0] - this.basePos[0],
10367
+ this.x[1] - this.basePos[1],
10368
+ this.x[2] - this.basePos[2]
10369
+ ],
10370
+ nSats,
10371
+ ratio: void 0,
10372
+ sigmas: [
10373
+ Math.sqrt(Math.max(this.P[0][0], 0)),
10374
+ Math.sqrt(Math.max(this.P[1][1], 0)),
10375
+ Math.sqrt(Math.max(this.P[2][2], 0))
10376
+ ],
10377
+ ambiguities,
10378
+ refSatellites
10379
+ };
10380
+ }
10381
+ };
10382
+
9681
10383
  // src/positioning/index.ts
9682
10384
  var GM_GPS3 = 3986005e8;
9683
10385
  var F_REL = -4442807633e-19;
@@ -9714,11 +10416,11 @@ var PRIMARY_FREQ_HZ = {
9714
10416
  R: 1602e6
9715
10417
  };
9716
10418
  var F_L1 = 157542e4;
9717
- function tropoDelay(elevationRad) {
10419
+ function tropoDelay2(elevationRad) {
9718
10420
  const sinEl = Math.sin(elevationRad);
9719
10421
  return 2.47 / (sinEl + 0.0121);
9720
10422
  }
9721
- function sagnac(pos, travelTimeS) {
10423
+ function sagnac2(pos, travelTimeS) {
9722
10424
  const theta = OMEGA_E * travelTimeS;
9723
10425
  const c = Math.cos(theta);
9724
10426
  const s = Math.sin(theta);
@@ -9789,7 +10491,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
9789
10491
  const isKepler3 = eph.system !== "R" && eph.system !== "S";
9790
10492
  const dts = dtsClock - (tgd && isKepler3 ? eph.tgd : 0);
9791
10493
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
9792
- const [sx, sy, sz] = sagnac(sat, travel);
10494
+ const [sx, sy, sz] = sagnac2(sat, travel);
9793
10495
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
9794
10496
  const ux = (x2 - sx) / rho;
9795
10497
  const uy = (y2 - sy) / rho;
@@ -9806,7 +10508,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
9806
10508
  const sinEl = Math.max(Math.sin(elev), 0.1);
9807
10509
  weight = sinEl * sinEl;
9808
10510
  }
9809
- const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
10511
+ const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
9810
10512
  let ionoM = 0;
9811
10513
  if (iono && positionSane) {
9812
10514
  const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
@@ -12212,6 +12914,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12212
12914
  RINEX_CODES,
12213
12915
  RTCM3_MESSAGE_NAMES,
12214
12916
  Rtcm3Decoder,
12917
+ RtkFloatEngine,
12215
12918
  SECONDS_IN_DAY,
12216
12919
  SECONDS_IN_HOUR,
12217
12920
  SECONDS_IN_MINUTE,
@@ -12390,11 +13093,13 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
12390
13093
  selectEphemeris,
12391
13094
  setGloFreqNumber,
12392
13095
  setRtcm3DebugHandler,
13096
+ solveDgnss,
12393
13097
  solveSpp,
12394
13098
  sp3Position,
12395
13099
  stationHeaderLines,
12396
13100
  systemCmp,
12397
13101
  systemName,
13102
+ toRtkEpoch,
12398
13103
  transformFrame,
12399
13104
  ubxFrames,
12400
13105
  updateStationMeta,