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.
@@ -20,10 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/positioning/index.ts
21
21
  var positioning_exports = {};
22
22
  __export(positioning_exports, {
23
+ RtkFloatEngine: () => RtkFloatEngine,
23
24
  ionoFree: () => ionoFree,
24
25
  klobucharDelay: () => klobucharDelay,
26
+ lambdaReduction: () => lambdaReduction,
27
+ lambdaSearch: () => lambdaSearch,
25
28
  satClockCorrection: () => satClockCorrection,
26
- solveSpp: () => solveSpp
29
+ solveDgnss: () => solveDgnss,
30
+ solveSpp: () => solveSpp,
31
+ toRtkEpoch: () => toRtkEpoch
27
32
  });
28
33
  module.exports = __toCommonJS(positioning_exports);
29
34
 
@@ -155,8 +160,7 @@ var GM_GPS = 3986005e8;
155
160
  var GM_GAL = 3986004418e5;
156
161
  var GM_BDS = 3986004418e5;
157
162
  var GM_GLO = 39860044e7;
158
- var BDS_GEO_MAX_INCLINATION_RAD = 0.1;
159
- var BDS_GEO_MIN_SEMIMAJOR_AXIS_M = 4e7;
163
+ var BDS_GEO_PRNS = /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 59, 60, 61, 62, 63]);
160
164
  var AE_GLO = 6378136;
161
165
  var J2_GLO = 108263e-8;
162
166
  var TWO_PI = 2 * Math.PI;
@@ -203,7 +207,7 @@ function keplerPosition(eph, t) {
203
207
  const yp = rk * Math.sin(uk);
204
208
  const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
205
209
  const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
206
- const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
210
+ const isBdsGeo = eph.system === "C" && BDS_GEO_PRNS.has(Number(eph.prn.slice(1)));
207
211
  if (isBdsGeo) {
208
212
  const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
209
213
  const cosO2 = Math.cos(omegak2);
@@ -393,6 +397,12 @@ function invert4x4(m) {
393
397
  }
394
398
  return result;
395
399
  }
400
+ function selectEphemeris(ephemerides, prn, timeMs) {
401
+ return selectBest(
402
+ ephemerides.filter((e) => e.prn === prn),
403
+ timeMs
404
+ );
405
+ }
396
406
  function computeSatPosition(eph, timeMs) {
397
407
  if (eph.system === "R" || eph.system === "S") {
398
408
  const leapMs = getGpsLeap(new Date(timeMs)) * 1e3;
@@ -404,6 +414,20 @@ function computeSatPosition(eph, timeMs) {
404
414
  if (eph.system === "C") tow = (tow - 14 + 604800) % 604800;
405
415
  return keplerPosition(eph, tow);
406
416
  }
417
+ function selectBest(ephs, timeMs) {
418
+ let best = null;
419
+ let bestDt = Infinity;
420
+ for (const eph of ephs) {
421
+ const dt = Math.abs(timeMs - eph.tocDate.getTime());
422
+ const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
423
+ if (dt > maxAge) continue;
424
+ if (dt < bestDt) {
425
+ bestDt = dt;
426
+ best = eph;
427
+ }
428
+ }
429
+ return best;
430
+ }
407
431
  var GPS_EPOCH_MS = START_GPS_TIME.getTime();
408
432
  var MS_PER_WEEK = 7 * 864e5;
409
433
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
@@ -440,6 +464,1012 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
440
464
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
441
465
  }
442
466
 
467
+ // src/positioning/lambda.ts
468
+ var LOOPMAX = 1e4;
469
+ var ROUND = (x) => Math.floor(x + 0.5);
470
+ var SGN = (x) => x <= 0 ? -1 : 1;
471
+ function ldFactor(n, Q, L, D) {
472
+ const A = Float64Array.from(Q);
473
+ for (let i = n - 1; i >= 0; i--) {
474
+ D[i] = A[i + i * n];
475
+ if (D[i] <= 0) return false;
476
+ const a = Math.sqrt(D[i]);
477
+ for (let j = 0; j <= i; j++) L[i + j * n] = A[i + j * n] / a;
478
+ for (let j = 0; j <= i - 1; j++)
479
+ for (let k = 0; k <= j; k++) A[j + k * n] -= L[i + k * n] * L[i + j * n];
480
+ for (let j = 0; j <= i; j++) L[i + j * n] /= L[i + i * n];
481
+ }
482
+ return true;
483
+ }
484
+ function gauss(n, L, Z, i, j) {
485
+ const mu = ROUND(L[i + j * n]);
486
+ if (mu !== 0) {
487
+ for (let k = i; k < n; k++) L[k + n * j] -= mu * L[k + i * n];
488
+ for (let k = 0; k < n; k++) Z[k + n * j] -= mu * Z[k + i * n];
489
+ }
490
+ }
491
+ function perm(n, L, D, j, del, Z) {
492
+ const eta = D[j] / del;
493
+ const lam = D[j + 1] * L[j + 1 + j * n] / del;
494
+ D[j] = eta * D[j + 1];
495
+ D[j + 1] = del;
496
+ for (let k = 0; k <= j - 1; k++) {
497
+ const a0 = L[j + k * n];
498
+ const a1 = L[j + 1 + k * n];
499
+ L[j + k * n] = -L[j + 1 + j * n] * a0 + a1;
500
+ L[j + 1 + k * n] = eta * a0 + lam * a1;
501
+ }
502
+ L[j + 1 + j * n] = lam;
503
+ for (let k = j + 2; k < n; k++) {
504
+ const t = L[k + j * n];
505
+ L[k + j * n] = L[k + (j + 1) * n];
506
+ L[k + (j + 1) * n] = t;
507
+ }
508
+ for (let k = 0; k < n; k++) {
509
+ const t = Z[k + j * n];
510
+ Z[k + j * n] = Z[k + (j + 1) * n];
511
+ Z[k + (j + 1) * n] = t;
512
+ }
513
+ }
514
+ function reduction(n, L, D, Z) {
515
+ let j = n - 2;
516
+ let k = n - 2;
517
+ while (j >= 0) {
518
+ if (j <= k) for (let i = j + 1; i < n; i++) gauss(n, L, Z, i, j);
519
+ const del = D[j] + L[j + 1 + j * n] * L[j + 1 + j * n] * D[j + 1];
520
+ if (del + 1e-6 < D[j + 1]) {
521
+ perm(n, L, D, j, del, Z);
522
+ k = j;
523
+ j = n - 2;
524
+ } else j--;
525
+ }
526
+ }
527
+ function search(n, m, L, D, zs, zn, s) {
528
+ let nn = 0;
529
+ let imax = 0;
530
+ let maxdist = 1e99;
531
+ const S = new Float64Array(n * n);
532
+ const dist = new Float64Array(n);
533
+ const zb = new Float64Array(n);
534
+ const z = new Float64Array(n);
535
+ const step = new Float64Array(n);
536
+ let k = n - 1;
537
+ dist[k] = 0;
538
+ zb[k] = zs[k];
539
+ z[k] = ROUND(zb[k]);
540
+ let y = zb[k] - z[k];
541
+ step[k] = SGN(y);
542
+ let c = 0;
543
+ for (; c < LOOPMAX; c++) {
544
+ const newdist = dist[k] + y * y / D[k];
545
+ if (newdist < maxdist) {
546
+ if (k !== 0) {
547
+ dist[--k] = newdist;
548
+ for (let i = 0; i <= k; i++)
549
+ S[k + i * n] = S[k + 1 + i * n] + (z[k + 1] - zb[k + 1]) * L[k + 1 + i * n];
550
+ zb[k] = zs[k] + S[k + k * n];
551
+ z[k] = ROUND(zb[k]);
552
+ y = zb[k] - z[k];
553
+ step[k] = SGN(y);
554
+ } else {
555
+ if (nn < m) {
556
+ if (nn === 0 || newdist > s[imax]) imax = nn;
557
+ for (let i = 0; i < n; i++) zn[i + nn * n] = z[i];
558
+ s[nn++] = newdist;
559
+ } else {
560
+ if (newdist < s[imax]) {
561
+ for (let i = 0; i < n; i++) zn[i + imax * n] = z[i];
562
+ s[imax] = newdist;
563
+ imax = 0;
564
+ for (let i = 0; i < m; i++) if (s[imax] < s[i]) imax = i;
565
+ }
566
+ maxdist = s[imax];
567
+ }
568
+ z[0] += step[0];
569
+ y = zb[0] - z[0];
570
+ step[0] = -step[0] - SGN(step[0]);
571
+ }
572
+ } else {
573
+ if (k === n - 1) break;
574
+ k++;
575
+ z[k] += step[k];
576
+ y = zb[k] - z[k];
577
+ step[k] = -step[k] - SGN(step[k]);
578
+ }
579
+ }
580
+ for (let i = 0; i < m - 1; i++) {
581
+ for (let j = i + 1; j < m; j++) {
582
+ if (s[i] < s[j]) continue;
583
+ const t = s[i];
584
+ s[i] = s[j];
585
+ s[j] = t;
586
+ for (let l = 0; l < n; l++) {
587
+ const u = zn[l + i * n];
588
+ zn[l + i * n] = zn[l + j * n];
589
+ zn[l + j * n] = u;
590
+ }
591
+ }
592
+ }
593
+ return c < LOOPMAX;
594
+ }
595
+ function solveZt(n, m, Z, E) {
596
+ const w = n + m;
597
+ const M = new Float64Array(n * w);
598
+ for (let r = 0; r < n; r++) {
599
+ for (let cIdx = 0; cIdx < n; cIdx++) M[r * w + cIdx] = Z[cIdx + r * n];
600
+ for (let cIdx = 0; cIdx < m; cIdx++) M[r * w + n + cIdx] = E[r + cIdx * n];
601
+ }
602
+ for (let col = 0; col < n; col++) {
603
+ let piv = col;
604
+ for (let r = col + 1; r < n; r++)
605
+ if (Math.abs(M[r * w + col]) > Math.abs(M[piv * w + col])) piv = r;
606
+ if (Math.abs(M[piv * w + col]) < 1e-12) return null;
607
+ if (piv !== col) {
608
+ for (let cIdx = col; cIdx < w; cIdx++) {
609
+ const t = M[col * w + cIdx];
610
+ M[col * w + cIdx] = M[piv * w + cIdx];
611
+ M[piv * w + cIdx] = t;
612
+ }
613
+ }
614
+ const d = M[col * w + col];
615
+ for (let cIdx = col; cIdx < w; cIdx++) M[col * w + cIdx] /= d;
616
+ for (let r = 0; r < n; r++) {
617
+ if (r === col) continue;
618
+ const f = M[r * w + col];
619
+ if (f === 0) continue;
620
+ for (let cIdx = col; cIdx < w; cIdx++)
621
+ M[r * w + cIdx] -= f * M[col * w + cIdx];
622
+ }
623
+ }
624
+ const X = new Float64Array(n * m);
625
+ for (let r = 0; r < n; r++)
626
+ for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
627
+ return X;
628
+ }
629
+ function lambdaSearch(a, Q, n, m = 2) {
630
+ if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
631
+ const L = new Float64Array(n * n);
632
+ const D = new Float64Array(n);
633
+ if (!ldFactor(n, Q, L, D)) return null;
634
+ const Z = new Float64Array(n * n);
635
+ for (let i = 0; i < n; i++) Z[i + i * n] = 1;
636
+ reduction(n, L, D, Z);
637
+ const z = new Float64Array(n);
638
+ for (let j = 0; j < n; j++) {
639
+ let sum = 0;
640
+ for (let k = 0; k < n; k++) sum += Z[k + j * n] * a[k];
641
+ z[j] = sum;
642
+ }
643
+ const E = new Float64Array(n * m);
644
+ const s = new Float64Array(m);
645
+ if (!search(n, m, L, D, z, E, s)) return null;
646
+ const F = solveZt(n, m, Z, E);
647
+ if (!F) return null;
648
+ const candidates = [];
649
+ for (let c = 0; c < m; c++) {
650
+ const v = new Float64Array(n);
651
+ for (let i = 0; i < n; i++) v[i] = ROUND(F[i + c * n]);
652
+ candidates.push(v);
653
+ }
654
+ const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
655
+ return { candidates, residuals: s, ratio };
656
+ }
657
+ function lambdaReduction(Q, n) {
658
+ if (n <= 0 || Q.length < n * n) return null;
659
+ const L = new Float64Array(n * n);
660
+ const D = new Float64Array(n);
661
+ if (!ldFactor(n, Q, L, D)) return null;
662
+ const Z = new Float64Array(n * n);
663
+ for (let i = 0; i < n; i++) Z[i + i * n] = 1;
664
+ reduction(n, L, D, Z);
665
+ return Z;
666
+ }
667
+
668
+ // src/positioning/rtk.ts
669
+ var L1_CODES = {
670
+ G: ["1C"],
671
+ E: ["1C"],
672
+ R: ["1C"],
673
+ J: ["1C"],
674
+ C: ["2I", "1P"]
675
+ // B1I first (classic B1), then B1C
676
+ };
677
+ function toRtkEpoch(meas) {
678
+ const out = /* @__PURE__ */ new Map();
679
+ const rank = /* @__PURE__ */ new Map();
680
+ for (const m of meas) {
681
+ const prefs = L1_CODES[m.prn[0]];
682
+ if (!prefs || m.pr === null || !Number.isFinite(m.pr)) continue;
683
+ const r = prefs.indexOf(m.code);
684
+ if (r < 0) continue;
685
+ const prev = rank.get(m.prn);
686
+ if (prev !== void 0 && prev <= r) continue;
687
+ rank.set(m.prn, r);
688
+ out.set(m.prn, {
689
+ code: m.code,
690
+ pr: m.pr,
691
+ cp: m.cp ?? null,
692
+ lockTimeMs: m.lockTimeS !== void 0 ? Math.round(m.lockTimeS * 1e3) : void 0,
693
+ gloChannel: m.gloChannel ?? null
694
+ });
695
+ }
696
+ return out;
697
+ }
698
+ function carrierFreqHz(sys, code, gloChannel) {
699
+ const band = code[0];
700
+ switch (sys) {
701
+ case "G":
702
+ case "J":
703
+ return band === "1" ? 157542e4 : band === "2" ? 12276e5 : 117645e4;
704
+ case "E":
705
+ return band === "1" ? 157542e4 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : band === "8" ? 1191795e3 : 127875e4;
706
+ case "C":
707
+ return band === "1" ? 157542e4 : band === "2" ? 1561098e3 : band === "5" ? 117645e4 : band === "7" ? 120714e4 : 126852e4;
708
+ case "R": {
709
+ if (gloChannel === null) return 0;
710
+ if (band === "1") return 1602e6 + gloChannel * 562500;
711
+ if (band === "2") return 1246e6 + gloChannel * 437500;
712
+ return 1202025e3;
713
+ }
714
+ default:
715
+ return 0;
716
+ }
717
+ }
718
+ function sagnac(pos, travelTimeS) {
719
+ const theta = OMEGA_E * travelTimeS;
720
+ const c = Math.cos(theta);
721
+ const s = Math.sin(theta);
722
+ return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
723
+ }
724
+ function tropoDelay(elevationRad) {
725
+ const sinEl = Math.sin(elevationRad);
726
+ return 2.47 / (sinEl + 0.0121);
727
+ }
728
+ function resolveEphemeris(src, prn, timeMs) {
729
+ if (Array.isArray(src))
730
+ return selectEphemeris(src, prn, timeMs);
731
+ return src.get(prn) ?? null;
732
+ }
733
+ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere) {
734
+ const out = [];
735
+ for (const [prn, mR] of rover) {
736
+ const sys = prn[0];
737
+ if (!"GERCJ".includes(sys)) continue;
738
+ const mB = base.get(prn);
739
+ if (!mB || mB.code !== mR.code) continue;
740
+ if (mR.pr === null || mB.pr === null || !Number.isFinite(mR.pr) || !Number.isFinite(mB.pr))
741
+ continue;
742
+ const eph = resolveEphemeris(ephemerides, prn, timeMs);
743
+ if (!eph) continue;
744
+ const satAt = (pr) => {
745
+ const tTx = timeMs - pr / C_LIGHT * 1e3;
746
+ const dts = satClockCorrection(eph, tTx);
747
+ const sat = computeSatPosition(eph, tTx - dts * 1e3);
748
+ return Number.isFinite(sat.x) ? sat : null;
749
+ };
750
+ const satB = satAt(mB.pr);
751
+ const satR = satAt(mR.pr);
752
+ if (!satB || !satR) continue;
753
+ const travelB = Math.hypot(
754
+ satB.x - basePos[0],
755
+ satB.y - basePos[1],
756
+ satB.z - basePos[2]
757
+ ) / C_LIGHT;
758
+ const [bx, by, bz] = sagnac(satB, travelB);
759
+ const rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
760
+ const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
761
+ if (elB < maskRad) continue;
762
+ const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
763
+ const freq = carrierFreqHz(sys, mR.code, gloK);
764
+ out.push({
765
+ prn,
766
+ group: sys + mR.code,
767
+ lambda: freq > 0 ? C_LIGHT / freq : 0,
768
+ satR,
769
+ rhoB,
770
+ elB,
771
+ tropoB: troposphere ? tropoDelay(elB) : 0,
772
+ prR: mR.pr,
773
+ prB: mB.pr,
774
+ cpR: mR.cp ?? null,
775
+ cpB: mB.cp ?? null,
776
+ lockR: mR.lockTimeMs,
777
+ lockB: mB.lockTimeMs
778
+ });
779
+ }
780
+ return out;
781
+ }
782
+ function roverTerms(g, x, y, z, troposphere) {
783
+ const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
784
+ const [sx, sy, sz] = sagnac(g.satR, travel);
785
+ const rho = Math.hypot(sx - x, sy - y, sz - z);
786
+ const u = [
787
+ (x - sx) / rho,
788
+ (y - sy) / rho,
789
+ (z - sz) / rho
790
+ ];
791
+ let dTropo = 0;
792
+ if (troposphere) {
793
+ const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
794
+ dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
795
+ }
796
+ return { rho, u, dTropo };
797
+ }
798
+ function sdVariance(sigmaM, elRad) {
799
+ const sinEl = Math.max(Math.sin(elRad), 0.05);
800
+ return 2 * sigmaM * sigmaM * (1 + 1 / (sinEl * sinEl));
801
+ }
802
+ function zeros(n, m) {
803
+ return Array.from({ length: n }, () => new Array(m).fill(0));
804
+ }
805
+ function matInv(A) {
806
+ const n = A.length;
807
+ const M = A.map((row, i) => {
808
+ const r = [...row, ...new Array(n).fill(0)];
809
+ r[n + i] = 1;
810
+ return r;
811
+ });
812
+ for (let col = 0; col < n; col++) {
813
+ let pivot = col;
814
+ for (let r = col + 1; r < n; r++) {
815
+ if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
816
+ }
817
+ if (Math.abs(M[pivot][col]) < 1e-13) return null;
818
+ [M[col], M[pivot]] = [M[pivot], M[col]];
819
+ const d = M[col][col];
820
+ for (let c = 0; c < 2 * n; c++) M[col][c] /= d;
821
+ for (let r = 0; r < n; r++) {
822
+ if (r === col) continue;
823
+ const f = M[r][col];
824
+ if (f === 0) continue;
825
+ for (let c = 0; c < 2 * n; c++) M[r][c] -= f * M[col][c];
826
+ }
827
+ }
828
+ return M.map((row) => row.slice(n));
829
+ }
830
+ function matMul(A, B) {
831
+ const n = A.length;
832
+ const k = B.length;
833
+ const m = B[0]?.length ?? 0;
834
+ const C = zeros(n, m);
835
+ for (let i = 0; i < n; i++) {
836
+ for (let l = 0; l < k; l++) {
837
+ const a = A[i][l];
838
+ if (a === 0) continue;
839
+ for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
840
+ }
841
+ }
842
+ return C;
843
+ }
844
+ function transpose(A) {
845
+ const n = A.length;
846
+ const m = A[0]?.length ?? 0;
847
+ const T = zeros(m, n);
848
+ for (let i = 0; i < n; i++) for (let j = 0; j < m; j++) T[j][i] = A[i][j];
849
+ return T;
850
+ }
851
+ function groupEntries(geom) {
852
+ const groups = /* @__PURE__ */ new Map();
853
+ for (const g of geom) {
854
+ const list = groups.get(g.group);
855
+ if (list) list.push(g);
856
+ else groups.set(g.group, [g]);
857
+ }
858
+ return groups;
859
+ }
860
+ function ddCovariance(rows, sigmaCode, sigmaPhase) {
861
+ const n = rows.length;
862
+ const R = zeros(n, n);
863
+ const sd = (r, g) => sdVariance(r.kind === "code" ? sigmaCode : sigmaPhase, g.elB);
864
+ for (let i = 0; i < n; i++) {
865
+ const ri = rows[i];
866
+ R[i][i] = sd(ri, ri.g) + sd(ri, ri.ref);
867
+ for (let j = i + 1; j < n; j++) {
868
+ const rj = rows[j];
869
+ if (ri.ref.prn === rj.ref.prn && ri.kind === rj.kind && ri.g.group === rj.g.group) {
870
+ R[i][j] = R[j][i] = sd(ri, ri.ref);
871
+ }
872
+ }
873
+ }
874
+ return R;
875
+ }
876
+ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
877
+ const {
878
+ elevationMaskDeg = 10,
879
+ troposphere = true,
880
+ maxIterations = 15,
881
+ convergenceM = 1e-4,
882
+ codeSigmaM = 0.3,
883
+ rejectThresholdM = 20,
884
+ initialPosition
885
+ } = opts;
886
+ let geom = buildGeometry(
887
+ rover,
888
+ base,
889
+ basePos,
890
+ ephemerides,
891
+ timeMs,
892
+ elevationMaskDeg * Math.PI / 180,
893
+ troposphere
894
+ );
895
+ const rejected = [];
896
+ for (; ; ) {
897
+ const groups = groupEntries(geom);
898
+ const rows = [];
899
+ const refSatellites = {};
900
+ for (const [group, list] of groups) {
901
+ if (list.length < 2) continue;
902
+ const ref = list.reduce((a, b) => b.elB > a.elB ? b : a);
903
+ refSatellites[group] = ref.prn;
904
+ for (const g of list) {
905
+ if (g === ref) continue;
906
+ rows.push({
907
+ g,
908
+ ref,
909
+ kind: "code",
910
+ z: g.prR - g.prB - (ref.prR - ref.prB)
911
+ });
912
+ }
913
+ }
914
+ if (rows.length < 3) return null;
915
+ const W = matInv(ddCovariance(rows, codeSigmaM, codeSigmaM));
916
+ if (!W) return null;
917
+ let [x, y, z] = initialPosition ?? basePos;
918
+ let iterations = 0;
919
+ let converged = false;
920
+ const residuals = {};
921
+ let cov = null;
922
+ for (let iter = 0; iter < maxIterations; iter++) {
923
+ iterations = iter + 1;
924
+ const H = [];
925
+ const v = [];
926
+ const terms = /* @__PURE__ */ new Map();
927
+ const termOf = (g) => {
928
+ let t = terms.get(g.prn);
929
+ if (!t) {
930
+ t = roverTerms(g, x, y, z, troposphere);
931
+ terms.set(g.prn, t);
932
+ }
933
+ return t;
934
+ };
935
+ for (const row of rows) {
936
+ const ts = termOf(row.g);
937
+ const tr = termOf(row.ref);
938
+ const pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
939
+ v.push(row.z - pred);
940
+ H.push([ts.u[0] - tr.u[0], ts.u[1] - tr.u[1], ts.u[2] - tr.u[2]]);
941
+ }
942
+ const Ht = transpose(H);
943
+ const HtW = matMul(Ht, W);
944
+ const N = matMul(HtW, H);
945
+ const b = matMul(
946
+ HtW,
947
+ v.map((s) => [s])
948
+ );
949
+ const Ninv = matInv(N);
950
+ if (!Ninv) return null;
951
+ const dx = matMul(
952
+ Ninv,
953
+ b.map((r) => [r[0]])
954
+ );
955
+ x += dx[0][0];
956
+ y += dx[1][0];
957
+ z += dx[2][0];
958
+ cov = Ninv;
959
+ rows.forEach((row, i) => {
960
+ residuals[row.g.prn] = v[i] - (H[i][0] * dx[0][0] + H[i][1] * dx[1][0] + H[i][2] * dx[2][0]);
961
+ });
962
+ if (Math.hypot(dx[0][0], dx[1][0], dx[2][0]) < convergenceM) {
963
+ converged = true;
964
+ break;
965
+ }
966
+ }
967
+ let worst = null;
968
+ let worstAbs = rejectThresholdM;
969
+ for (const [prn, r] of Object.entries(residuals)) {
970
+ if (Math.abs(r) > worstAbs) {
971
+ worstAbs = Math.abs(r);
972
+ worst = prn;
973
+ }
974
+ }
975
+ if (worst && geom.length > 4) {
976
+ rejected.push(worst);
977
+ geom = geom.filter((g) => g.prn !== worst);
978
+ continue;
979
+ }
980
+ const usedSatellites = [
981
+ ...new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn]))
982
+ ].sort();
983
+ return {
984
+ position: [x, y, z],
985
+ baseline: [x - basePos[0], y - basePos[1], z - basePos[2]],
986
+ sigmas: cov ? [
987
+ Math.sqrt(Math.max(cov[0][0], 0)),
988
+ Math.sqrt(Math.max(cov[1][1], 0)),
989
+ Math.sqrt(Math.max(cov[2][2], 0))
990
+ ] : [0, 0, 0],
991
+ usedSatellites,
992
+ refSatellites,
993
+ residuals,
994
+ rejectedSatellites: rejected,
995
+ nSats: usedSatellites.length,
996
+ iterations,
997
+ converged
998
+ };
999
+ }
1000
+ }
1001
+ var RtkFloatEngine = class {
1002
+ basePos;
1003
+ ephemerides;
1004
+ o;
1005
+ /** State vector [x, y, z, N₁…] and covariance; null before init. */
1006
+ x = null;
1007
+ P = [];
1008
+ amb = [];
1009
+ refs = /* @__PURE__ */ new Map();
1010
+ track = /* @__PURE__ */ new Map();
1011
+ lastMs = null;
1012
+ constructor(basePos, ephemerides, opts = {}) {
1013
+ this.basePos = basePos;
1014
+ this.ephemerides = ephemerides;
1015
+ this.o = {
1016
+ mode: opts.mode ?? "kinematic",
1017
+ elevationMaskDeg: opts.elevationMaskDeg ?? 10,
1018
+ troposphere: opts.troposphere ?? true,
1019
+ codeSigmaM: opts.codeSigmaM ?? 0.3,
1020
+ phaseSigmaM: opts.phaseSigmaM ?? 3e-3,
1021
+ processNoisePosM: opts.processNoisePosM ?? 0,
1022
+ kinematicSigmaM: opts.kinematicSigmaM ?? 30,
1023
+ ambProcessNoiseCycles: opts.ambProcessNoiseCycles ?? 1e-4,
1024
+ ambInitSigmaCycles: opts.ambInitSigmaCycles ?? 30,
1025
+ maxGapMs: opts.maxGapMs ?? 1e4,
1026
+ codeGateM: opts.codeGateM ?? 30,
1027
+ slipGateM: opts.slipGateM ?? 5,
1028
+ updateIterations: opts.updateIterations ?? 2,
1029
+ ambiguityResolution: opts.ambiguityResolution ?? "off",
1030
+ ratioThreshold: opts.ratioThreshold ?? 3,
1031
+ arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
1032
+ glonassAr: opts.glonassAr ?? false,
1033
+ partialFixing: opts.partialFixing ?? true,
1034
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4
1035
+ };
1036
+ }
1037
+ /** Clear all filter state (position, ambiguities, lock history). */
1038
+ reset() {
1039
+ this.x = null;
1040
+ this.P = [];
1041
+ this.amb = [];
1042
+ this.refs.clear();
1043
+ this.track.clear();
1044
+ this.lastMs = null;
1045
+ }
1046
+ ambIndex(prn) {
1047
+ return this.amb.findIndex((a) => a.prn === prn);
1048
+ }
1049
+ /** Remove one ambiguity state (row/col) from x and P. */
1050
+ dropAmb(idx) {
1051
+ const s = 3 + idx;
1052
+ this.amb.splice(idx, 1);
1053
+ this.x.splice(s, 1);
1054
+ this.P.splice(s, 1);
1055
+ for (const row of this.P) row.splice(s, 1);
1056
+ }
1057
+ /** Append an ambiguity state with the given value and variance. */
1058
+ addAmb(entry, value, variance) {
1059
+ this.amb.push(entry);
1060
+ this.x.push(value);
1061
+ const n = this.x.length;
1062
+ for (const row of this.P) row.push(0);
1063
+ const newRow = new Array(n).fill(0);
1064
+ newRow[n - 1] = variance;
1065
+ this.P.push(newRow);
1066
+ }
1067
+ /**
1068
+ * Re-map a group's DD ambiguities from the old reference r to the
1069
+ * new reference r' (which must hold a state): N_i' = N_i − N_r' and
1070
+ * the old reference's slot becomes N_r = −N_r'. Exact linear
1071
+ * transform of state and covariance (P' = T P Tᵀ).
1072
+ */
1073
+ retarget(group, oldRef, newRefIdx) {
1074
+ const n = this.x.length;
1075
+ const j = 3 + newRefIdx;
1076
+ const T = zeros(n, n);
1077
+ for (let i = 0; i < n; i++) T[i][i] = 1;
1078
+ for (let k = 0; k < this.amb.length; k++) {
1079
+ if (this.amb[k].group !== group) continue;
1080
+ const s = 3 + k;
1081
+ if (s === j) T[s][s] = -1;
1082
+ else T[s][j] -= 1;
1083
+ }
1084
+ this.x = matMul(
1085
+ T,
1086
+ this.x.map((v) => [v])
1087
+ ).map((r) => r[0]);
1088
+ this.P = matMul(matMul(T, this.P), transpose(T));
1089
+ const entry = this.amb[newRefIdx];
1090
+ entry.prn = oldRef;
1091
+ }
1092
+ /**
1093
+ * Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
1094
+ * extract the float subvector b and covariance Q_b from the filter,
1095
+ * run MLAMBDA and, without touching the filter state, condition the
1096
+ * position on the best integer candidate,
1097
+ * xa = x − P_xb·Q_b⁻¹·(b − ǎ)
1098
+ * (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
1099
+ * positive definite / invertible or the search fails; ratio
1100
+ * validation is the caller's job.
1101
+ */
1102
+ tryFix(idxs) {
1103
+ const k = idxs.length;
1104
+ const b = new Float64Array(k);
1105
+ const Qb = new Float64Array(k * k);
1106
+ const QbRows = [];
1107
+ for (let i = 0; i < k; i++) {
1108
+ const si = 3 + idxs[i];
1109
+ b[i] = this.x[si];
1110
+ const row = [];
1111
+ for (let j = 0; j < k; j++) {
1112
+ const sj = 3 + idxs[j];
1113
+ const q = (this.P[si][sj] + this.P[sj][si]) / 2;
1114
+ Qb[i + j * k] = q;
1115
+ row.push(q);
1116
+ }
1117
+ QbRows.push(row);
1118
+ }
1119
+ const res = lambdaSearch(b, Qb, k, 2);
1120
+ if (!res) return null;
1121
+ const best = res.candidates[0];
1122
+ const QbInv = matInv(QbRows);
1123
+ if (!QbInv) return null;
1124
+ const db = [];
1125
+ for (let i = 0; i < k; i++) db.push(b[i] - best[i]);
1126
+ const position = [
1127
+ this.x[0],
1128
+ this.x[1],
1129
+ this.x[2]
1130
+ ];
1131
+ for (let r = 0; r < 3; r++) {
1132
+ let dx = 0;
1133
+ for (let i = 0; i < k; i++) {
1134
+ let yi = 0;
1135
+ for (let j = 0; j < k; j++) yi += QbInv[i][j] * db[j];
1136
+ dx += this.P[r][3 + idxs[i]] * yi;
1137
+ }
1138
+ position[r] -= dx;
1139
+ }
1140
+ return { ratio: res.ratio, position, intAmb: [...best] };
1141
+ }
1142
+ /**
1143
+ * Process one synchronized epoch (same nominal `timeMs` for both
1144
+ * receivers). Returns null until a first position can be
1145
+ * initialised (via an internal DGNSS solve) or when the epoch has
1146
+ * fewer than 3 usable DD rows.
1147
+ */
1148
+ process(rover, base, timeMs) {
1149
+ const o = this.o;
1150
+ const geom = buildGeometry(
1151
+ rover,
1152
+ base,
1153
+ this.basePos,
1154
+ this.ephemerides,
1155
+ timeMs,
1156
+ o.elevationMaskDeg * Math.PI / 180,
1157
+ o.troposphere
1158
+ );
1159
+ const byPrn = new Map(geom.map((g) => [g.prn, g]));
1160
+ const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
1161
+ if (this.x) {
1162
+ for (let i = this.amb.length - 1; i >= 0; i--) {
1163
+ const prn = this.amb[i].prn;
1164
+ const tr = this.track.get(prn);
1165
+ const seen = byPrn.get(prn);
1166
+ const hasPhase = seen && seen.cpR !== null && seen.cpB !== null;
1167
+ const staleMs = tr ? timeMs - tr.lastMs : Infinity;
1168
+ if (!hasPhase && staleMs > o.maxGapMs) this.dropAmb(i);
1169
+ }
1170
+ }
1171
+ const slipped = /* @__PURE__ */ new Set();
1172
+ for (const g of geom) {
1173
+ if (g.cpR === null || g.cpB === null) continue;
1174
+ const tr = this.track.get(g.prn);
1175
+ if (!tr) continue;
1176
+ if (timeMs - tr.lastMs > o.maxGapMs) slipped.add(g.prn);
1177
+ 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)
1178
+ slipped.add(g.prn);
1179
+ }
1180
+ if (!this.x) {
1181
+ const dg = solveDgnss(
1182
+ rover,
1183
+ base,
1184
+ this.basePos,
1185
+ this.ephemerides,
1186
+ timeMs,
1187
+ {
1188
+ elevationMaskDeg: o.elevationMaskDeg,
1189
+ troposphere: o.troposphere,
1190
+ codeSigmaM: o.codeSigmaM
1191
+ }
1192
+ );
1193
+ if (!dg) return null;
1194
+ this.x = [...dg.position];
1195
+ this.P = zeros(3, 3);
1196
+ for (let i = 0; i < 3; i++) {
1197
+ const s = Math.max(dg.sigmas[i], 1);
1198
+ this.P[i][i] = 25 * s * s;
1199
+ }
1200
+ this.amb = [];
1201
+ } else if (o.mode === "kinematic") {
1202
+ for (let i = 0; i < 3; i++) {
1203
+ for (let j = 0; j < this.x.length; j++) {
1204
+ this.P[i][j] = 0;
1205
+ this.P[j][i] = 0;
1206
+ }
1207
+ this.P[i][i] = o.kinematicSigmaM * o.kinematicSigmaM;
1208
+ }
1209
+ } else {
1210
+ const q = o.processNoisePosM * o.processNoisePosM * dtS;
1211
+ for (let i = 0; i < 3; i++) this.P[i][i] += q;
1212
+ }
1213
+ const qAmb = o.ambProcessNoiseCycles * o.ambProcessNoiseCycles * dtS;
1214
+ for (let k = 0; k < this.amb.length; k++) this.P[3 + k][3 + k] += qAmb;
1215
+ const groups = groupEntries(geom);
1216
+ const newRefs = /* @__PURE__ */ new Map();
1217
+ for (const [group, list] of groups) {
1218
+ if (list.length < 2) continue;
1219
+ const phase = list.filter(
1220
+ (g) => g.cpR !== null && g.cpB !== null && !slipped.has(g.prn)
1221
+ );
1222
+ const pool = phase.length >= 2 ? phase : list;
1223
+ const prevRef = this.refs.get(group);
1224
+ const best = pool.reduce((a, b) => b.elB > a.elB ? b : a);
1225
+ let ref = best;
1226
+ if (best.prn !== prevRef) {
1227
+ const anyState = this.amb.some((a) => a.group === group);
1228
+ if (anyState) {
1229
+ if (this.ambIndex(best.prn) >= 0) {
1230
+ this.retarget(group, prevRef ?? best.prn, this.ambIndex(best.prn));
1231
+ } else if (prevRef && pool.some((g) => g.prn === prevRef)) {
1232
+ ref = pool.find((g) => g.prn === prevRef);
1233
+ } else {
1234
+ for (let i = this.amb.length - 1; i >= 0; i--)
1235
+ if (this.amb[i].group === group) this.dropAmb(i);
1236
+ }
1237
+ }
1238
+ }
1239
+ newRefs.set(group, ref);
1240
+ this.refs.set(group, ref.prn);
1241
+ }
1242
+ for (const group of [...this.refs.keys()])
1243
+ if (!newRefs.has(group)) this.refs.delete(group);
1244
+ for (const prn of slipped) {
1245
+ const i = this.ambIndex(prn);
1246
+ if (i >= 0) this.dropAmb(i);
1247
+ }
1248
+ const initAmb = (g, ref) => {
1249
+ if (g.lambda <= 0 || ref.lambda <= 0) return;
1250
+ const zPhi = g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB);
1251
+ const zP = g.prR - g.prB - (ref.prR - ref.prB);
1252
+ const n0 = (zPhi - zP) / g.lambda;
1253
+ this.addAmb(
1254
+ { prn: g.prn, group: g.group, lambda: g.lambda },
1255
+ n0,
1256
+ o.ambInitSigmaCycles * o.ambInitSigmaCycles
1257
+ );
1258
+ };
1259
+ for (const [group, ref] of newRefs) {
1260
+ for (const g of groups.get(group)) {
1261
+ if (g === ref || g.cpR === null || g.cpB === null) continue;
1262
+ if (ref.cpR === null || ref.cpB === null) continue;
1263
+ const i = this.ambIndex(g.prn);
1264
+ if (i >= 0) {
1265
+ this.amb[i].lambda = g.lambda;
1266
+ this.amb[i].group = g.group;
1267
+ continue;
1268
+ }
1269
+ initAmb(g, ref);
1270
+ }
1271
+ }
1272
+ for (let i = this.amb.length - 1; i >= 0; i--) {
1273
+ const a = this.amb[i];
1274
+ if (newRefs.get(a.group)?.prn === a.prn) this.dropAmb(i);
1275
+ }
1276
+ let rows = [];
1277
+ for (const [group, ref] of newRefs) {
1278
+ for (const g of groups.get(group)) {
1279
+ if (g === ref) continue;
1280
+ rows.push({
1281
+ g,
1282
+ ref,
1283
+ kind: "code",
1284
+ z: g.prR - g.prB - (ref.prR - ref.prB),
1285
+ ambIdx: -1
1286
+ });
1287
+ const i = this.ambIndex(g.prn);
1288
+ if (i >= 0 && g.cpR !== null && g.cpB !== null && ref.cpR !== null && ref.cpB !== null && g.lambda > 0 && ref.lambda > 0) {
1289
+ rows.push({
1290
+ g,
1291
+ ref,
1292
+ kind: "phase",
1293
+ z: g.lambda * (g.cpR - g.cpB) - ref.lambda * (ref.cpR - ref.cpB),
1294
+ ambIdx: i
1295
+ });
1296
+ }
1297
+ }
1298
+ }
1299
+ if (rows.length < 3) {
1300
+ this.lastMs = timeMs;
1301
+ return null;
1302
+ }
1303
+ const predict = (row, xs) => {
1304
+ const ts = roverTerms(row.g, xs[0], xs[1], xs[2], o.troposphere);
1305
+ const tr = roverTerms(row.ref, xs[0], xs[1], xs[2], o.troposphere);
1306
+ let pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
1307
+ const h = new Array(this.x.length).fill(0);
1308
+ h[0] = ts.u[0] - tr.u[0];
1309
+ h[1] = ts.u[1] - tr.u[1];
1310
+ h[2] = ts.u[2] - tr.u[2];
1311
+ if (row.ambIdx >= 0) {
1312
+ pred += this.amb[row.ambIdx].lambda * this.x[3 + row.ambIdx];
1313
+ h[3 + row.ambIdx] = this.amb[row.ambIdx].lambda;
1314
+ }
1315
+ return { pred, h };
1316
+ };
1317
+ const dropPrns = /* @__PURE__ */ new Set();
1318
+ const codeInn = /* @__PURE__ */ new Map();
1319
+ for (const row of rows) {
1320
+ if (row.kind !== "code") continue;
1321
+ const v = row.z - predict(row, this.x).pred;
1322
+ codeInn.set(row.g.prn, v);
1323
+ if (Math.abs(v) > o.codeGateM) dropPrns.add(row.g.prn);
1324
+ }
1325
+ for (const row of rows) {
1326
+ if (row.kind !== "phase" || dropPrns.has(row.g.prn)) continue;
1327
+ const v = row.z - predict(row, this.x).pred;
1328
+ const vc = codeInn.get(row.g.prn) ?? 0;
1329
+ if (Math.abs(v - vc) > o.slipGateM) {
1330
+ const zP = row.g.prR - row.g.prB - (row.ref.prR - row.ref.prB);
1331
+ const lam = this.amb[row.ambIdx].lambda;
1332
+ this.x[3 + row.ambIdx] = (row.z - zP) / lam;
1333
+ const s = 3 + row.ambIdx;
1334
+ for (let j = 0; j < this.x.length; j++) {
1335
+ this.P[s][j] = 0;
1336
+ this.P[j][s] = 0;
1337
+ }
1338
+ this.P[s][s] = o.ambInitSigmaCycles * o.ambInitSigmaCycles;
1339
+ }
1340
+ }
1341
+ if (dropPrns.size) rows = rows.filter((r) => !dropPrns.has(r.g.prn));
1342
+ if (rows.length < 3) {
1343
+ this.lastMs = timeMs;
1344
+ return null;
1345
+ }
1346
+ const n = this.x.length;
1347
+ const R = ddCovariance(rows, o.codeSigmaM, o.phaseSigmaM);
1348
+ const xPrior = [...this.x];
1349
+ const PPrior = this.P;
1350
+ let xi = [...this.x];
1351
+ let K = [];
1352
+ let H = [];
1353
+ for (let it = 0; it < Math.max(o.updateIterations, 1); it++) {
1354
+ H = [];
1355
+ const v = [];
1356
+ for (const row of rows) {
1357
+ const { pred, h } = predict(row, xi);
1358
+ let corr = 0;
1359
+ for (let j = 0; j < n; j++) corr += h[j] * (xPrior[j] - xi[j]);
1360
+ v.push(row.z - pred - corr);
1361
+ H.push(h);
1362
+ }
1363
+ const Ht = transpose(H);
1364
+ const S = matMul(matMul(H, PPrior), Ht);
1365
+ for (let i = 0; i < rows.length; i++)
1366
+ for (let j = 0; j < rows.length; j++) S[i][j] += R[i][j];
1367
+ const Sinv = matInv(S);
1368
+ if (!Sinv) {
1369
+ this.lastMs = timeMs;
1370
+ return null;
1371
+ }
1372
+ K = matMul(matMul(PPrior, Ht), Sinv);
1373
+ const dx = matMul(
1374
+ K,
1375
+ v.map((s) => [s])
1376
+ );
1377
+ xi = xPrior.map((s, i) => s + dx[i][0]);
1378
+ }
1379
+ this.x = xi;
1380
+ const KH = matMul(K, H);
1381
+ const IKH = zeros(n, n);
1382
+ for (let i = 0; i < n; i++)
1383
+ for (let j = 0; j < n; j++) IKH[i][j] = (i === j ? 1 : 0) - KH[i][j];
1384
+ const Pnew = matMul(IKH, PPrior);
1385
+ for (let i = 0; i < n; i++)
1386
+ for (let j = i; j < n; j++) {
1387
+ const s = (Pnew[i][j] + Pnew[j][i]) / 2;
1388
+ Pnew[i][j] = s;
1389
+ Pnew[j][i] = s;
1390
+ }
1391
+ this.P = Pnew;
1392
+ let status = rows.some(
1393
+ (r) => r.kind === "phase"
1394
+ ) ? "float" : "dgnss";
1395
+ let ratio;
1396
+ let nFixed;
1397
+ let fixedPos = null;
1398
+ let fixedAmbiguities;
1399
+ if (o.ambiguityResolution === "instant") {
1400
+ const cands = [];
1401
+ for (const row of rows) {
1402
+ if (row.kind !== "phase") continue;
1403
+ if (row.g.prn[0] === "R" && !o.glonassAr) continue;
1404
+ if (row.g.elB < o.arElevationMaskDeg * Math.PI / 180) continue;
1405
+ cands.push({ idx: row.ambIdx, el: row.g.elB });
1406
+ }
1407
+ cands.sort((a, b) => b.el - a.el);
1408
+ const floor = Math.max(
1409
+ o.minFixAmbiguities,
1410
+ Math.ceil(cands.length / 2),
1411
+ 1
1412
+ );
1413
+ while (cands.length >= floor) {
1414
+ const fix = this.tryFix(cands.map((c) => c.idx));
1415
+ if (!fix) break;
1416
+ const capped = Math.min(fix.ratio, 999.9);
1417
+ ratio ??= capped;
1418
+ if (fix.ratio >= o.ratioThreshold) {
1419
+ ratio = capped;
1420
+ status = "fixed";
1421
+ nFixed = cands.length;
1422
+ fixedPos = fix.position;
1423
+ fixedAmbiguities = {};
1424
+ cands.forEach((c, i) => {
1425
+ fixedAmbiguities[this.amb[c.idx].prn] = fix.intAmb[i];
1426
+ });
1427
+ break;
1428
+ }
1429
+ if (!o.partialFixing) break;
1430
+ cands.pop();
1431
+ }
1432
+ }
1433
+ for (const g of geom) {
1434
+ if (g.cpR === null || g.cpB === null) continue;
1435
+ this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
1436
+ }
1437
+ this.lastMs = timeMs;
1438
+ const ambiguities = {};
1439
+ for (let k = 0; k < this.amb.length; k++)
1440
+ ambiguities[this.amb[k].prn] = this.x[3 + k];
1441
+ const refSatellites = {};
1442
+ for (const [group, ref] of newRefs) refSatellites[group] = ref.prn;
1443
+ const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
1444
+ return {
1445
+ timeMs,
1446
+ position: fixedPos ?? [this.x[0], this.x[1], this.x[2]],
1447
+ floatBaseline: [
1448
+ this.x[0] - this.basePos[0],
1449
+ this.x[1] - this.basePos[1],
1450
+ this.x[2] - this.basePos[2]
1451
+ ],
1452
+ status,
1453
+ nSats,
1454
+ ratio,
1455
+ nFixed,
1456
+ fixedBaseline: fixedPos ? [
1457
+ fixedPos[0] - this.basePos[0],
1458
+ fixedPos[1] - this.basePos[1],
1459
+ fixedPos[2] - this.basePos[2]
1460
+ ] : void 0,
1461
+ fixedAmbiguities,
1462
+ sigmas: [
1463
+ Math.sqrt(Math.max(this.P[0][0], 0)),
1464
+ Math.sqrt(Math.max(this.P[1][1], 0)),
1465
+ Math.sqrt(Math.max(this.P[2][2], 0))
1466
+ ],
1467
+ ambiguities,
1468
+ refSatellites
1469
+ };
1470
+ }
1471
+ };
1472
+
443
1473
  // src/positioning/index.ts
444
1474
  var GM_GPS2 = 3986005e8;
445
1475
  var F_REL = -4442807633e-19;
@@ -476,11 +1506,11 @@ var PRIMARY_FREQ_HZ = {
476
1506
  R: 1602e6
477
1507
  };
478
1508
  var F_L1 = 157542e4;
479
- function tropoDelay(elevationRad) {
1509
+ function tropoDelay2(elevationRad) {
480
1510
  const sinEl = Math.sin(elevationRad);
481
1511
  return 2.47 / (sinEl + 0.0121);
482
1512
  }
483
- function sagnac(pos, travelTimeS) {
1513
+ function sagnac2(pos, travelTimeS) {
484
1514
  const theta = OMEGA_E * travelTimeS;
485
1515
  const c = Math.cos(theta);
486
1516
  const s = Math.sin(theta);
@@ -551,7 +1581,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
551
1581
  const isKepler = eph.system !== "R" && eph.system !== "S";
552
1582
  const dts = dtsClock - (tgd && isKepler ? eph.tgd : 0);
553
1583
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
554
- const [sx, sy, sz] = sagnac(sat, travel);
1584
+ const [sx, sy, sz] = sagnac2(sat, travel);
555
1585
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
556
1586
  const ux = (x2 - sx) / rho;
557
1587
  const uy = (y2 - sy) / rho;
@@ -568,7 +1598,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
568
1598
  const sinEl = Math.max(Math.sin(elev), 0.1);
569
1599
  weight = sinEl * sinEl;
570
1600
  }
571
- const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
1601
+ const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
572
1602
  let ionoM = 0;
573
1603
  if (iono && positionSane) {
574
1604
  const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
@@ -653,8 +1683,13 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
653
1683
  }
654
1684
  // Annotate the CommonJS export names for ESM import in node:
655
1685
  0 && (module.exports = {
1686
+ RtkFloatEngine,
656
1687
  ionoFree,
657
1688
  klobucharDelay,
1689
+ lambdaReduction,
1690
+ lambdaSearch,
658
1691
  satClockCorrection,
659
- solveSpp
1692
+ solveDgnss,
1693
+ solveSpp,
1694
+ toRtkEpoch
660
1695
  });