gnss-js 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(src_exports, {
24
24
  BAND_LABELS: () => BAND_LABELS,
25
25
  BAND_PRESETS: () => BAND_PRESETS,
26
26
  BDS_SATELLITES: () => BDS_SATELLITES,
27
+ BLQ_CONSTITUENTS: () => BLQ_CONSTITUENTS,
27
28
  BitReader: () => BitReader,
28
29
  CONSTELLATIONS: () => CONSTELLATIONS,
29
30
  C_LIGHT: () => C_LIGHT,
@@ -121,6 +122,7 @@ __export(src_exports, {
121
122
  analyzeQuality: () => analyzeQuality,
122
123
  antexFreqOfGroup: () => antexFreqOfGroup,
123
124
  applyHelmert: () => applyHelmert,
125
+ applyHelmertFit: () => applyHelmertFit,
124
126
  applyIonoDcb: () => applyIonoDcb,
125
127
  applyOrbitClock: () => applyOrbitClock,
126
128
  biasMetres: () => biasMetres,
@@ -168,6 +170,7 @@ __export(src_exports, {
168
170
  ecefToAzEl: () => ecefToAzEl,
169
171
  ecefToGeodetic: () => ecefToGeodetic,
170
172
  ephInfoToEphemeris: () => ephInfoToEphemeris,
173
+ estimateHelmert: () => estimateHelmert,
171
174
  estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
172
175
  estimateNetworkFcbs: () => estimateNetworkFcbs,
173
176
  estimateWidelaneFcb: () => estimateWidelaneFcb,
@@ -179,6 +182,7 @@ __export(src_exports, {
179
182
  feedNavicBlock: () => feedNavicBlock,
180
183
  fetchSourcetable: () => fetchSourcetable,
181
184
  findSatBias: () => findSatBias,
185
+ fitCoordinateSeries: () => fitCoordinateSeries,
182
186
  fixPppPosition: () => fixPppPosition,
183
187
  fmtD: () => fmtD,
184
188
  fmtF: () => fmtF,
@@ -273,12 +277,14 @@ __export(src_exports, {
273
277
  nlWavelength: () => nlWavelength,
274
278
  nmeaCoordToDecimal: () => nmeaCoordToDecimal,
275
279
  obsEpochToDate: () => obsEpochToDate,
280
+ oceanLoadingEnu: () => oceanLoadingEnu,
276
281
  oem4Frames: () => oem4Frames,
277
282
  padL: () => padL,
278
283
  padR: () => padR,
279
284
  parseAntex: () => parseAntex,
280
285
  parseBiasSinex: () => parseBiasSinex,
281
286
  parseBinex: () => parseBinex,
287
+ parseBlq: () => parseBlq,
282
288
  parseClk: () => parseClk,
283
289
  parseCrxDataLine: () => parseCrxDataLine,
284
290
  parseIonex: () => parseIonex,
@@ -16804,6 +16810,103 @@ function solidEarthTide(rcv, sun, moon) {
16804
16810
  return disp;
16805
16811
  }
16806
16812
 
16813
+ // src/positioning/ocean-loading.ts
16814
+ var BLQ_CONSTITUENTS = [
16815
+ "M2",
16816
+ "S2",
16817
+ "N2",
16818
+ "K2",
16819
+ "K1",
16820
+ "O1",
16821
+ "P1",
16822
+ "Q1",
16823
+ "Mf",
16824
+ "Mm",
16825
+ "Ssa"
16826
+ ];
16827
+ var ARGS = [
16828
+ [140519e-9, 2, -2, 0, 0],
16829
+ // M2
16830
+ [145444e-9, 0, 0, 0, 0],
16831
+ // S2
16832
+ [13788e-8, 2, -3, 1, 0],
16833
+ // N2
16834
+ [145842e-9, 2, 0, 0, 0],
16835
+ // K2
16836
+ [72921e-9, 1, 0, 0, 0.25],
16837
+ // K1
16838
+ [67598e-9, 1, -2, 0, -0.25],
16839
+ // O1
16840
+ [72523e-9, -1, 0, 0, -0.25],
16841
+ // P1
16842
+ [64959e-9, 1, -3, 1, -0.25],
16843
+ // Q1
16844
+ [53234e-10, 0, 2, 0, 0],
16845
+ // Mf
16846
+ [26392e-10, 0, 1, -1, 0],
16847
+ // Mm
16848
+ [3982e-10, 2, 0, 0, 0]
16849
+ // Ssa
16850
+ ];
16851
+ var D2R2 = Math.PI / 180;
16852
+ function oceanLoadingEnu(c, utcMs) {
16853
+ const day = Math.floor(utcMs / 864e5);
16854
+ const fday = (utcMs - day * 864e5) / 1e3;
16855
+ const days = day - Date.UTC(1975, 0, 1) / 864e5 + 1;
16856
+ const t = (27392.500528 + 1.000000035 * days) / 36525;
16857
+ const t2 = t * t;
16858
+ const t3 = t2 * t;
16859
+ const a = [
16860
+ fday,
16861
+ (279.69668 + 36000.768930485 * t + 303e-6 * t2) * D2R2,
16862
+ // H0
16863
+ (270.434358 + 481267.88314137 * t - 1133e-6 * t2 + 19e-7 * t3) * D2R2,
16864
+ // S0
16865
+ (334.329653 + 4069.0340329577 * t - 0.010325 * t2 - 12e-6 * t3) * D2R2,
16866
+ // P0
16867
+ 2 * Math.PI
16868
+ ];
16869
+ const dp = [0, 0, 0];
16870
+ for (let i = 0; i < ARGS.length; i++) {
16871
+ let ang = 0;
16872
+ for (let j = 0; j < 5; j++) ang += a[j] * ARGS[i][j];
16873
+ for (let k = 0; k < 3; k++)
16874
+ dp[k] += c.amplitude[i][k] * Math.cos(ang - c.phase[i][k] * D2R2);
16875
+ }
16876
+ return [-dp[1], -dp[2], dp[0]];
16877
+ }
16878
+ function parseBlq(text) {
16879
+ const out = /* @__PURE__ */ new Map();
16880
+ const lines = text.split(/\r?\n/);
16881
+ for (let i = 0; i < lines.length; i++) {
16882
+ const l = lines[i];
16883
+ if (!l.trim() || l.startsWith("$$")) continue;
16884
+ const name = l.trim().split(/\s+/)[0].toUpperCase();
16885
+ const rows = [];
16886
+ let j = i + 1;
16887
+ while (j < lines.length && rows.length < 6) {
16888
+ const r = lines[j];
16889
+ j++;
16890
+ if (r.startsWith("$$") || !r.trim()) continue;
16891
+ const v = r.trim().split(/\s+/).map(Number);
16892
+ if (v.length !== 11 || v.some((x) => !Number.isFinite(x))) break;
16893
+ rows.push(v);
16894
+ }
16895
+ if (rows.length === 6) {
16896
+ out.set(name, {
16897
+ amplitude: BLQ_CONSTITUENTS.map(
16898
+ (_, k) => [rows[0][k], rows[1][k], rows[2][k]]
16899
+ ),
16900
+ phase: BLQ_CONSTITUENTS.map(
16901
+ (_, k) => [rows[3][k], rows[4][k], rows[5][k]]
16902
+ )
16903
+ });
16904
+ i = j - 1;
16905
+ }
16906
+ }
16907
+ return out;
16908
+ }
16909
+
16807
16910
  // src/positioning/ppp-corrections.ts
16808
16911
  function applyCorrections(corrections, geom, ctx) {
16809
16912
  if (corrections.evaluate) return corrections.evaluate(geom, ctx);
@@ -16821,6 +16924,14 @@ function createPppCorrections(cfg) {
16821
16924
  const sun = sunEcef(timeMs);
16822
16925
  const moon = moonEcef(timeMs);
16823
16926
  const tideDisp = useTides ? solidEarthTide(rcvEcef, sun, moon) : [0, 0, 0];
16927
+ if (cfg.oceanLoading) {
16928
+ const [e, n, u] = oceanLoadingEnu(cfg.oceanLoading, timeMs);
16929
+ const [lat, lon] = ecefToGeodetic(rcvEcef[0], rcvEcef[1], rcvEcef[2]);
16930
+ const d = enuToEcef([e, n, u], lat, lon);
16931
+ tideDisp[0] += d[0];
16932
+ tideDisp[1] += d[1];
16933
+ tideDisp[2] += d[2];
16934
+ }
16824
16935
  return { timeMs, sun, moon, tideDisp };
16825
16936
  },
16826
16937
  evaluate(geom, ctx) {
@@ -16875,7 +16986,7 @@ function createPppCorrections(cfg) {
16875
16986
  rangeM += interpPcv(off, zenithDeg);
16876
16987
  }
16877
16988
  }
16878
- if (useTides && ctx) {
16989
+ if (ctx) {
16879
16990
  rangeM -= dot(e, ctx.tideDisp);
16880
16991
  }
16881
16992
  if (useWindup && frame) {
@@ -17261,7 +17372,7 @@ var PppEngine = class {
17261
17372
  for (const ci of clkIdx.values()) P[ci][ci] = CLK_VAR;
17262
17373
  P[IDX_ZWD][IDX_ZWD] += ztdQ * (ei > 0 ? 1 : 0);
17263
17374
  const rcv = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
17264
- const [latRad, , heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
17375
+ const [latRad, lonRad, heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
17265
17376
  const epochCtx = corrections?.epochContext?.(epoch.timeMs, rcv) ?? null;
17266
17377
  const vis = [];
17267
17378
  let epochZhd = 0;
@@ -17294,7 +17405,17 @@ var PppEngine = class {
17294
17405
  const pIf = g * o.c1 - (g - 1) * o.c2;
17295
17406
  const lIf = g * (o.l1 * lam1) - (g - 1) * (o.l2 * lam2);
17296
17407
  if (!Number.isFinite(pIf) || !Number.isFinite(lIf)) continue;
17297
- const trop = opts.troposphere ?? true ? niellMapping(elRad, latRad, heightM, epoch.timeMs) : { zhd: 0, mh: 0, mw: 0 };
17408
+ const trop = opts.troposphere ?? true ? opts.troposphereModel ? opts.troposphereModel.delays(
17409
+ epoch.timeMs,
17410
+ latRad,
17411
+ lonRad,
17412
+ heightM,
17413
+ elRad,
17414
+ azRad
17415
+ ) : {
17416
+ ...niellMapping(elRad, latRad, heightM, epoch.timeMs),
17417
+ gradient: 0
17418
+ } : { zhd: 0, mh: 0, mw: 0, gradient: 0 };
17298
17419
  const { zhd, mh, mw } = trop;
17299
17420
  epochZhd = zhd;
17300
17421
  const geom = {
@@ -17319,7 +17440,7 @@ var PppEngine = class {
17319
17440
  lIf,
17320
17441
  satClkM: sat.state.clkM,
17321
17442
  range,
17322
- tropoFixed: zhd * mh + corr.rangeM,
17443
+ tropoFixed: zhd * mh + trop.gradient + corr.rangeM,
17323
17444
  mw,
17324
17445
  windupM: corr.phaseWindupM,
17325
17446
  slip,
@@ -19314,6 +19435,495 @@ async function analyzeQuality(file, header, onProgress, signal) {
19314
19435
  };
19315
19436
  }
19316
19437
 
19438
+ // src/analysis/noise-model.ts
19439
+ function cholesky(M, n) {
19440
+ for (let j = 0; j < n; j++) {
19441
+ let d = M[j * n + j];
19442
+ for (let k = 0; k < j; k++) d -= M[j * n + k] ** 2;
19443
+ if (d <= 0) return false;
19444
+ d = Math.sqrt(d);
19445
+ M[j * n + j] = d;
19446
+ for (let i = j + 1; i < n; i++) {
19447
+ let s = M[i * n + j];
19448
+ for (let k = 0; k < j; k++) s -= M[i * n + k] * M[j * n + k];
19449
+ M[i * n + j] = s / d;
19450
+ }
19451
+ }
19452
+ return true;
19453
+ }
19454
+ function forward(L, n, b) {
19455
+ for (let i = 0; i < n; i++) {
19456
+ let s = b[i];
19457
+ for (let k = 0; k < i; k++) s -= L[i * n + k] * b[k];
19458
+ b[i] = s / L[i * n + i];
19459
+ }
19460
+ }
19461
+ function flickerCovariance(idx) {
19462
+ const N = idx[idx.length - 1] + 1;
19463
+ const h = new Float64Array(N);
19464
+ h[0] = 1;
19465
+ for (let i = 1; i < N; i++) h[i] = h[i - 1] * (i - 1 + 0.5) / i;
19466
+ const n = idx.length;
19467
+ const F = new Float64Array(n * n);
19468
+ for (let a = 0; a < n; a++)
19469
+ for (let b = 0; b <= a; b++) {
19470
+ const ia = idx[a];
19471
+ const ib = idx[b];
19472
+ let s = 0;
19473
+ for (let k = 0; k <= ib; k++) s += h[ia - k] * h[ib - k];
19474
+ F[a * n + b] = s;
19475
+ F[b * n + a] = s;
19476
+ }
19477
+ let tr = 0;
19478
+ for (let a = 0; a < n; a++) tr += F[a * n + a];
19479
+ const scale = n / tr;
19480
+ for (let i = 0; i < F.length; i++) F[i] *= scale;
19481
+ return F;
19482
+ }
19483
+ var MAX_SAMPLES = 360;
19484
+ function fitWhiteFlicker(A, y, tYears) {
19485
+ let rows = A.map((_, i) => i);
19486
+ if (rows.length > MAX_SAMPLES) {
19487
+ const step = rows.length / MAX_SAMPLES;
19488
+ rows = Array.from({ length: MAX_SAMPLES }, (_, k) => Math.floor(k * step));
19489
+ }
19490
+ const n = rows.length;
19491
+ const p = A[0]?.length ?? 0;
19492
+ if (n < p + 5) return null;
19493
+ const dts = rows.slice(1).map((r, k) => tYears[r] - tYears[rows[k]]).filter((d) => d > 0).sort((a, b) => a - b);
19494
+ const dt = dts[dts.length >> 1] ?? 1 / 365.25;
19495
+ const t0 = tYears[rows[0]];
19496
+ const idx = rows.map((r) => Math.round((tYears[r] - t0) / dt));
19497
+ for (let k = 1; k < n; k++)
19498
+ if (idx[k] <= idx[k - 1]) idx[k] = idx[k - 1] + 1;
19499
+ const F = flickerCovariance(idx);
19500
+ const evaluate = (phi) => {
19501
+ const C2 = new Float64Array(n * n);
19502
+ for (let i = 0; i < n * n; i++) C2[i] = phi * F[i];
19503
+ for (let i = 0; i < n; i++) C2[i * n + i] += 1 - phi;
19504
+ if (!cholesky(C2, n)) return null;
19505
+ const Aw = Array.from({ length: p }, (_, j) => {
19506
+ const col = Float64Array.from(rows, (r) => A[r][j]);
19507
+ forward(C2, n, col);
19508
+ return col;
19509
+ });
19510
+ const yw = Float64Array.from(rows, (r) => y[r]);
19511
+ forward(C2, n, yw);
19512
+ const N = Array.from(
19513
+ { length: p },
19514
+ (_, a) => Array.from({ length: p }, (_2, b) => {
19515
+ let s = 0;
19516
+ for (let i = 0; i < n; i++) s += Aw[a][i] * Aw[b][i];
19517
+ return s;
19518
+ })
19519
+ );
19520
+ const rhs = Array.from({ length: p }, (_, a) => {
19521
+ let s = 0;
19522
+ for (let i = 0; i < n; i++) s += Aw[a][i] * yw[i];
19523
+ return s;
19524
+ });
19525
+ const Ninv = invert(N);
19526
+ if (!Ninv) return null;
19527
+ const beta = Ninv.map((row) => row.reduce((s, v, k) => s + v * rhs[k], 0));
19528
+ let rss = 0;
19529
+ for (let i = 0; i < n; i++) {
19530
+ let m = 0;
19531
+ for (let j = 0; j < p; j++) m += Aw[j][i] * beta[j];
19532
+ rss += (yw[i] - m) ** 2;
19533
+ }
19534
+ const sigma2 = rss / n;
19535
+ let logdet = 0;
19536
+ for (let i = 0; i < n; i++) logdet += 2 * Math.log(C2[i * n + i]);
19537
+ const logL = -0.5 * (n * Math.log(sigma2) + logdet + n);
19538
+ return {
19539
+ logL,
19540
+ velocity: beta[1],
19541
+ velocitySigma: Math.sqrt(sigma2 * Ninv[1][1])
19542
+ };
19543
+ };
19544
+ let best = null;
19545
+ for (let k = 0; k <= 20; k++) {
19546
+ const phi = Math.min(k / 20, 0.995);
19547
+ const r = evaluate(phi);
19548
+ if (r && (!best || r.logL > best.r.logL)) best = { phi, r };
19549
+ }
19550
+ if (!best) return null;
19551
+ let lo = Math.max(0, best.phi - 0.05);
19552
+ let hi = Math.min(0.995, best.phi + 0.05);
19553
+ const g = (Math.sqrt(5) - 1) / 2;
19554
+ for (let it = 0; it < 12; it++) {
19555
+ const a = hi - g * (hi - lo);
19556
+ const b = lo + g * (hi - lo);
19557
+ const ra = evaluate(a);
19558
+ const rb = evaluate(b);
19559
+ if (!ra || !rb) break;
19560
+ if (ra.logL > rb.logL) {
19561
+ hi = b;
19562
+ if (ra.logL > best.r.logL) best = { phi: a, r: ra };
19563
+ } else {
19564
+ lo = a;
19565
+ if (rb.logL > best.r.logL) best = { phi: b, r: rb };
19566
+ }
19567
+ }
19568
+ return {
19569
+ flickerShare: best.phi,
19570
+ velocitySigma: best.r.velocitySigma,
19571
+ velocity: best.r.velocity
19572
+ };
19573
+ }
19574
+ function invert(N) {
19575
+ const p = N.length;
19576
+ const M = N.map((row, i) => [
19577
+ ...row,
19578
+ ...Array.from({ length: p }, (_, j) => i === j ? 1 : 0)
19579
+ ]);
19580
+ for (let c = 0; c < p; c++) {
19581
+ let piv = c;
19582
+ for (let r = c + 1; r < p; r++)
19583
+ if (Math.abs(M[r][c]) > Math.abs(M[piv][c])) piv = r;
19584
+ if (Math.abs(M[piv][c]) < 1e-14) return null;
19585
+ [M[c], M[piv]] = [M[piv], M[c]];
19586
+ const d = M[c][c];
19587
+ for (let k = 0; k < 2 * p; k++) M[c][k] /= d;
19588
+ for (let r = 0; r < p; r++) {
19589
+ if (r === c) continue;
19590
+ const f = M[r][c];
19591
+ if (f === 0) continue;
19592
+ for (let k = 0; k < 2 * p; k++) M[r][k] -= f * M[c][k];
19593
+ }
19594
+ }
19595
+ return M.map((row) => row.slice(p));
19596
+ }
19597
+
19598
+ // src/analysis/coordinate-series.ts
19599
+ var MS_PER_YEAR = 365.25 * 864e5;
19600
+ var TWO_PI2 = 2 * Math.PI;
19601
+ var MIN_SIGMA_M = 5e-4;
19602
+ function lsq(A, y, w) {
19603
+ const p = A[0]?.length ?? 0;
19604
+ const N = Array.from({ length: p }, () => new Array(p).fill(0));
19605
+ const b = new Array(p).fill(0);
19606
+ for (let i = 0; i < A.length; i++) {
19607
+ const a = A[i];
19608
+ for (let j = 0; j < p; j++) {
19609
+ b[j] += w[i] * a[j] * y[i];
19610
+ for (let k = 0; k < p; k++) N[j][k] += w[i] * a[j] * a[k];
19611
+ }
19612
+ }
19613
+ const M = N.map((row, i) => [
19614
+ ...row,
19615
+ ...Array.from({ length: p }, (_, j) => i === j ? 1 : 0)
19616
+ ]);
19617
+ for (let c = 0; c < p; c++) {
19618
+ let piv = c;
19619
+ for (let r = c + 1; r < p; r++)
19620
+ if (Math.abs(M[r][c]) > Math.abs(M[piv][c])) piv = r;
19621
+ if (Math.abs(M[piv][c]) < 1e-12) return null;
19622
+ [M[c], M[piv]] = [M[piv], M[c]];
19623
+ const d = M[c][c];
19624
+ for (let k = 0; k < 2 * p; k++) M[c][k] /= d;
19625
+ for (let r = 0; r < p; r++) {
19626
+ if (r === c) continue;
19627
+ const f = M[r][c];
19628
+ if (f === 0) continue;
19629
+ for (let k = 0; k < 2 * p; k++) M[r][k] -= f * M[c][k];
19630
+ }
19631
+ }
19632
+ const Ninv = M.map((row) => row.slice(p));
19633
+ const x = Ninv.map((row) => row.reduce((s, v, k) => s + v * b[k], 0));
19634
+ return { x, Ninv };
19635
+ }
19636
+ function median3(xs) {
19637
+ const s = [...xs].sort((a, b) => a - b);
19638
+ const m = s.length >> 1;
19639
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
19640
+ }
19641
+ function robustSigma(xs) {
19642
+ if (xs.length < 2) return 0;
19643
+ const m = median3(xs);
19644
+ return 1.4826 * median3(xs.map((x) => Math.abs(x - m)));
19645
+ }
19646
+ function designRow(tMs, d) {
19647
+ const dt = (tMs - d.refEpochMs) / MS_PER_YEAR;
19648
+ const row = [1, dt];
19649
+ for (const j of d.jumps) row.push(tMs >= j ? 1 : 0);
19650
+ if (d.seasonal) {
19651
+ const ph = TWO_PI2 * (tMs / MS_PER_YEAR);
19652
+ row.push(Math.sin(ph), Math.cos(ph), Math.sin(2 * ph), Math.cos(2 * ph));
19653
+ }
19654
+ return row;
19655
+ }
19656
+ function fitComponent(t, y, w, used, d) {
19657
+ const A = [];
19658
+ const yy = [];
19659
+ const ww = [];
19660
+ for (let i = 0; i < t.length; i++) {
19661
+ if (!used[i]) continue;
19662
+ A.push(designRow(t[i], d));
19663
+ yy.push(y[i]);
19664
+ ww.push(w[i]);
19665
+ }
19666
+ if (A.length <= (A[0]?.length ?? 0)) return null;
19667
+ const s = lsq(A, yy, ww);
19668
+ if (!s) return null;
19669
+ const residuals = t.map(
19670
+ (ti, i) => y[i] - designRow(ti, d).reduce((acc, a, k) => acc + a * s.x[k], 0)
19671
+ );
19672
+ let wrss = 0;
19673
+ let sumW = 0;
19674
+ for (let i = 0; i < t.length; i++) {
19675
+ if (!used[i]) continue;
19676
+ wrss += w[i] * residuals[i] ** 2;
19677
+ sumW += w[i];
19678
+ }
19679
+ return { ...s, wrss, sumW, residuals };
19680
+ }
19681
+ function fitCoordinateSeries(samples, opts = {}) {
19682
+ const pts = [...samples].sort((a, b) => a.tMs - b.tMs);
19683
+ if (pts.length < 5) return null;
19684
+ const t = pts.map((p) => p.tMs);
19685
+ const spanYears = (t[t.length - 1] - t[0]) / MS_PER_YEAR;
19686
+ if (spanYears <= 0) return null;
19687
+ const ref = [0, 1, 2].map(
19688
+ (k2) => median3(pts.map((p) => p.ecef[k2]))
19689
+ );
19690
+ const [lat, lon] = ecefToGeodetic(ref[0], ref[1], ref[2]);
19691
+ const sl = Math.sin(lat);
19692
+ const cl = Math.cos(lat);
19693
+ const so = Math.sin(lon);
19694
+ const co = Math.cos(lon);
19695
+ const enu = pts.map((p) => {
19696
+ const dx = p.ecef[0] - ref[0];
19697
+ const dy = p.ecef[1] - ref[1];
19698
+ const dz = p.ecef[2] - ref[2];
19699
+ return [
19700
+ -so * dx + co * dy,
19701
+ -sl * co * dx - sl * so * dy + cl * dz,
19702
+ cl * co * dx + cl * so * dy + sl * dz
19703
+ ];
19704
+ });
19705
+ const weights = [0, 1, 2].map(
19706
+ (k2) => pts.map((p) => {
19707
+ const s = p.sigmaEnu?.[k2];
19708
+ return s && s > 0 ? 1 / Math.max(s, MIN_SIGMA_M) ** 2 : 1;
19709
+ })
19710
+ );
19711
+ const series = [0, 1, 2].map((k2) => enu.map((v) => v[k2]));
19712
+ const seasonal = opts.seasonal === "auto" || opts.seasonal === void 0 ? spanYears >= 2 : opts.seasonal;
19713
+ const design = {
19714
+ refEpochMs: opts.refEpochMs ?? (t[0] + t[t.length - 1]) / 2,
19715
+ jumps: [...opts.jumps ?? []].filter((j) => j > t[0] && j <= t[t.length - 1]).sort((a, b) => a - b),
19716
+ seasonal
19717
+ };
19718
+ const detected = /* @__PURE__ */ new Set();
19719
+ const k = opts.outlierK ?? 4;
19720
+ const used = pts.map(() => true);
19721
+ const fitAll = () => [0, 1, 2].map(
19722
+ (c) => fitComponent(t, series[c], weights[c], used, design)
19723
+ );
19724
+ const robustPass = () => {
19725
+ for (let iter = 0; iter < 10; iter++) {
19726
+ const sol2 = fitAll();
19727
+ if (sol2.some((s) => !s)) return null;
19728
+ const scales = sol2.map(
19729
+ (s) => Math.max(robustSigma(s.residuals.filter((_, i) => used[i])), 1e-4)
19730
+ );
19731
+ let changed = false;
19732
+ for (let i = 0; i < t.length; i++) {
19733
+ const bad = sol2.some(
19734
+ (s, c) => Math.abs(s.residuals[i]) > k * scales[c]
19735
+ );
19736
+ if (bad === used[i]) {
19737
+ used[i] = !bad;
19738
+ changed = true;
19739
+ }
19740
+ }
19741
+ if (!changed) return sol2;
19742
+ }
19743
+ return fitAll();
19744
+ };
19745
+ let sol = robustPass();
19746
+ if (!sol) return null;
19747
+ if (opts.detectJumps ?? true) {
19748
+ const minSide = opts.minSamplesPerSide ?? 5;
19749
+ for (let added = 0; added < (opts.maxJumps ?? 3); added++) {
19750
+ const idx = t.map((_, i) => i).filter((i) => used[i]);
19751
+ const nUsed = idx.length;
19752
+ const bicOf = (s, p) => s.reduce((acc, c) => acc + nUsed * Math.log(c.wrss / c.sumW), 0) + 3 * p * Math.log(nUsed);
19753
+ const p0 = designRow(t[0], design).length;
19754
+ const bic0 = bicOf(sol, p0);
19755
+ let best = null;
19756
+ for (let j = minSide; j <= nUsed - minSide; j++) {
19757
+ const tj = (t[idx[j - 1]] + t[idx[j]]) / 2;
19758
+ if (design.jumps.some((x) => Math.abs(x - tj) < 7 * 864e5))
19759
+ continue;
19760
+ const trial = {
19761
+ ...design,
19762
+ jumps: [...design.jumps, tj].sort((a, b) => a - b)
19763
+ };
19764
+ const s = [0, 1, 2].map(
19765
+ (c) => fitComponent(t, series[c], weights[c], used, trial)
19766
+ );
19767
+ if (s.some((x) => !x)) continue;
19768
+ const bic = bicOf(s, p0 + 1);
19769
+ if (!best || bic < best.bic)
19770
+ best = { tMs: tj, bic, sol: s };
19771
+ }
19772
+ if (!best || bic0 - best.bic <= 10) break;
19773
+ design.jumps = [...design.jumps, best.tMs].sort((a, b) => a - b);
19774
+ detected.add(best.tMs);
19775
+ sol = robustPass();
19776
+ if (!sol) return null;
19777
+ }
19778
+ }
19779
+ const nJ = design.jumps.length;
19780
+ const component = (c) => {
19781
+ const s = sol[c];
19782
+ const nUsed = used.filter(Boolean).length;
19783
+ const p = s.x.length;
19784
+ const vf = nUsed > p ? s.wrss / (nUsed - p) : 1;
19785
+ const amp = (i) => seasonal ? Math.hypot(s.x[i], s.x[i + 1]) : null;
19786
+ const noise = opts.noiseModel ?? true ? fitWhiteFlicker(
19787
+ t.flatMap((ti, i) => used[i] ? [designRow(ti, design)] : []),
19788
+ series[c].filter((_, i) => used[i]),
19789
+ t.flatMap((ti, i) => used[i] ? [ti / MS_PER_YEAR] : [])
19790
+ ) : null;
19791
+ return {
19792
+ offset: s.x[0],
19793
+ velocity: s.x[1],
19794
+ velocitySigma: Math.sqrt(s.Ninv[1][1] * vf),
19795
+ velocitySigmaRealistic: noise?.velocitySigma ?? null,
19796
+ flickerShare: noise?.flickerShare ?? null,
19797
+ steps: s.x.slice(2, 2 + nJ),
19798
+ annualAmplitude: amp(2 + nJ),
19799
+ semiannualAmplitude: amp(4 + nJ),
19800
+ wrms: Math.sqrt(s.wrss / s.sumW)
19801
+ };
19802
+ };
19803
+ const points = pts.map((p, i) => ({
19804
+ tMs: p.tMs,
19805
+ enu: enu[i],
19806
+ model: [0, 1, 2].map((c) => series[c][i] - sol[c].residuals[i]),
19807
+ outlier: !used[i]
19808
+ }));
19809
+ return {
19810
+ refEcef: ref,
19811
+ refEpochMs: design.refEpochMs,
19812
+ e: component(0),
19813
+ n: component(1),
19814
+ u: component(2),
19815
+ jumps: design.jumps.map((tMs) => ({ tMs, detected: detected.has(tMs) })),
19816
+ points,
19817
+ spanYears,
19818
+ used: used.filter(Boolean).length,
19819
+ outliers: used.filter((u) => !u).length,
19820
+ seasonal
19821
+ };
19822
+ }
19823
+
19824
+ // src/analysis/network-alignment.ts
19825
+ function applyHelmertFit(x, f) {
19826
+ const [r1, r2, r3] = f.r;
19827
+ return [
19828
+ x[0] + f.t[0] + f.d * x[0] - r3 * x[1] + r2 * x[2],
19829
+ x[1] + f.t[1] + r3 * x[0] + f.d * x[1] - r1 * x[2],
19830
+ x[2] + f.t[2] - r2 * x[0] + r1 * x[1] + f.d * x[2]
19831
+ ];
19832
+ }
19833
+ function solve(N, b) {
19834
+ const n = b.length;
19835
+ const M = N.map((row, i) => [...row, b[i]]);
19836
+ for (let c = 0; c < n; c++) {
19837
+ let p = c;
19838
+ for (let r = c + 1; r < n; r++)
19839
+ if (Math.abs(M[r][c]) > Math.abs(M[p][c])) p = r;
19840
+ if (Math.abs(M[p][c]) < 1e-6) return null;
19841
+ [M[c], M[p]] = [M[p], M[c]];
19842
+ for (let r = 0; r < n; r++) {
19843
+ if (r === c) continue;
19844
+ const f = M[r][c] / M[c][c];
19845
+ for (let k = c; k <= n; k++) M[r][k] -= f * M[c][k];
19846
+ }
19847
+ }
19848
+ return M.map((row, i) => row[n] / row[i]);
19849
+ }
19850
+ function estimateHelmert(from, to, opts = {}) {
19851
+ const model = opts.model ?? "translation";
19852
+ const nPar = model === "translation" ? 3 : 7;
19853
+ const minRefs = opts.minRefs ?? (model === "translation" ? 3 : 4);
19854
+ const maxRes = opts.maxResidualM ?? 0.015;
19855
+ if (from.length !== to.length) return null;
19856
+ const used = from.map(() => true);
19857
+ const c = [0, 1, 2].map(
19858
+ (k) => from.reduce((s, p) => s + p[k], 0) / Math.max(from.length, 1)
19859
+ );
19860
+ for (; ; ) {
19861
+ const idx = used.flatMap((u, i) => u ? [i] : []);
19862
+ if (idx.length < minRefs || idx.length * 3 <= nPar) return null;
19863
+ const N = Array.from(
19864
+ { length: nPar },
19865
+ () => new Array(nPar).fill(0)
19866
+ );
19867
+ const b = new Array(nPar).fill(0);
19868
+ for (const i of idx) {
19869
+ const x = [from[i][0] - c[0], from[i][1] - c[1], from[i][2] - c[2]];
19870
+ for (let k = 0; k < 3; k++) {
19871
+ const row = new Array(nPar).fill(0);
19872
+ row[k] = 1;
19873
+ if (nPar === 7) {
19874
+ if (k === 0) {
19875
+ row[4] = x[2];
19876
+ row[5] = -x[1];
19877
+ } else if (k === 1) {
19878
+ row[3] = -x[2];
19879
+ row[5] = x[0];
19880
+ } else {
19881
+ row[3] = x[1];
19882
+ row[4] = -x[0];
19883
+ }
19884
+ row[6] = x[k];
19885
+ }
19886
+ const y = to[i][k] - from[i][k];
19887
+ for (let a = 0; a < nPar; a++) {
19888
+ b[a] += row[a] * y;
19889
+ for (let bb = 0; bb < nPar; bb++) N[a][bb] += row[a] * row[bb];
19890
+ }
19891
+ }
19892
+ }
19893
+ const p = solve(N, b);
19894
+ if (!p) return null;
19895
+ const r = nPar === 7 ? [p[3], p[4], p[5]] : [0, 0, 0];
19896
+ const d = nPar === 7 ? p[6] : 0;
19897
+ const t = [
19898
+ p[0] - (d * c[0] - r[2] * c[1] + r[1] * c[2]),
19899
+ p[1] - (r[2] * c[0] + d * c[1] - r[0] * c[2]),
19900
+ p[2] - (-r[1] * c[0] + r[0] * c[1] + d * c[2])
19901
+ ];
19902
+ const fit = { model, t, r, d, residuals: [], used, rms: 0 };
19903
+ fit.residuals = from.map((x, i) => {
19904
+ const y = applyHelmertFit(x, fit);
19905
+ return [to[i][0] - y[0], to[i][1] - y[1], to[i][2] - y[2]];
19906
+ });
19907
+ let worst = -1;
19908
+ let worstNorm = maxRes;
19909
+ let ss = 0;
19910
+ for (const i of idx) {
19911
+ const n = Math.hypot(...fit.residuals[i]);
19912
+ ss += n * n;
19913
+ if (n > worstNorm) {
19914
+ worstNorm = n;
19915
+ worst = i;
19916
+ }
19917
+ }
19918
+ fit.rms = Math.sqrt(ss / (3 * idx.length));
19919
+ if (worst < 0 || idx.length <= minRefs) {
19920
+ fit.used = [...used];
19921
+ return fit;
19922
+ }
19923
+ used[worst] = false;
19924
+ }
19925
+ }
19926
+
19317
19927
  // src/antex/index.ts
19318
19928
  function label(line) {
19319
19929
  return (line.length >= 80 ? line.substring(60, 80) : line.substring(60)).trimEnd();
@@ -20509,6 +21119,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20509
21119
  BAND_LABELS,
20510
21120
  BAND_PRESETS,
20511
21121
  BDS_SATELLITES,
21122
+ BLQ_CONSTITUENTS,
20512
21123
  BitReader,
20513
21124
  CONSTELLATIONS,
20514
21125
  C_LIGHT,
@@ -20606,6 +21217,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20606
21217
  analyzeQuality,
20607
21218
  antexFreqOfGroup,
20608
21219
  applyHelmert,
21220
+ applyHelmertFit,
20609
21221
  applyIonoDcb,
20610
21222
  applyOrbitClock,
20611
21223
  biasMetres,
@@ -20653,6 +21265,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20653
21265
  ecefToAzEl,
20654
21266
  ecefToGeodetic,
20655
21267
  ephInfoToEphemeris,
21268
+ estimateHelmert,
20656
21269
  estimateNarrowlaneFcb,
20657
21270
  estimateNetworkFcbs,
20658
21271
  estimateWidelaneFcb,
@@ -20664,6 +21277,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20664
21277
  feedNavicBlock,
20665
21278
  fetchSourcetable,
20666
21279
  findSatBias,
21280
+ fitCoordinateSeries,
20667
21281
  fixPppPosition,
20668
21282
  fmtD,
20669
21283
  fmtF,
@@ -20758,12 +21372,14 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20758
21372
  nlWavelength,
20759
21373
  nmeaCoordToDecimal,
20760
21374
  obsEpochToDate,
21375
+ oceanLoadingEnu,
20761
21376
  oem4Frames,
20762
21377
  padL,
20763
21378
  padR,
20764
21379
  parseAntex,
20765
21380
  parseBiasSinex,
20766
21381
  parseBinex,
21382
+ parseBlq,
20767
21383
  parseClk,
20768
21384
  parseCrxDataLine,
20769
21385
  parseIonex,