gnss-js 2.13.0 → 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) {
@@ -17013,7 +17124,32 @@ function sagnacRotate(x, y, z, travelS) {
17013
17124
  const s = Math.sin(theta);
17014
17125
  return [x * c + y * s, -x * s + y * c, z];
17015
17126
  }
17016
- function satStateAtEmission(source, prn, recvTimeMs, rcv) {
17127
+ function satStateAtEmission(source, prn, recvTimeMs, rcv, pseudorangeM) {
17128
+ if (pseudorangeM !== void 0 && pseudorangeM > 1e6) {
17129
+ let tEmit = recvTimeMs - pseudorangeM / C_LIGHT * 1e3;
17130
+ const s0 = source.satState(prn, tEmit);
17131
+ if (!s0) return null;
17132
+ tEmit -= s0.clkS * 1e3;
17133
+ const s = source.satState(prn, tEmit);
17134
+ if (!s) return null;
17135
+ let travelS2 = 0.075;
17136
+ let pos = [s.x, s.y, s.z];
17137
+ for (let iter = 0; iter < 3; iter++) {
17138
+ pos = sagnacRotate(s.x, s.y, s.z, travelS2);
17139
+ travelS2 = Math.hypot(pos[0] - rcv[0], pos[1] - rcv[1], pos[2] - rcv[2]) / C_LIGHT;
17140
+ }
17141
+ const velDotPos2 = s.x * s.vx + s.y * s.vy + s.z * s.vz;
17142
+ const relS2 = -2 * velDotPos2 / (C_LIGHT * C_LIGHT);
17143
+ return {
17144
+ state: {
17145
+ x: pos[0],
17146
+ y: pos[1],
17147
+ z: pos[2],
17148
+ clkM: C_LIGHT * (s.clkS + relS2)
17149
+ },
17150
+ travelS: travelS2
17151
+ };
17152
+ }
17017
17153
  let travelS = 0.075;
17018
17154
  let sx = 0;
17019
17155
  let sy = 0;
@@ -17236,7 +17372,7 @@ var PppEngine = class {
17236
17372
  for (const ci of clkIdx.values()) P[ci][ci] = CLK_VAR;
17237
17373
  P[IDX_ZWD][IDX_ZWD] += ztdQ * (ei > 0 ? 1 : 0);
17238
17374
  const rcv = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
17239
- const [latRad, , heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
17375
+ const [latRad, lonRad, heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
17240
17376
  const epochCtx = corrections?.epochContext?.(epoch.timeMs, rcv) ?? null;
17241
17377
  const vis = [];
17242
17378
  let epochZhd = 0;
@@ -17246,7 +17382,7 @@ var PppEngine = class {
17246
17382
  const o = applySatBiases(source, raw, epoch.timeMs);
17247
17383
  if (!o) continue;
17248
17384
  const slip = detectSlip(o, ei);
17249
- const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
17385
+ const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv, o.c1);
17250
17386
  if (!sat) continue;
17251
17387
  const [elRad, azRad] = getAer(
17252
17388
  sat.state.x,
@@ -17269,7 +17405,17 @@ var PppEngine = class {
17269
17405
  const pIf = g * o.c1 - (g - 1) * o.c2;
17270
17406
  const lIf = g * (o.l1 * lam1) - (g - 1) * (o.l2 * lam2);
17271
17407
  if (!Number.isFinite(pIf) || !Number.isFinite(lIf)) continue;
17272
- 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 };
17273
17419
  const { zhd, mh, mw } = trop;
17274
17420
  epochZhd = zhd;
17275
17421
  const geom = {
@@ -17294,7 +17440,7 @@ var PppEngine = class {
17294
17440
  lIf,
17295
17441
  satClkM: sat.state.clkM,
17296
17442
  range,
17297
- tropoFixed: zhd * mh + corr.rangeM,
17443
+ tropoFixed: zhd * mh + trop.gradient + corr.rangeM,
17298
17444
  mw,
17299
17445
  windupM: corr.phaseWindupM,
17300
17446
  slip,
@@ -19289,6 +19435,495 @@ async function analyzeQuality(file, header, onProgress, signal) {
19289
19435
  };
19290
19436
  }
19291
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
+
19292
19927
  // src/antex/index.ts
19293
19928
  function label(line) {
19294
19929
  return (line.length >= 80 ? line.substring(60, 80) : line.substring(60)).trimEnd();
@@ -20484,6 +21119,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20484
21119
  BAND_LABELS,
20485
21120
  BAND_PRESETS,
20486
21121
  BDS_SATELLITES,
21122
+ BLQ_CONSTITUENTS,
20487
21123
  BitReader,
20488
21124
  CONSTELLATIONS,
20489
21125
  C_LIGHT,
@@ -20581,6 +21217,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20581
21217
  analyzeQuality,
20582
21218
  antexFreqOfGroup,
20583
21219
  applyHelmert,
21220
+ applyHelmertFit,
20584
21221
  applyIonoDcb,
20585
21222
  applyOrbitClock,
20586
21223
  biasMetres,
@@ -20628,6 +21265,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20628
21265
  ecefToAzEl,
20629
21266
  ecefToGeodetic,
20630
21267
  ephInfoToEphemeris,
21268
+ estimateHelmert,
20631
21269
  estimateNarrowlaneFcb,
20632
21270
  estimateNetworkFcbs,
20633
21271
  estimateWidelaneFcb,
@@ -20639,6 +21277,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20639
21277
  feedNavicBlock,
20640
21278
  fetchSourcetable,
20641
21279
  findSatBias,
21280
+ fitCoordinateSeries,
20642
21281
  fixPppPosition,
20643
21282
  fmtD,
20644
21283
  fmtF,
@@ -20733,12 +21372,14 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
20733
21372
  nlWavelength,
20734
21373
  nmeaCoordToDecimal,
20735
21374
  obsEpochToDate,
21375
+ oceanLoadingEnu,
20736
21376
  oem4Frames,
20737
21377
  padL,
20738
21378
  padR,
20739
21379
  parseAntex,
20740
21380
  parseBiasSinex,
20741
21381
  parseBinex,
21382
+ parseBlq,
20742
21383
  parseClk,
20743
21384
  parseCrxDataLine,
20744
21385
  parseIonex,