gnss-js 1.23.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/{chunk-CUWF56ST.js → chunk-T2ZMNRCY.js} +311 -3
- package/dist/index.cjs +313 -3
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/positioning.cjs +313 -3
- package/dist/positioning.d.cts +167 -5
- package/dist/positioning.d.ts +167 -5
- package/dist/positioning.js +5 -1
- package/package.json +1 -1
package/dist/positioning.cjs
CHANGED
|
@@ -23,6 +23,8 @@ __export(positioning_exports, {
|
|
|
23
23
|
RtkFloatEngine: () => RtkFloatEngine,
|
|
24
24
|
ionoFree: () => ionoFree,
|
|
25
25
|
klobucharDelay: () => klobucharDelay,
|
|
26
|
+
lambdaReduction: () => lambdaReduction,
|
|
27
|
+
lambdaSearch: () => lambdaSearch,
|
|
26
28
|
satClockCorrection: () => satClockCorrection,
|
|
27
29
|
solveDgnss: () => solveDgnss,
|
|
28
30
|
solveSpp: () => solveSpp,
|
|
@@ -462,6 +464,207 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
|
|
|
462
464
|
return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
|
|
463
465
|
}
|
|
464
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
|
+
|
|
465
668
|
// src/positioning/rtk.ts
|
|
466
669
|
var L1_CODES = {
|
|
467
670
|
G: ["1C"],
|
|
@@ -822,7 +1025,13 @@ var RtkFloatEngine = class {
|
|
|
822
1025
|
maxGapMs: opts.maxGapMs ?? 1e4,
|
|
823
1026
|
codeGateM: opts.codeGateM ?? 30,
|
|
824
1027
|
slipGateM: opts.slipGateM ?? 5,
|
|
825
|
-
updateIterations: opts.updateIterations ?? 2
|
|
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
|
|
826
1035
|
};
|
|
827
1036
|
}
|
|
828
1037
|
/** Clear all filter state (position, ambiguities, lock history). */
|
|
@@ -880,6 +1089,56 @@ var RtkFloatEngine = class {
|
|
|
880
1089
|
const entry = this.amb[newRefIdx];
|
|
881
1090
|
entry.prn = oldRef;
|
|
882
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
|
+
}
|
|
883
1142
|
/**
|
|
884
1143
|
* Process one synchronized epoch (same nominal `timeMs` for both
|
|
885
1144
|
* receivers). Returns null until a first position can be
|
|
@@ -1130,6 +1389,47 @@ var RtkFloatEngine = class {
|
|
|
1130
1389
|
Pnew[j][i] = s;
|
|
1131
1390
|
}
|
|
1132
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
|
+
}
|
|
1133
1433
|
for (const g of geom) {
|
|
1134
1434
|
if (g.cpR === null || g.cpB === null) continue;
|
|
1135
1435
|
this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
|
|
@@ -1143,14 +1443,22 @@ var RtkFloatEngine = class {
|
|
|
1143
1443
|
const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
|
|
1144
1444
|
return {
|
|
1145
1445
|
timeMs,
|
|
1146
|
-
position: [this.x[0], this.x[1], this.x[2]],
|
|
1446
|
+
position: fixedPos ?? [this.x[0], this.x[1], this.x[2]],
|
|
1147
1447
|
floatBaseline: [
|
|
1148
1448
|
this.x[0] - this.basePos[0],
|
|
1149
1449
|
this.x[1] - this.basePos[1],
|
|
1150
1450
|
this.x[2] - this.basePos[2]
|
|
1151
1451
|
],
|
|
1452
|
+
status,
|
|
1152
1453
|
nSats,
|
|
1153
|
-
ratio
|
|
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,
|
|
1154
1462
|
sigmas: [
|
|
1155
1463
|
Math.sqrt(Math.max(this.P[0][0], 0)),
|
|
1156
1464
|
Math.sqrt(Math.max(this.P[1][1], 0)),
|
|
@@ -1378,6 +1686,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
1378
1686
|
RtkFloatEngine,
|
|
1379
1687
|
ionoFree,
|
|
1380
1688
|
klobucharDelay,
|
|
1689
|
+
lambdaReduction,
|
|
1690
|
+
lambdaSearch,
|
|
1381
1691
|
satClockCorrection,
|
|
1382
1692
|
solveDgnss,
|
|
1383
1693
|
solveSpp,
|
package/dist/positioning.d.cts
CHANGED
|
@@ -54,7 +54,29 @@ declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad:
|
|
|
54
54
|
* (LAMBDA) needs the joint float ambiguity covariance — which the
|
|
55
55
|
* EKF maintains natively. This mirrors RTKLIB's float-filter
|
|
56
56
|
* structure (rtkpos.c), which the implementation was validated
|
|
57
|
-
* against
|
|
57
|
+
* against.
|
|
58
|
+
*
|
|
59
|
+
* Stage 2 — integer ambiguity resolution (opt-in via
|
|
60
|
+
* `ambiguityResolution: 'instant'`): after each float update the
|
|
61
|
+
* engine extracts the DD ambiguity subvector and its joint covariance
|
|
62
|
+
* from the filter, runs MLAMBDA (`lambdaSearch`, a port of RTKLIB's
|
|
63
|
+
* lambda.c) and validates the best candidate with the ratio test
|
|
64
|
+
* (s₂/s₁ ≥ `ratioThreshold`, default 3.0 — RTKLIB's default). On
|
|
65
|
+
* acceptance the rover position is conditioned on the integer
|
|
66
|
+
* ambiguities (xa = x − P_xb·Q_b⁻¹·(b − ǎ), RTKLIB rtkpos.c
|
|
67
|
+
* `resamb_LAMBDA`) and reported as a FIXED solution; the filter state
|
|
68
|
+
* itself is *not* modified (instant, per-epoch fixing — RTKLIB's
|
|
69
|
+
* `armode=instantaneous`; continuous AR/fix-and-hold, which feed the
|
|
70
|
+
* fix back into the filter, are stage-3 continuity options). When the
|
|
71
|
+
* full set fails the ratio test, partial fixing retries after
|
|
72
|
+
* dropping the lowest-elevation ambiguity, one at a time, down to
|
|
73
|
+
* half the candidate set (and never below `minFixAmbiguities`) — an
|
|
74
|
+
* elevation-ordered subset walk in the spirit of RTKLIB demo5's
|
|
75
|
+
* satellite exclusion, floored so an unconverged float cannot buy a
|
|
76
|
+
* lucky ratio on a tiny subset. GLONASS ambiguities are excluded from
|
|
77
|
+
* fixing by
|
|
78
|
+
* default (DD IFB is only integer-safe across same-model receivers;
|
|
79
|
+
* enable with `glonassAr`).
|
|
58
80
|
*
|
|
59
81
|
* Differencing model: for satellites s and reference r observed by
|
|
60
82
|
* rover R and base B, the DD pseudorange/phase cancels satellite
|
|
@@ -208,18 +230,65 @@ interface RtkFloatOptions {
|
|
|
208
230
|
slipGateM?: number;
|
|
209
231
|
/** Measurement-update relinearisations (IEKF). Default 2. */
|
|
210
232
|
updateIterations?: number;
|
|
233
|
+
/**
|
|
234
|
+
* Integer ambiguity resolution: 'off' keeps the stage-1 float-only
|
|
235
|
+
* behaviour; 'instant' attempts a per-epoch LAMBDA fix after each
|
|
236
|
+
* float update (no state feedback — RTKLIB `armode=instantaneous`).
|
|
237
|
+
* Default 'off'.
|
|
238
|
+
*/
|
|
239
|
+
ambiguityResolution?: 'off' | 'instant';
|
|
240
|
+
/** Ratio-test acceptance threshold (s₂/s₁). Default 3.0 (RTKLIB). */
|
|
241
|
+
ratioThreshold?: number;
|
|
242
|
+
/**
|
|
243
|
+
* Additional elevation mask (deg) for ambiguities entering the fix
|
|
244
|
+
* (RTKLIB `elmaskar`); 0 = use every measured ambiguity. Default 0.
|
|
245
|
+
*/
|
|
246
|
+
arElevationMaskDeg?: number;
|
|
247
|
+
/**
|
|
248
|
+
* Include GLONASS FDMA ambiguities in the integer fix. Only sound
|
|
249
|
+
* across same-model receivers (differential IFB). Default false.
|
|
250
|
+
*/
|
|
251
|
+
glonassAr?: boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Partial fixing: when the full ambiguity set fails the ratio test,
|
|
254
|
+
* drop the lowest-elevation ambiguity and retry (elevation-ordered
|
|
255
|
+
* subset walk), never below half the candidates. Default true.
|
|
256
|
+
*/
|
|
257
|
+
partialFixing?: boolean;
|
|
258
|
+
/** Minimum DD ambiguities to attempt/accept a fix. Default 4. */
|
|
259
|
+
minFixAmbiguities?: number;
|
|
211
260
|
}
|
|
212
261
|
interface RtkFloatSolution {
|
|
213
262
|
/** Epoch, GPS-scale milliseconds. */
|
|
214
263
|
timeMs: number;
|
|
215
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* Rover position, ECEF metres — the ambiguity-fixed position when
|
|
266
|
+
* `status === 'fixed'`, the float filter position otherwise.
|
|
267
|
+
*/
|
|
216
268
|
position: [number, number, number];
|
|
217
|
-
/** Float baseline rover − base, ECEF metres. */
|
|
269
|
+
/** Float baseline rover − base, ECEF metres (always the float). */
|
|
218
270
|
floatBaseline: [number, number, number];
|
|
271
|
+
/**
|
|
272
|
+
* Solution quality: 'fixed' when LAMBDA passed the ratio test,
|
|
273
|
+
* 'float' when carrier ambiguities contributed but no (accepted)
|
|
274
|
+
* fix, 'dgnss' when the epoch was updated from code DDs only.
|
|
275
|
+
*/
|
|
276
|
+
status: 'fixed' | 'float' | 'dgnss';
|
|
219
277
|
/** Satellites contributing DD rows this epoch (incl. references). */
|
|
220
278
|
nSats: number;
|
|
221
|
-
/**
|
|
279
|
+
/**
|
|
280
|
+
* Ambiguity-validation ratio (s₂/s₁ of the LAMBDA candidates,
|
|
281
|
+
* capped at 999.9 as RTKLIB): the accepted subset's ratio when
|
|
282
|
+
* fixed, the full-set ratio otherwise. Undefined when no fix was
|
|
283
|
+
* attempted (`ambiguityResolution: 'off'` or no eligible set).
|
|
284
|
+
*/
|
|
222
285
|
ratio?: number;
|
|
286
|
+
/** Number of integer-fixed DD ambiguities (when `status==='fixed'`). */
|
|
287
|
+
nFixed?: number;
|
|
288
|
+
/** Fixed baseline rover − base, ECEF metres (when fixed). */
|
|
289
|
+
fixedBaseline?: [number, number, number];
|
|
290
|
+
/** Integer DD ambiguity (cycles) per fixed PRN (when fixed). */
|
|
291
|
+
fixedAmbiguities?: Record<string, number>;
|
|
223
292
|
/** Formal position sigmas (filter covariance), ECEF metres. */
|
|
224
293
|
sigmas: [number, number, number];
|
|
225
294
|
/** Float DD ambiguity (cycles) per non-reference PRN. */
|
|
@@ -262,6 +331,17 @@ declare class RtkFloatEngine {
|
|
|
262
331
|
* transform of state and covariance (P' = T P Tᵀ).
|
|
263
332
|
*/
|
|
264
333
|
private retarget;
|
|
334
|
+
/**
|
|
335
|
+
* Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
|
|
336
|
+
* extract the float subvector b and covariance Q_b from the filter,
|
|
337
|
+
* run MLAMBDA and, without touching the filter state, condition the
|
|
338
|
+
* position on the best integer candidate,
|
|
339
|
+
* xa = x − P_xb·Q_b⁻¹·(b − ǎ)
|
|
340
|
+
* (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
|
|
341
|
+
* positive definite / invertible or the search fails; ratio
|
|
342
|
+
* validation is the caller's job.
|
|
343
|
+
*/
|
|
344
|
+
private tryFix;
|
|
265
345
|
/**
|
|
266
346
|
* Process one synchronized epoch (same nominal `timeMs` for both
|
|
267
347
|
* receivers). Returns null until a first position can be
|
|
@@ -271,6 +351,88 @@ declare class RtkFloatEngine {
|
|
|
271
351
|
process(rover: RtkEpochMeasurements, base: RtkEpochMeasurements, timeMs: number): RtkFloatSolution | null;
|
|
272
352
|
}
|
|
273
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Integer ambiguity resolution: LAMBDA decorrelation + MLAMBDA
|
|
356
|
+
* integer least-squares search.
|
|
357
|
+
*
|
|
358
|
+
* Port of RTKLIB's `lambda.c` (demo5 / rtklibexplorer fork,
|
|
359
|
+
* src/lambda.c, Copyright (c) 2007-2008 T. Takasu, BSD-2-Clause),
|
|
360
|
+
* cross-checked against the reference papers where the C comments are
|
|
361
|
+
* thin:
|
|
362
|
+
*
|
|
363
|
+
* - [1] P.J.G. Teunissen, "The least-squares ambiguity decorrelation
|
|
364
|
+
* adjustment: a method for fast GPS ambiguity estimation",
|
|
365
|
+
* J. Geodesy 70, 65-82, 1995.
|
|
366
|
+
* - [2] X.-W. Chang, X. Yang, T. Zhou, "MLAMBDA: A modified LAMBDA
|
|
367
|
+
* method for integer least-squares estimation", J. Geodesy 79,
|
|
368
|
+
* 552-565, 2005.
|
|
369
|
+
*
|
|
370
|
+
* Pipeline (identical to RTKLIB's `lambda()`):
|
|
371
|
+
* 1. LᵀDL factorization of the float covariance, Q = Lᵀ·diag(D)·L
|
|
372
|
+
* with L unit lower triangular (RTKLIB's `LD`).
|
|
373
|
+
* 2. LAMBDA reduction [1]: integer Gauss transformations interleaved
|
|
374
|
+
* with symmetric permutations until the transformed D is
|
|
375
|
+
* (approximately) descending — producing a unimodular Z with
|
|
376
|
+
* Qz = ZᵀQZ = Lᵀ·diag(D)·L far better conditioned than Q.
|
|
377
|
+
* 3. MLAMBDA search [2] in the decorrelated space: depth-first
|
|
378
|
+
* enumeration over a shrinking ellipsoid, keeping the `m` smallest
|
|
379
|
+
* residual quadratic forms (z−ž)ᵀQz⁻¹(z−ž).
|
|
380
|
+
* 4. Back-transform the candidates to the original space, a = Z⁻ᵀ·ž
|
|
381
|
+
* (RTKLIB solves ZᵀF = E; Z is unimodular so F is exactly integer).
|
|
382
|
+
*
|
|
383
|
+
* Deviations from lambda.c:
|
|
384
|
+
* - matrices are `Float64Array` in the same column-major layout, with
|
|
385
|
+
* allocation-failure paths dropped (JS throws instead),
|
|
386
|
+
* - failures (non-positive-definite Q, search loop overflow) return
|
|
387
|
+
* `null` instead of a status code + stderr message,
|
|
388
|
+
* - back-transformed candidates are rounded to the nearest integer:
|
|
389
|
+
* F = Z⁻ᵀ·ž is exactly integer in ℤ arithmetic and the rounding only
|
|
390
|
+
* removes the ~1e-12 fuzz of the floating-point triangular solve,
|
|
391
|
+
* - the ratio s₂/s₁ used by the RTKLIB caller (`rtkpos.c`) is returned
|
|
392
|
+
* alongside the candidates (∞ when s₁ = 0, i.e. noise-free input).
|
|
393
|
+
*/
|
|
394
|
+
/** Result of an integer least-squares search. */
|
|
395
|
+
interface LambdaResult {
|
|
396
|
+
/**
|
|
397
|
+
* The `m` best integer candidate vectors (length n each), sorted by
|
|
398
|
+
* ascending residual quadratic form; `candidates[0]` is the integer
|
|
399
|
+
* least-squares minimizer.
|
|
400
|
+
*/
|
|
401
|
+
candidates: Float64Array[];
|
|
402
|
+
/**
|
|
403
|
+
* Residual quadratic forms sᵢ = (a−ǎᵢ)ᵀQ⁻¹(a−ǎᵢ) matching
|
|
404
|
+
* `candidates`, ascending.
|
|
405
|
+
*/
|
|
406
|
+
residuals: Float64Array;
|
|
407
|
+
/**
|
|
408
|
+
* Ratio-test statistic s₂/s₁ (second-best over best); `Infinity`
|
|
409
|
+
* when the best residual is exactly 0 (noise-free input). Callers
|
|
410
|
+
* typically cap it (RTKLIB uses 999.9) before thresholding.
|
|
411
|
+
*/
|
|
412
|
+
ratio: number;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* LAMBDA/MLAMBDA integer least-squares estimation (RTKLIB `lambda`):
|
|
416
|
+
* decorrelate the float ambiguities, search the `m` best integer
|
|
417
|
+
* vectors and back-transform them to the original space.
|
|
418
|
+
*
|
|
419
|
+
* @param a Float ambiguities (length ≥ n).
|
|
420
|
+
* @param Q Their covariance, n×n (symmetric — row/column-major agree).
|
|
421
|
+
* @param n Number of ambiguities.
|
|
422
|
+
* @param m Number of candidates to return (default 2: best +
|
|
423
|
+
* second-best, what the ratio test needs).
|
|
424
|
+
* @returns Candidates + residuals + ratio, or null when Q is not
|
|
425
|
+
* positive definite or the search does not terminate.
|
|
426
|
+
*/
|
|
427
|
+
declare function lambdaSearch(a: Float64Array, Q: Float64Array, n: number, m?: number): LambdaResult | null;
|
|
428
|
+
/**
|
|
429
|
+
* LAMBDA reduction only (RTKLIB `lambda_reduction`): the unimodular
|
|
430
|
+
* decorrelation matrix Z (n×n, column-major) such that Qz = ZᵀQZ is
|
|
431
|
+
* (near-)diagonally dominant. Exposed for diagnostics/tests; returns
|
|
432
|
+
* null when Q is not positive definite.
|
|
433
|
+
*/
|
|
434
|
+
declare function lambdaReduction(Q: Float64Array, n: number): Float64Array | null;
|
|
435
|
+
|
|
274
436
|
/**
|
|
275
437
|
* Single-point positioning (SPP) from pseudoranges and broadcast
|
|
276
438
|
* ephemerides.
|
|
@@ -351,4 +513,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
|
|
|
351
513
|
*/
|
|
352
514
|
declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
|
|
353
515
|
|
|
354
|
-
export { type DgnssOptions, type DgnssSolution, type EphemerisSource, type KlobucharCoeffs, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
|
|
516
|
+
export { type DgnssOptions, type DgnssSolution, type EphemerisSource, type KlobucharCoeffs, type LambdaResult, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
|