gnss-js 2.11.0 → 2.13.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.
Files changed (37) hide show
  1. package/README.md +19 -1
  2. package/dist/analysis.cjs +24 -25
  3. package/dist/analysis.js +2 -2
  4. package/dist/android.cjs +338 -0
  5. package/dist/android.d.cts +68 -0
  6. package/dist/android.d.ts +68 -0
  7. package/dist/android.js +311 -0
  8. package/dist/{clk-DGVBGRzA.d.cts → bias-sinex-OGMw9xEZ.d.cts} +66 -1
  9. package/dist/{clk-DGVBGRzA.d.ts → bias-sinex-OGMw9xEZ.d.ts} +66 -1
  10. package/dist/{chunk-MDO77CCC.js → chunk-2DJ4XENO.js} +45 -0
  11. package/dist/{chunk-IQHQHUOB.js → chunk-2KQMCJ5Z.js} +5 -3
  12. package/dist/chunk-E3Y6YKQM.js +65 -0
  13. package/dist/{chunk-3U5AX7PY.js → chunk-FEABDV5P.js} +24 -25
  14. package/dist/{chunk-RFPR4LKM.js → chunk-HWHD5E6V.js} +19 -75
  15. package/dist/{chunk-KUSUN7DP.js → chunk-MFCPYUEI.js} +1 -1
  16. package/dist/{chunk-SSWFX3P3.js → chunk-NIJ6WFQJ.js} +6 -321
  17. package/dist/{chunk-UMINQTMK.js → chunk-RWFJEVLJ.js} +589 -295
  18. package/dist/chunk-TWGZTRBL.js +323 -0
  19. package/dist/index.cjs +655 -316
  20. package/dist/index.d.cts +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +14 -9
  23. package/dist/osnma.cjs +1336 -0
  24. package/dist/osnma.d.cts +273 -0
  25. package/dist/osnma.d.ts +273 -0
  26. package/dist/osnma.js +1216 -0
  27. package/dist/positioning.cjs +585 -291
  28. package/dist/positioning.d.cts +114 -4
  29. package/dist/positioning.d.ts +114 -4
  30. package/dist/positioning.js +1 -1
  31. package/dist/rinex.cjs +70 -25
  32. package/dist/rinex.d.cts +2 -50
  33. package/dist/rinex.d.ts +2 -50
  34. package/dist/rinex.js +4 -2
  35. package/dist/sbf.js +7 -4
  36. package/dist/ubx.js +3 -2
  37. package/package.json +11 -1
@@ -265,6 +265,12 @@ function solveZt(n, m, Z, E) {
265
265
  for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
266
266
  return X;
267
267
  }
268
+ function normCdf(x) {
269
+ const z = Math.abs(x) / Math.SQRT2;
270
+ const t = 1 / (1 + 0.3275911 * z);
271
+ const erf = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-z * z);
272
+ return x >= 0 ? 0.5 * (1 + erf) : 0.5 * (1 - erf);
273
+ }
268
274
  function lambdaSearch(a, Q, n, m = 2) {
269
275
  if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
270
276
  const L = new Float64Array(n * n);
@@ -291,7 +297,10 @@ function lambdaSearch(a, Q, n, m = 2) {
291
297
  candidates.push(v);
292
298
  }
293
299
  const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
294
- return { candidates, residuals: s, ratio };
300
+ let successRate = 1;
301
+ for (let i = 0; i < n; i++)
302
+ successRate *= 2 * normCdf(1 / (2 * Math.sqrt(D[i]))) - 1;
303
+ return { candidates, residuals: s, ratio, successRate };
295
304
  }
296
305
  function lambdaReduction(Q, n) {
297
306
  if (n <= 0 || Q.length < n * n) return null;
@@ -306,7 +315,7 @@ function lambdaReduction(Q, n) {
306
315
 
307
316
  // src/positioning/ppp-antenna.ts
308
317
  function parseAntexTime(s) {
309
- const p = s.trim().split(/\s+/).map(Number);
318
+ const p = s.trim().split(/[\s:-]+/).map(Number);
310
319
  if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
311
320
  return Date.UTC(
312
321
  p[0],
@@ -356,11 +365,21 @@ function buildPppAntenna(antex) {
356
365
  satOffset(prn, dateMs, band1, band2, f1, f2) {
357
366
  const list = sats.get(prn);
358
367
  if (!list) return null;
359
- const entry = list.find((e) => {
360
- const from = parseAntexTime(e.validFrom);
361
- const until = parseAntexTime(e.validUntil);
362
- return (from == null || dateMs >= from) && (until == null || dateMs <= until);
363
- });
368
+ let entry;
369
+ let bestDist = Infinity;
370
+ for (const e of list) {
371
+ const from = parseAntexTime(e.validFrom) ?? -Infinity;
372
+ const until = parseAntexTime(e.validUntil) ?? Infinity;
373
+ if (dateMs >= from && dateMs <= until) {
374
+ entry = e;
375
+ break;
376
+ }
377
+ const dist = dateMs < from ? from - dateMs : dateMs - until;
378
+ if (dist < bestDist) {
379
+ bestDist = dist;
380
+ entry = e;
381
+ }
382
+ }
364
383
  if (!entry) return null;
365
384
  const a = freqOf(entry, band1);
366
385
  const b = freqOf(entry, band2);
@@ -368,10 +387,14 @@ function buildPppAntenna(antex) {
368
387
  return withZen(entry, combineIf(a, b, f1, f2));
369
388
  },
370
389
  rcvOffset(antType, band1, band2, f1, f2) {
371
- const entry = rcvs.get(antType);
390
+ const entry = rcvs.get(antType) ?? rcvs.get(antType.substring(0, 16).padEnd(16, " ") + "NONE");
372
391
  if (!entry) return null;
373
- const a = freqOf(entry, band1);
374
- const b = freqOf(entry, band2);
392
+ let a = freqOf(entry, band1);
393
+ let b = freqOf(entry, band2);
394
+ if (!a || !b) {
395
+ a = freqOf(entry, "G01");
396
+ b = freqOf(entry, "G02");
397
+ }
375
398
  if (!a || !b) return null;
376
399
  return withZen(entry, combineIf(a, b, f1, f2));
377
400
  }
@@ -447,13 +470,13 @@ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
447
470
  kx[2] - k[2] * kdotx - kcrossY[2]
448
471
  ];
449
472
  const north = enuToEcef([0, 1, 0], latRad, lonRad);
450
- const east = enuToEcef([1, 0, 0], latRad, lonRad);
473
+ const west = enuToEcef([-1, 0, 0], latRad, lonRad);
451
474
  const kdotn = dot(k, north);
452
- const kcrossE = cross(k, east);
475
+ const kcrossW = cross(k, west);
453
476
  const dR = [
454
- north[0] - k[0] * kdotn + kcrossE[0],
455
- north[1] - k[1] * kdotn + kcrossE[1],
456
- north[2] - k[2] * kdotn + kcrossE[2]
477
+ north[0] - k[0] * kdotn + kcrossW[0],
478
+ north[1] - k[1] * kdotn + kcrossW[1],
479
+ north[2] - k[2] * kdotn + kcrossW[2]
457
480
  ];
458
481
  const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
459
482
  const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
@@ -580,9 +603,11 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
580
603
  let baseLatRad = 0;
581
604
  let baseLonRad = 0;
582
605
  if (antenna?.base) {
583
- const [latDeg, lonDeg] = ecefToGeodetic(basePos[0], basePos[1], basePos[2]);
584
- baseLatRad = latDeg * Math.PI / 180;
585
- baseLonRad = lonDeg * Math.PI / 180;
606
+ [baseLatRad, baseLonRad] = ecefToGeodetic(
607
+ basePos[0],
608
+ basePos[1],
609
+ basePos[2]
610
+ );
586
611
  }
587
612
  for (const [prn, mR] of rover) {
588
613
  const sys = prn[0];
@@ -678,12 +703,12 @@ function roverTerms(g, x, y, z, troposphere) {
678
703
  dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
679
704
  }
680
705
  if (g.offR) {
681
- const [latDeg, lonDeg] = ecefToGeodetic(x, y, z);
706
+ const [latRad, lonRad] = ecefToGeodetic(x, y, z);
682
707
  rho += rcvAntennaRangeM(
683
708
  g.offR,
684
709
  [-u[0], -u[1], -u[2]],
685
- latDeg * Math.PI / 180,
686
- lonDeg * Math.PI / 180,
710
+ latRad,
711
+ lonRad,
687
712
  elR,
688
713
  g.roverDelta
689
714
  );
@@ -2498,6 +2523,234 @@ function hasToOrbitClock(orbit, clock, refTimeMs, validitySeconds) {
2498
2523
  };
2499
2524
  }
2500
2525
 
2526
+ // src/positioning/ppp-ar.ts
2527
+ function wlWavelength(f1, f2) {
2528
+ return C_LIGHT / (f1 - f2);
2529
+ }
2530
+ function nlWavelength(f1, f2) {
2531
+ return C_LIGHT / (f1 + f2);
2532
+ }
2533
+ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
2534
+ const wlThreshold = opts.wlThreshold ?? 0.25;
2535
+ const ratioThreshold = opts.ratioThreshold ?? 3;
2536
+ const minPartial = opts.minPartial ?? 4;
2537
+ const minSuccess = opts.minSuccessRate ?? 0;
2538
+ const n = sats.length;
2539
+ const none = (rejectedWl2, ratio = 0) => ({
2540
+ fixed: false,
2541
+ ratio,
2542
+ refPrn: null,
2543
+ refPrns: [],
2544
+ sats: [],
2545
+ rejectedWl: rejectedWl2
2546
+ });
2547
+ const elev = (i) => sats[i].elevDeg ?? -1;
2548
+ const wlOf = (i) => sats[i].mwCyc - (sats[i].wlBiasCyc ?? 0);
2549
+ const refOfSys = /* @__PURE__ */ new Map();
2550
+ for (let i = 0; i < n; i++) {
2551
+ const sys = sats[i].prn[0];
2552
+ const r = refOfSys.get(sys);
2553
+ if (r === void 0 || elev(i) > elev(r)) refOfSys.set(sys, i);
2554
+ }
2555
+ const nWlOf = /* @__PURE__ */ new Map();
2556
+ const refIdx = /* @__PURE__ */ new Map();
2557
+ const rejectedWl = [];
2558
+ for (const r of refOfSys.values()) {
2559
+ nWlOf.set(r, Math.round(wlOf(r)));
2560
+ refIdx.set(r, r);
2561
+ }
2562
+ for (let i = 0; i < n; i++) {
2563
+ if (refIdx.has(i)) continue;
2564
+ const r = refOfSys.get(sats[i].prn[0]);
2565
+ const sdWl = wlOf(i) - wlOf(r);
2566
+ const k = Math.round(sdWl);
2567
+ if (Math.abs(sdWl - k) <= wlThreshold) {
2568
+ nWlOf.set(i, nWlOf.get(r) + k);
2569
+ refIdx.set(i, r);
2570
+ } else rejectedWl.push(sats[i].prn);
2571
+ }
2572
+ const lamNl = (i) => nlWavelength(sats[i].f1, sats[i].f2);
2573
+ const n1Float = (i) => {
2574
+ const s = sats[i];
2575
+ return s.aIF / lamNl(i) - s.f2 / (s.f1 - s.f2) * nWlOf.get(i) - (s.nlBiasCyc ?? 0);
2576
+ };
2577
+ const qN1 = (a, b) => Qaif[a * n + b] / (lamNl(a) * lamNl(b));
2578
+ let cand = [...refIdx.keys()].filter((i) => refIdx.get(i) !== i).sort((a, b) => elev(b) - elev(a));
2579
+ if (cand.length < 1) return none(rejectedWl);
2580
+ let best = null;
2581
+ let firstRatio = 0;
2582
+ for (; ; ) {
2583
+ const m = cand.length;
2584
+ const sd = new Float64Array(m);
2585
+ const Qsd = new Float64Array(m * m);
2586
+ for (let a = 0; a < m; a++) {
2587
+ const ia = cand[a];
2588
+ const ra = refIdx.get(ia);
2589
+ sd[a] = n1Float(ia) - n1Float(ra);
2590
+ for (let b = 0; b < m; b++) {
2591
+ const ib = cand[b];
2592
+ const rb = refIdx.get(ib);
2593
+ Qsd[a * m + b] = qN1(ia, ib) - qN1(ia, rb) - qN1(ra, ib) + qN1(ra, rb);
2594
+ }
2595
+ }
2596
+ const res = lambdaSearch(sd, Qsd, m, 2);
2597
+ const ratio = res ? Math.min(res.ratio, 999.9) : 0;
2598
+ if (best === null && firstRatio === 0) firstRatio = ratio;
2599
+ if (res && ratio >= ratioThreshold && res.successRate >= minSuccess) {
2600
+ best = { set: cand, ratio, fix: Float64Array.from(res.candidates[0]) };
2601
+ break;
2602
+ }
2603
+ if (m <= minPartial) break;
2604
+ cand = cand.slice(0, -1);
2605
+ }
2606
+ if (!best) return { ...none(rejectedWl, firstRatio), refPrn: null };
2607
+ const refN1 = /* @__PURE__ */ new Map();
2608
+ for (const r of refOfSys.values()) refN1.set(r, Math.round(n1Float(r)));
2609
+ const out = [];
2610
+ const emit = (i, n1) => {
2611
+ const s = sats[i];
2612
+ const l = lamNl(i);
2613
+ const nWl = nWlOf.get(i);
2614
+ out.push({
2615
+ prn: s.prn,
2616
+ nWl,
2617
+ n1,
2618
+ aIF: l * n1 + l * (s.f2 / (s.f1 - s.f2)) * nWl,
2619
+ ref: sats[refIdx.get(i)].prn
2620
+ });
2621
+ };
2622
+ const usedRefs = new Set(best.set.map((i) => refIdx.get(i)));
2623
+ for (const r of usedRefs) emit(r, refN1.get(r));
2624
+ best.set.forEach(
2625
+ (i, a) => emit(i, refN1.get(refIdx.get(i)) + Math.round(best.fix[a]))
2626
+ );
2627
+ const refPrns = [...usedRefs].map((r) => sats[r].prn);
2628
+ return {
2629
+ fixed: true,
2630
+ ratio: best.ratio,
2631
+ refPrn: refPrns[0] ?? null,
2632
+ refPrns,
2633
+ sats: out,
2634
+ rejectedWl
2635
+ };
2636
+ }
2637
+ function solveSpd(A, b) {
2638
+ const n = b.length;
2639
+ const L = Array.from({ length: n }, () => new Array(n).fill(0));
2640
+ for (let i = 0; i < n; i++) {
2641
+ for (let j = 0; j <= i; j++) {
2642
+ let s = A[i][j];
2643
+ for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
2644
+ if (i === j) {
2645
+ if (s <= 0) return null;
2646
+ L[i][j] = Math.sqrt(s);
2647
+ } else {
2648
+ L[i][j] = s / L[j][j];
2649
+ }
2650
+ }
2651
+ }
2652
+ const y = new Array(n).fill(0);
2653
+ for (let i = 0; i < n; i++) {
2654
+ let s = b[i];
2655
+ for (let k = 0; k < i; k++) s -= L[i][k] * y[k];
2656
+ y[i] = s / L[i][i];
2657
+ }
2658
+ const x = new Array(n).fill(0);
2659
+ for (let i = n - 1; i >= 0; i--) {
2660
+ let s = y[i];
2661
+ for (let k = i + 1; k < n; k++) s -= L[k][i] * x[k];
2662
+ x[i] = s / L[i][i];
2663
+ }
2664
+ return x;
2665
+ }
2666
+ function fixPppPosition(state, fcb, opts = {}) {
2667
+ const floatPos = state.position;
2668
+ const noFix = {
2669
+ fixed: false,
2670
+ position: floatPos,
2671
+ floatPosition: floatPos,
2672
+ shift: [0, 0, 0],
2673
+ nFixed: 0,
2674
+ ratio: 0,
2675
+ refPrn: null
2676
+ };
2677
+ const amb = state.ambiguities;
2678
+ if (amb.length < 3) return noFix;
2679
+ const sats = amb.map((a) => ({
2680
+ prn: a.prn,
2681
+ aIF: a.aIF,
2682
+ mwCyc: a.mwCyc,
2683
+ f1: a.f1,
2684
+ f2: a.f2,
2685
+ wlBiasCyc: fcb.satWlFcb.get(a.prn) ?? 0,
2686
+ nlBiasCyc: fcb.satNlFcb.get(a.prn) ?? 0,
2687
+ elevDeg: a.elevDeg
2688
+ }));
2689
+ const n = sats.length;
2690
+ const Qaif = new Float64Array(n * n);
2691
+ for (let i = 0; i < n; i++)
2692
+ for (let j = 0; j < n; j++)
2693
+ Qaif[i * n + j] = state.covariance[amb[i].index][amb[j].index];
2694
+ const res = resolvePppAmbiguities(sats, Qaif, {
2695
+ ...opts,
2696
+ minPartial: opts.minPartial ?? Math.max(4, opts.minFixed ?? 0)
2697
+ });
2698
+ if (!res.fixed || res.refPrn === null) {
2699
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
2700
+ }
2701
+ const idxOfPrn = new Map(amb.map((a) => [a.prn, a.index]));
2702
+ const floatOfPrn = new Map(amb.map((a) => [a.prn, a.aIF]));
2703
+ const fixedOf = new Map(res.sats.map((s) => [s.prn, s.aIF]));
2704
+ const pairs = res.sats.filter(
2705
+ (s) => s.ref !== s.prn && idxOfPrn.has(s.prn) && idxOfPrn.has(s.ref)
2706
+ ).map((s) => ({ prn: s.prn, ref: s.ref }));
2707
+ const m = pairs.length;
2708
+ if (m < Math.max(1, opts.minFixed ?? 1))
2709
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
2710
+ const P = state.covariance;
2711
+ const ix = (prn) => idxOfPrn.get(prn);
2712
+ const qAA = (i, j) => P[ix(i)][ix(j)];
2713
+ const qXA = (r, i) => P[r][ix(i)];
2714
+ const Qzz = Array.from({ length: m }, () => new Array(m).fill(0));
2715
+ const z = new Array(m).fill(0);
2716
+ for (let a = 0; a < m; a++) {
2717
+ const { prn: ia, ref: ra } = pairs[a];
2718
+ z[a] = floatOfPrn.get(ia) - floatOfPrn.get(ra) - (fixedOf.get(ia) - fixedOf.get(ra));
2719
+ for (let b = 0; b < m; b++) {
2720
+ const { prn: ib, ref: rb } = pairs[b];
2721
+ Qzz[a][b] = qAA(ia, ib) - qAA(ia, rb) - qAA(ra, ib) + qAA(ra, rb);
2722
+ }
2723
+ }
2724
+ const Qxz = [0, 1, 2].map(
2725
+ (r) => pairs.map(({ prn, ref }) => qXA(r, prn) - qXA(r, ref))
2726
+ );
2727
+ const y = solveSpd(Qzz, z);
2728
+ if (!y) return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
2729
+ let chi2 = 0;
2730
+ for (let a = 0; a < m; a++) chi2 += z[a] * y[a];
2731
+ if (chi2 / m > (opts.maxChi2 ?? Infinity))
2732
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
2733
+ const shift = [0, 0, 0];
2734
+ for (let r = 0; r < 3; r++) {
2735
+ let s = 0;
2736
+ for (let a = 0; a < m; a++) s += Qxz[r][a] * y[a];
2737
+ shift[r] = -s;
2738
+ }
2739
+ return {
2740
+ fixed: true,
2741
+ position: [
2742
+ floatPos[0] + shift[0],
2743
+ floatPos[1] + shift[1],
2744
+ floatPos[2] + shift[2]
2745
+ ],
2746
+ floatPosition: floatPos,
2747
+ shift,
2748
+ nFixed: m,
2749
+ ratio: res.ratio,
2750
+ refPrn: res.refPrn
2751
+ };
2752
+ }
2753
+
2501
2754
  // src/positioning/ppp-tropo.ts
2502
2755
  var HYD_AVG_A = [
2503
2756
  0.0012769934,
@@ -2705,13 +2958,11 @@ function createPppCorrections(cfg) {
2705
2958
  let rangeM = 0;
2706
2959
  let phaseWindupM = 0;
2707
2960
  const e = geom.los;
2708
- const [latDeg, lonDeg] = ecefToGeodetic(
2961
+ const [latRad, lonRad] = ecefToGeodetic(
2709
2962
  geom.rcvEcef[0],
2710
2963
  geom.rcvEcef[1],
2711
2964
  geom.rcvEcef[2]
2712
2965
  );
2713
- const latRad = latDeg * Math.PI / 180;
2714
- const lonRad = lonDeg * Math.PI / 180;
2715
2966
  const frame = ctx ? satBodyFrame(geom.satEcef, ctx.sun) : null;
2716
2967
  if (useSat && frame && geom.band1 && geom.band2) {
2717
2968
  const off = cfg.antenna.satOffset(
@@ -2769,6 +3020,10 @@ function createPppCorrections(cfg) {
2769
3020
  }
2770
3021
 
2771
3022
  // src/positioning/ppp.ts
3023
+ var NO_FCB = {
3024
+ satWlFcb: /* @__PURE__ */ new Map(),
3025
+ satNlFcb: /* @__PURE__ */ new Map()
3026
+ };
2772
3027
  function matVec(P, h) {
2773
3028
  const n = P.length;
2774
3029
  const out = new Array(n).fill(0);
@@ -2780,6 +3035,34 @@ function matVec(P, h) {
2780
3035
  }
2781
3036
  return out;
2782
3037
  }
3038
+ function tdLsq(qs) {
3039
+ const M = [0, 1, 2, 3].map(() => [0, 0, 0, 0, 0]);
3040
+ for (const q of qs) {
3041
+ const a = [-q.v.e[0], -q.v.e[1], -q.v.e[2], 1];
3042
+ for (let i = 0; i < 4; i++) {
3043
+ M[i][4] += a[i] * q.d;
3044
+ for (let j = 0; j < 4; j++) M[i][j] += a[i] * a[j];
3045
+ }
3046
+ }
3047
+ for (let c = 0; c < 4; c++) {
3048
+ let p = c;
3049
+ for (let r = c + 1; r < 4; r++)
3050
+ if (Math.abs(M[r][c]) > Math.abs(M[p][c])) p = r;
3051
+ if (Math.abs(M[p][c]) < 1e-9) return null;
3052
+ [M[c], M[p]] = [M[p], M[c]];
3053
+ for (let r = 0; r < 4; r++) {
3054
+ if (r === c) continue;
3055
+ const f = M[r][c] / M[c][c];
3056
+ for (let k = c; k < 5; k++) M[r][k] -= f * M[c][k];
3057
+ }
3058
+ }
3059
+ return [0, 1, 2, 3].map((i) => M[i][4] / M[i][i]);
3060
+ }
3061
+ function tdResidual(q, s) {
3062
+ return q.d - (-q.v.e[0] * s[0] - q.v.e[1] * s[1] - q.v.e[2] * s[2] + s[3]);
3063
+ }
3064
+ var TD_SLIP_M = 0.08;
3065
+ var CODE_GATE_M = 15;
2783
3066
  function ekfUpdateScalar(x, P, h, innovation, r, rejectK = 0) {
2784
3067
  const n = x.length;
2785
3068
  const Ph = matVec(P, h);
@@ -2812,10 +3095,17 @@ function ifCoeffs(f1, f2) {
2812
3095
  return { g, lambdaIf };
2813
3096
  }
2814
3097
  var Sp3EphemerisSource = class {
2815
- constructor(sp3, clk) {
3098
+ constructor(sp3, clk, osb) {
2816
3099
  this.sp3 = sp3;
2817
3100
  this.clk = clk;
3101
+ this.osb = osb;
3102
+ if (osb) {
3103
+ this.codeBias = (prn, code, t) => osb.get(prn, "C" + code, t);
3104
+ this.phaseBias = (prn, code, t) => osb.get(prn, "L" + code, t);
3105
+ }
2818
3106
  }
3107
+ codeBias;
3108
+ phaseBias;
2819
3109
  satState(prn, tEmitMs) {
2820
3110
  const p = sp3Position(this.sp3, prn, tEmitMs);
2821
3111
  if (!p || p.clk == null) return null;
@@ -2833,6 +3123,21 @@ var Sp3EphemerisSource = class {
2833
3123
  return { x: p.x, y: p.y, z: p.z, vx, vy, vz, clkS };
2834
3124
  }
2835
3125
  };
3126
+ function applySatBiases(source, o, tMs) {
3127
+ if (!source.codeBias && !source.phaseBias) return o;
3128
+ const b1 = source.codeBias?.(o.prn, o.code1 ?? "", tMs) ?? 0;
3129
+ const b2 = source.codeBias?.(o.prn, o.code2 ?? "", tMs) ?? 0;
3130
+ let l1 = o.l1;
3131
+ let l2 = o.l2;
3132
+ if (source.phaseBias) {
3133
+ const p1 = source.phaseBias(o.prn, o.phase1 ?? o.code1 ?? "", tMs);
3134
+ const p2 = source.phaseBias(o.prn, o.phase2 ?? o.code2 ?? "", tMs);
3135
+ if (p1 == null || p2 == null) return null;
3136
+ l1 -= p1 / (C_LIGHT / o.f1);
3137
+ l2 -= p2 / (C_LIGHT / o.f2);
3138
+ }
3139
+ return { ...o, c1: o.c1 - b1, c2: o.c2 - b2, l1, l2 };
3140
+ }
2836
3141
  function sagnacRotate(x, y, z, travelS) {
2837
3142
  const theta = OMEGA_E * travelS;
2838
3143
  const c = Math.cos(theta);
@@ -2894,6 +3199,16 @@ var PppEngine = class {
2894
3199
  arcs;
2895
3200
  arcSnap;
2896
3201
  mwState;
3202
+ /** Previous epoch's phase terms per satellite, for the time-differenced
3203
+ * (clock-free) cycle-slip test. */
3204
+ tdPrev;
3205
+ /** Consecutive gated-out phase updates per satellite. */
3206
+ phaseRejects;
3207
+ /** PPP-AR: the latest accepted fix (null: float). */
3208
+ lastFix;
3209
+ lastArMs;
3210
+ firstFixMs;
3211
+ fixedEpochs;
2897
3212
  series;
2898
3213
  ei = 0;
2899
3214
  t0 = 0;
@@ -2929,6 +3244,12 @@ var PppEngine = class {
2929
3244
  this.arcs = [];
2930
3245
  this.arcSnap = /* @__PURE__ */ new Map();
2931
3246
  this.mwState = /* @__PURE__ */ new Map();
3247
+ this.tdPrev = /* @__PURE__ */ new Map();
3248
+ this.phaseRejects = /* @__PURE__ */ new Map();
3249
+ this.lastFix = null;
3250
+ this.lastArMs = -Infinity;
3251
+ this.firstFixMs = null;
3252
+ this.fixedEpochs = 0;
2932
3253
  this.series = [];
2933
3254
  this.ei = 0;
2934
3255
  this.t0 = 0;
@@ -3022,7 +3343,9 @@ var PppEngine = class {
3022
3343
  lastSeen,
3023
3344
  arcSnap,
3024
3345
  mwState,
3025
- series
3346
+ series,
3347
+ tdPrev,
3348
+ phaseRejects
3026
3349
  } = this;
3027
3350
  const flushArc = this.flushArc.bind(this);
3028
3351
  const detectSlip = this.detectSlip.bind(this);
@@ -3044,13 +3367,15 @@ var PppEngine = class {
3044
3367
  for (const ci of clkIdx.values()) P[ci][ci] = CLK_VAR;
3045
3368
  P[IDX_ZWD][IDX_ZWD] += ztdQ * (ei > 0 ? 1 : 0);
3046
3369
  const rcv = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
3047
- const [latDeg, , heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
3048
- const latRad = latDeg * Math.PI / 180;
3370
+ const [latRad, , heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
3049
3371
  const epochCtx = corrections?.epochContext?.(epoch.timeMs, rcv) ?? null;
3050
3372
  const vis = [];
3051
3373
  let epochZhd = 0;
3052
- for (const o of epoch.obs) {
3053
- if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
3374
+ for (const raw of epoch.obs) {
3375
+ if (raw.c1 === 0 || raw.c2 === 0 || raw.l1 === 0 || raw.l2 === 0)
3376
+ continue;
3377
+ const o = applySatBiases(source, raw, epoch.timeMs);
3378
+ if (!o) continue;
3054
3379
  const slip = detectSlip(o, ei);
3055
3380
  const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
3056
3381
  if (!sat) continue;
@@ -3072,9 +3397,7 @@ var PppEngine = class {
3072
3397
  const lam1 = C_LIGHT / o.f1;
3073
3398
  const lam2 = C_LIGHT / o.f2;
3074
3399
  if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
3075
- const b1 = source.codeBias?.(o.prn, o.code1 ?? "", epoch.timeMs) ?? 0;
3076
- const b2 = source.codeBias?.(o.prn, o.code2 ?? "", epoch.timeMs) ?? 0;
3077
- const pIf = g * (o.c1 - b1) - (g - 1) * (o.c2 - b2);
3400
+ const pIf = g * o.c1 - (g - 1) * o.c2;
3078
3401
  const lIf = g * (o.l1 * lam1) - (g - 1) * (o.l2 * lam2);
3079
3402
  if (!Number.isFinite(pIf) || !Number.isFinite(lIf)) continue;
3080
3403
  const trop = opts.troposphere ?? true ? niellMapping(elRad, latRad, heightM, epoch.timeMs) : { zhd: 0, mh: 0, mw: 0 };
@@ -3107,9 +3430,63 @@ var PppEngine = class {
3107
3430
  windupM: corr.phaseWindupM,
3108
3431
  slip,
3109
3432
  f1: o.f1,
3110
- f2: o.f2
3433
+ f2: o.f2,
3434
+ sat: [sat.state.x, sat.state.y, sat.state.z]
3111
3435
  });
3112
3436
  }
3437
+ {
3438
+ const dq = [];
3439
+ for (const v of vis) {
3440
+ const prev = tdPrev.get(v.prn);
3441
+ const model = -v.satClkM + v.tropoFixed + v.windupM;
3442
+ if (prev && prev.ei === ei - 1 && !v.slip) {
3443
+ const r0 = Math.hypot(
3444
+ prev.sat[0] - rcv[0],
3445
+ prev.sat[1] - rcv[1],
3446
+ prev.sat[2] - rcv[2]
3447
+ );
3448
+ dq.push({
3449
+ v,
3450
+ d: v.lIf - prev.lIf - (v.range - r0) - (model - prev.model)
3451
+ });
3452
+ }
3453
+ tdPrev.set(v.prn, { ei, lIf: v.lIf, sat: v.sat, model });
3454
+ }
3455
+ if (!kinematic) {
3456
+ if (dq.length >= 4) {
3457
+ const sorted = dq.map((q) => q.d).sort((a, b) => a - b);
3458
+ const med = sorted[Math.floor(sorted.length / 2)];
3459
+ for (const q of dq)
3460
+ if (Math.abs(q.d - med) > TD_SLIP_M) q.v.slip = true;
3461
+ }
3462
+ } else if (dq.length >= 6) {
3463
+ let use = dq;
3464
+ for (let iter = 0; iter < 6; iter++) {
3465
+ const sol = use.length >= 6 ? tdLsq(use) : null;
3466
+ if (!sol) {
3467
+ for (const q of dq) q.v.slip = true;
3468
+ break;
3469
+ }
3470
+ let worst = -1;
3471
+ let worstR = 0;
3472
+ use.forEach((q, k) => {
3473
+ const r = Math.abs(tdResidual(q, sol));
3474
+ if (r > worstR) {
3475
+ worstR = r;
3476
+ worst = k;
3477
+ }
3478
+ });
3479
+ if (worstR <= TD_SLIP_M || iter === 5) {
3480
+ for (const q of dq)
3481
+ if (Math.abs(tdResidual(q, sol)) > TD_SLIP_M) q.v.slip = true;
3482
+ break;
3483
+ }
3484
+ use = use.filter((_, k) => k !== worst);
3485
+ }
3486
+ } else {
3487
+ for (const q of dq) q.v.slip = true;
3488
+ }
3489
+ }
3113
3490
  const zwd0 = x[IDX_ZWD];
3114
3491
  const bySys = /* @__PURE__ */ new Map();
3115
3492
  for (const v of vis) {
@@ -3119,6 +3496,20 @@ var PppEngine = class {
3119
3496
  if (arr) arr.push(r);
3120
3497
  else bySys.set(sys, [r]);
3121
3498
  }
3499
+ const posSigma = Math.sqrt(
3500
+ P[IDX_X][IDX_X] + P[IDX_Y][IDX_Y] + P[IDX_Z][IDX_Z]
3501
+ );
3502
+ const codeGate = CODE_GATE_M + 3 * posSigma;
3503
+ const screened = /* @__PURE__ */ new Set();
3504
+ for (const [sys, res] of bySys) {
3505
+ if (res.length < 4) continue;
3506
+ const med = [...res].sort((a, b) => a - b)[Math.floor(res.length / 2)];
3507
+ for (const v of vis) {
3508
+ if (v.prn[0] !== sys) continue;
3509
+ const r = v.pIf - (v.range - v.satClkM + v.tropoFixed + zwd0 * v.mw);
3510
+ if (Math.abs(r - med) > codeGate) screened.add(v.prn);
3511
+ }
3512
+ }
3122
3513
  for (const [sys, res] of bySys) {
3123
3514
  const ci = clockOf(sys);
3124
3515
  if (!clockSeeded.has(sys)) {
@@ -3129,9 +3520,11 @@ var PppEngine = class {
3129
3520
  P[ci][ci] = CLK_VAR;
3130
3521
  }
3131
3522
  let nSats = 0;
3132
- for (const v of vis) {
3523
+ const live = vis.filter((v) => !screened.has(v.prn));
3524
+ const rangeNow = (v) => v.range - (v.e[0] * (x[IDX_X] - rcv[0]) + v.e[1] * (x[IDX_Y] - rcv[1]) + v.e[2] * (x[IDX_Z] - rcv[2]));
3525
+ const prepAmbiguity = (v) => {
3133
3526
  const ci = clockOf(v.prn[0]);
3134
- const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
3527
+ const common = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
3135
3528
  if (v.slip) flushArc(v.prn);
3136
3529
  let ai = ambIdx.get(v.prn);
3137
3530
  if (ai === void 0 || v.slip) {
@@ -3148,67 +3541,91 @@ var PppEngine = class {
3148
3541
  }
3149
3542
  ambIdx.set(v.prn, ai);
3150
3543
  }
3151
- const n = x.length;
3544
+ return ai;
3545
+ };
3546
+ const codeUpdate = (v) => {
3547
+ const ci = clockOf(v.prn[0]);
3548
+ const common = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
3549
+ const sinEl = Math.max(Math.sin(v.elRad), 0.1);
3550
+ const h = new Array(x.length).fill(0);
3551
+ h[IDX_X] = -v.e[0];
3552
+ h[IDX_Y] = -v.e[1];
3553
+ h[IDX_Z] = -v.e[2];
3554
+ h[ci] = 1;
3555
+ h[IDX_ZWD] = v.mw;
3556
+ const innov = v.pIf - common;
3557
+ if (Math.abs(innov) < 1e6)
3558
+ ekfUpdateScalar(x, P, h, innov, (codeSigma / sinEl) ** 2, 4);
3559
+ };
3560
+ const phaseUpdate = (v, ai) => {
3561
+ const ci = clockOf(v.prn[0]);
3152
3562
  const sinEl = Math.max(Math.sin(v.elRad), 0.1);
3153
- const rCode = (codeSigma / sinEl) ** 2;
3154
3563
  const rPhase = (phaseSigma / sinEl) ** 2;
3155
- {
3156
- const h = new Array(n).fill(0);
3564
+ const commonPh = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
3565
+ const innov = v.lIf - (commonPh + v.windupM + x[ai]);
3566
+ const reinit = (phaseRejects.get(v.prn) ?? 0) >= 3;
3567
+ let used = false;
3568
+ if (!reinit) {
3569
+ const h = new Array(x.length).fill(0);
3157
3570
  h[IDX_X] = -v.e[0];
3158
3571
  h[IDX_Y] = -v.e[1];
3159
3572
  h[IDX_Z] = -v.e[2];
3160
3573
  h[ci] = 1;
3161
3574
  h[IDX_ZWD] = v.mw;
3162
- const innov = v.pIf - common;
3163
- if (Math.abs(innov) < 1e6) ekfUpdateScalar(x, P, h, innov, rCode, 4);
3575
+ h[ai] = 1;
3576
+ used = ekfUpdateScalar(x, P, h, innov, rPhase, 4);
3164
3577
  }
3165
- {
3166
- const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
3167
- const innov = v.lIf - (commonPh + v.windupM + x[ai]);
3168
- if (Math.abs(innov) > 0.15) {
3169
- flushArc(v.prn);
3170
- x[ai] = v.lIf - (commonPh + v.windupM);
3171
- for (let j = 0; j < P.length; j++) {
3172
- P[ai][j] = 0;
3173
- P[j][ai] = 0;
3174
- }
3175
- P[ai][ai] = 100 * 100;
3176
- } else {
3177
- const h = new Array(n).fill(0);
3178
- h[IDX_X] = -v.e[0];
3179
- h[IDX_Y] = -v.e[1];
3180
- h[IDX_Z] = -v.e[2];
3181
- h[ci] = 1;
3182
- h[IDX_ZWD] = v.mw;
3183
- h[ai] = 1;
3184
- ekfUpdateScalar(x, P, h, innov, rPhase, 4);
3185
- }
3186
- }
3187
- {
3188
- const elevDeg = v.elRad * 180 / Math.PI;
3189
- const mwMean = mwState.get(v.prn)?.mean ?? 0;
3190
- const s = arcSnap.get(v.prn);
3191
- if (s) {
3192
- s.aIF = x[ai];
3193
- s.mwCyc = mwMean;
3194
- s.elevSum += elevDeg;
3195
- s.nEpochs++;
3196
- s.endMs = epoch.timeMs;
3197
- } else {
3198
- arcSnap.set(v.prn, {
3199
- aIF: x[ai],
3200
- mwCyc: mwMean,
3201
- f1: v.f1,
3202
- f2: v.f2,
3203
- elevSum: elevDeg,
3204
- nEpochs: 1,
3205
- startMs: epoch.timeMs,
3206
- endMs: epoch.timeMs
3207
- });
3578
+ if (reinit) {
3579
+ flushArc(v.prn);
3580
+ x[ai] = v.lIf - (commonPh + v.windupM);
3581
+ for (let j = 0; j < P.length; j++) {
3582
+ P[ai][j] = 0;
3583
+ P[j][ai] = 0;
3208
3584
  }
3585
+ P[ai][ai] = 100 * 100;
3586
+ phaseRejects.delete(v.prn);
3587
+ } else if (used) phaseRejects.delete(v.prn);
3588
+ else phaseRejects.set(v.prn, (phaseRejects.get(v.prn) ?? 0) + 1);
3589
+ };
3590
+ const snapshot = (v, ai) => {
3591
+ const elevDeg = v.elRad * 180 / Math.PI;
3592
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
3593
+ const s = arcSnap.get(v.prn);
3594
+ if (s) {
3595
+ s.aIF = x[ai];
3596
+ s.mwCyc = mwMean;
3597
+ s.elevSum += elevDeg;
3598
+ s.nEpochs++;
3599
+ s.endMs = epoch.timeMs;
3600
+ } else {
3601
+ arcSnap.set(v.prn, {
3602
+ aIF: x[ai],
3603
+ mwCyc: mwMean,
3604
+ f1: v.f1,
3605
+ f2: v.f2,
3606
+ elevSum: elevDeg,
3607
+ nEpochs: 1,
3608
+ startMs: epoch.timeMs,
3609
+ endMs: epoch.timeMs
3610
+ });
3209
3611
  }
3210
3612
  lastSeen.set(v.prn, ei);
3211
3613
  nSats++;
3614
+ };
3615
+ if (kinematic) {
3616
+ for (const v of live) codeUpdate(v);
3617
+ for (const v of live) {
3618
+ const ai = prepAmbiguity(v);
3619
+ phaseUpdate(v, ai);
3620
+ snapshot(v, ai);
3621
+ }
3622
+ } else {
3623
+ for (const v of live) {
3624
+ const ai = prepAmbiguity(v);
3625
+ codeUpdate(v);
3626
+ phaseUpdate(v, ai);
3627
+ snapshot(v, ai);
3628
+ }
3212
3629
  }
3213
3630
  let sumPhaseResSq = 0;
3214
3631
  let nPhaseRes = 0;
@@ -3219,7 +3636,7 @@ var PppEngine = class {
3219
3636
  if (ai === void 0) continue;
3220
3637
  const sys = v.prn[0];
3221
3638
  const ci = clockOf(sys);
3222
- const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
3639
+ const commonPh = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
3223
3640
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
3224
3641
  if (Math.abs(innov) >= 0.15) continue;
3225
3642
  const arr = bySysRes.get(sys);
@@ -3243,6 +3660,8 @@ var PppEngine = class {
3243
3660
  flushArc(prn);
3244
3661
  }
3245
3662
  }
3663
+ this.tryFix(epoch.timeMs, vis);
3664
+ if (this.lastFix) this.fixedEpochs++;
3246
3665
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
3247
3666
  let enu = null;
3248
3667
  let error3d = null;
@@ -3278,12 +3697,75 @@ var PppEngine = class {
3278
3697
  nSats,
3279
3698
  ztdWet: x[IDX_ZWD],
3280
3699
  ztdHydrostatic: epochZhd,
3281
- phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
3700
+ phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0,
3701
+ nFixed: this.lastFix?.nFixed ?? 0,
3702
+ fixedPosition: this.lastFix?.position ?? null
3282
3703
  };
3283
3704
  series.push(result);
3284
3705
  this.ei = ei + 1;
3285
3706
  return result;
3286
3707
  }
3708
+ /**
3709
+ * PPP-AR. Every `everyS`, resolve the converged ambiguities of the
3710
+ * satellites in view (between satellites of one constellation) and, when
3711
+ * the fix passes the ratio, success-rate and χ² tests, condition the
3712
+ * float position on it ({@link fixPppPosition}). The float filter itself
3713
+ * is never constrained — a wrong fix can't contaminate later epochs, and
3714
+ * the next attempt starts clean. Between attempts the last fix carries
3715
+ * over (static) or is dropped (kinematic).
3716
+ */
3717
+ tryFix(tMs, vis) {
3718
+ const ar = this.opts.ar;
3719
+ if (!ar || !this.source.phaseBias) return;
3720
+ const cfg = ar === true ? {} : ar;
3721
+ if (tMs - this.lastArMs < (cfg.everyS ?? 120) * 1e3) {
3722
+ if (this.kinematic) this.lastFix = null;
3723
+ return;
3724
+ }
3725
+ this.lastArMs = tMs;
3726
+ this.lastFix = null;
3727
+ const { x, P, ambIdx, arcSnap, mwState } = this;
3728
+ const minArcMs = (cfg.minArcS ?? 600) * 1e3;
3729
+ const ambiguities = [];
3730
+ for (const v of vis) {
3731
+ const index = ambIdx.get(v.prn);
3732
+ const snap = arcSnap.get(v.prn);
3733
+ const mw = mwState.get(v.prn);
3734
+ if (index === void 0 || !snap || !mw) continue;
3735
+ if (tMs - snap.startMs < minArcMs || snap.nEpochs < 20) continue;
3736
+ ambiguities.push({
3737
+ prn: v.prn,
3738
+ index,
3739
+ aIF: x[index],
3740
+ mwCyc: mw.mean,
3741
+ f1: v.f1,
3742
+ f2: v.f2,
3743
+ elevDeg: v.elRad * 180 / Math.PI
3744
+ });
3745
+ }
3746
+ const fx = fixPppPosition(
3747
+ {
3748
+ position: [x[IDX_X], x[IDX_Y], x[IDX_Z]],
3749
+ covariance: P,
3750
+ ambiguities
3751
+ },
3752
+ NO_FCB,
3753
+ {
3754
+ ratioThreshold: cfg.ratio ?? 3,
3755
+ wlThreshold: cfg.wlThreshold ?? 0.15,
3756
+ minSuccessRate: cfg.successRate ?? 0.999,
3757
+ minFixed: cfg.minFixed ?? 5,
3758
+ maxChi2: cfg.maxChi2 ?? 20
3759
+ }
3760
+ );
3761
+ if (!fx.fixed) return;
3762
+ this.lastFix = {
3763
+ position: fx.position,
3764
+ nFixed: fx.nFixed,
3765
+ ratio: fx.ratio
3766
+ };
3767
+ this.firstFixMs ??= tMs;
3768
+ }
3287
3769
  /** Finalise the session: capture the AR state, flush open arcs, assemble. */
3288
3770
  solution() {
3289
3771
  const { opts, x, P, ambIdx, arcSnap, mwState, arcs, series, truth } = this;
@@ -3316,7 +3798,12 @@ var PppEngine = class {
3316
3798
  x[IDX_Y],
3317
3799
  x[IDX_Z]
3318
3800
  ];
3319
- const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
3801
+ const geo = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
3802
+ const llh = [
3803
+ geo[0] * 180 / Math.PI,
3804
+ geo[1] * 180 / Math.PI,
3805
+ geo[2]
3806
+ ];
3320
3807
  const finalError3d = truth ? Math.hypot(
3321
3808
  finalPos[0] - truth[0],
3322
3809
  finalPos[1] - truth[1],
@@ -3332,218 +3819,25 @@ var PppEngine = class {
3332
3819
  convergenceSec: this.convergenceSec,
3333
3820
  finalError3d,
3334
3821
  arcs,
3335
- finalState
3822
+ finalState,
3823
+ ...opts.ar && this.source.phaseBias ? {
3824
+ ar: {
3825
+ firstFixSec: this.firstFixMs === null || !series.length ? null : (this.firstFixMs - series[0].timeMs) / 1e3,
3826
+ nFixedEnd: this.lastFix?.nFixed ?? 0,
3827
+ position: this.lastFix?.position ?? null,
3828
+ fixedEpochShare: series.length ? this.fixedEpochs / series.length : 0
3829
+ }
3830
+ } : {}
3336
3831
  };
3337
3832
  }
3338
3833
  };
3339
3834
  function solvePpp(epochs, sp3OrSource, opts) {
3340
- const source = "satState" in sp3OrSource ? sp3OrSource : new Sp3EphemerisSource(sp3OrSource, opts.clk);
3835
+ const source = "satState" in sp3OrSource ? sp3OrSource : new Sp3EphemerisSource(sp3OrSource, opts.clk, opts.osb);
3341
3836
  const eng = new PppEngine(opts, source);
3342
3837
  for (const epoch of epochs) eng.process(epoch);
3343
3838
  return eng.solution();
3344
3839
  }
3345
3840
 
3346
- // src/positioning/ppp-ar.ts
3347
- function wlWavelength(f1, f2) {
3348
- return C_LIGHT / (f1 - f2);
3349
- }
3350
- function nlWavelength(f1, f2) {
3351
- return C_LIGHT / (f1 + f2);
3352
- }
3353
- function resolvePppAmbiguities(sats, Qaif, opts = {}) {
3354
- const wlThreshold = opts.wlThreshold ?? 0.25;
3355
- const ratioThreshold = opts.ratioThreshold ?? 3;
3356
- const kept = [];
3357
- const nWl = [];
3358
- const rejectedWl = [];
3359
- for (let i = 0; i < sats.length; i++) {
3360
- const s = sats[i];
3361
- const wlFloat = s.mwCyc - (s.wlBiasCyc ?? 0);
3362
- const r = Math.round(wlFloat);
3363
- if (Math.abs(wlFloat - r) <= wlThreshold) {
3364
- kept.push(i);
3365
- nWl.push(r);
3366
- } else {
3367
- rejectedWl.push(s.prn);
3368
- }
3369
- }
3370
- if (kept.length < 2) {
3371
- return {
3372
- fixed: false,
3373
- ratio: 0,
3374
- refPrn: null,
3375
- sats: [],
3376
- rejectedWl
3377
- };
3378
- }
3379
- const n1Float = [];
3380
- const lamNl = [];
3381
- for (let k = 0; k < kept.length; k++) {
3382
- const s = sats[kept[k]];
3383
- const lNl = nlWavelength(s.f1, s.f2);
3384
- lamNl.push(lNl);
3385
- const n1 = s.aIF / lNl - s.f2 / (s.f1 - s.f2) * nWl[k] - (s.nlBiasCyc ?? 0);
3386
- n1Float.push(n1);
3387
- }
3388
- let ref = 0;
3389
- for (let k = 1; k < kept.length; k++) {
3390
- const e = sats[kept[k]].elevDeg ?? -1;
3391
- if (e > (sats[kept[ref]].elevDeg ?? -1)) ref = k;
3392
- }
3393
- const m = kept.length - 1;
3394
- const sd = new Float64Array(m);
3395
- const Qsd = new Float64Array(m * m);
3396
- const others = [];
3397
- for (let k = 0; k < kept.length; k++) if (k !== ref) others.push(k);
3398
- const n = sats.length;
3399
- const qN1 = (ka, kb) => Qaif[kept[ka] * n + kept[kb]] / (lamNl[ka] * lamNl[kb]);
3400
- for (let a = 0; a < m; a++) {
3401
- const ka = others[a];
3402
- sd[a] = n1Float[ka] - n1Float[ref];
3403
- for (let b = 0; b < m; b++) {
3404
- const kb = others[b];
3405
- Qsd[a * m + b] = qN1(ka, kb) - qN1(ka, ref) - qN1(ref, kb) + qN1(ref, ref);
3406
- }
3407
- }
3408
- const res = lambdaSearch(sd, Qsd, m, 2);
3409
- const ratio = res ? Math.min(res.ratio, 999.9) : 0;
3410
- const fixed = !!res && ratio >= ratioThreshold;
3411
- const refN1 = Math.round(n1Float[ref]);
3412
- const sdFix = res?.candidates[0];
3413
- const outSats = [];
3414
- for (let k = 0; k < kept.length; k++) {
3415
- const s = sats[kept[k]];
3416
- let n1;
3417
- if (k === ref) {
3418
- n1 = refN1;
3419
- } else {
3420
- const a = others.indexOf(k);
3421
- n1 = fixed && sdFix ? refN1 + Math.round(sdFix[a]) : Math.round(n1Float[k]);
3422
- }
3423
- const lNl = lamNl[k];
3424
- const aIF = lNl * n1 + lNl * (s.f2 / (s.f1 - s.f2)) * nWl[k];
3425
- outSats.push({ prn: s.prn, nWl: nWl[k], n1, aIF });
3426
- }
3427
- return {
3428
- fixed,
3429
- ratio,
3430
- refPrn: sats[kept[ref]].prn,
3431
- sats: outSats,
3432
- rejectedWl
3433
- };
3434
- }
3435
- function solveSpd(A, b) {
3436
- const n = b.length;
3437
- const L = Array.from({ length: n }, () => new Array(n).fill(0));
3438
- for (let i = 0; i < n; i++) {
3439
- for (let j = 0; j <= i; j++) {
3440
- let s = A[i][j];
3441
- for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
3442
- if (i === j) {
3443
- if (s <= 0) return null;
3444
- L[i][j] = Math.sqrt(s);
3445
- } else {
3446
- L[i][j] = s / L[j][j];
3447
- }
3448
- }
3449
- }
3450
- const y = new Array(n).fill(0);
3451
- for (let i = 0; i < n; i++) {
3452
- let s = b[i];
3453
- for (let k = 0; k < i; k++) s -= L[i][k] * y[k];
3454
- y[i] = s / L[i][i];
3455
- }
3456
- const x = new Array(n).fill(0);
3457
- for (let i = n - 1; i >= 0; i--) {
3458
- let s = y[i];
3459
- for (let k = i + 1; k < n; k++) s -= L[k][i] * x[k];
3460
- x[i] = s / L[i][i];
3461
- }
3462
- return x;
3463
- }
3464
- function fixPppPosition(state, fcb, opts = {}) {
3465
- const floatPos = state.position;
3466
- const noFix = {
3467
- fixed: false,
3468
- position: floatPos,
3469
- floatPosition: floatPos,
3470
- shift: [0, 0, 0],
3471
- nFixed: 0,
3472
- ratio: 0,
3473
- refPrn: null
3474
- };
3475
- const amb = state.ambiguities;
3476
- if (amb.length < 3) return noFix;
3477
- const sats = amb.map((a) => ({
3478
- prn: a.prn,
3479
- aIF: a.aIF,
3480
- mwCyc: a.mwCyc,
3481
- f1: a.f1,
3482
- f2: a.f2,
3483
- wlBiasCyc: fcb.satWlFcb.get(a.prn) ?? 0,
3484
- nlBiasCyc: fcb.satNlFcb.get(a.prn) ?? 0,
3485
- elevDeg: a.elevDeg
3486
- }));
3487
- const n = sats.length;
3488
- const Qaif = new Float64Array(n * n);
3489
- for (let i = 0; i < n; i++)
3490
- for (let j = 0; j < n; j++)
3491
- Qaif[i * n + j] = state.covariance[amb[i].index][amb[j].index];
3492
- const res = resolvePppAmbiguities(sats, Qaif, opts);
3493
- if (!res.fixed || res.refPrn === null) {
3494
- return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
3495
- }
3496
- const idxOfPrn = new Map(amb.map((a) => [a.prn, a.index]));
3497
- const floatOfPrn = new Map(amb.map((a) => [a.prn, a.aIF]));
3498
- const kept = res.sats.filter((s) => idxOfPrn.has(s.prn));
3499
- const refPos = kept.findIndex((s) => s.prn === res.refPrn);
3500
- if (refPos < 0 || kept.length < 2) {
3501
- return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
3502
- }
3503
- const ambIdx = kept.map((s) => idxOfPrn.get(s.prn));
3504
- const aFloat = kept.map((s) => floatOfPrn.get(s.prn));
3505
- const aFixed = kept.map((s) => s.aIF);
3506
- const others = kept.map((_, i) => i).filter((i) => i !== refPos);
3507
- const m = others.length;
3508
- const P = state.covariance;
3509
- const qAA = (i, j) => P[ambIdx[i]][ambIdx[j]];
3510
- const qXA = (r, i) => P[r][ambIdx[i]];
3511
- const Qzz = Array.from({ length: m }, () => new Array(m).fill(0));
3512
- const z = new Array(m).fill(0);
3513
- for (let a = 0; a < m; a++) {
3514
- const ia = others[a];
3515
- z[a] = aFloat[ia] - aFloat[refPos] - (aFixed[ia] - aFixed[refPos]);
3516
- for (let b = 0; b < m; b++) {
3517
- const ib = others[b];
3518
- Qzz[a][b] = qAA(ia, ib) - qAA(ia, refPos) - qAA(refPos, ib) + qAA(refPos, refPos);
3519
- }
3520
- }
3521
- const Qxz = [0, 1, 2].map(
3522
- (r) => others.map((ia) => qXA(r, ia) - qXA(r, refPos))
3523
- );
3524
- const y = solveSpd(Qzz, z);
3525
- if (!y) return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
3526
- const shift = [0, 0, 0];
3527
- for (let r = 0; r < 3; r++) {
3528
- let s = 0;
3529
- for (let a = 0; a < m; a++) s += Qxz[r][a] * y[a];
3530
- shift[r] = -s;
3531
- }
3532
- return {
3533
- fixed: true,
3534
- position: [
3535
- floatPos[0] + shift[0],
3536
- floatPos[1] + shift[1],
3537
- floatPos[2] + shift[2]
3538
- ],
3539
- floatPosition: floatPos,
3540
- shift,
3541
- nFixed: m,
3542
- ratio: res.ratio,
3543
- refPrn: res.refPrn
3544
- };
3545
- }
3546
-
3547
3841
  // src/positioning/ppp-fcb.ts
3548
3842
  function extractWidelaneArcs(station, epochs, opts = {}) {
3549
3843
  const slipCyc = opts.slipCyc ?? 4;
@@ -4077,6 +4371,10 @@ export {
4077
4371
  SbasProcessor,
4078
4372
  applyOrbitClock,
4079
4373
  hasToOrbitClock,
4374
+ wlWavelength,
4375
+ nlWavelength,
4376
+ resolvePppAmbiguities,
4377
+ fixPppPosition,
4080
4378
  niellMapping,
4081
4379
  gmst,
4082
4380
  sunEcef,
@@ -4086,10 +4384,6 @@ export {
4086
4384
  Sp3EphemerisSource,
4087
4385
  PppEngine,
4088
4386
  solvePpp,
4089
- wlWavelength,
4090
- nlWavelength,
4091
- resolvePppAmbiguities,
4092
- fixPppPosition,
4093
4387
  extractWidelaneArcs,
4094
4388
  estimateWidelaneFcb,
4095
4389
  estimateNarrowlaneFcb,