gnss-js 2.0.0 → 2.2.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
@@ -79,6 +79,7 @@ __export(src_exports, {
79
79
  MultipathAccumulator: () => MultipathAccumulator,
80
80
  OEM4_HLEN: () => OEM4_HLEN,
81
81
  OMEGA_E: () => OMEGA_E,
82
+ PppEngine: () => PppEngine,
82
83
  REFERENCE_FRAMES: () => REFERENCE_FRAMES,
83
84
  RINEX_CODES: () => RINEX_CODES,
84
85
  RTCM2_MESSAGE_NAMES: () => RTCM2_MESSAGE_NAMES,
@@ -106,6 +107,7 @@ __export(src_exports, {
106
107
  SYS_SHORT: () => SYS_SHORT,
107
108
  SbasProcessor: () => SbasProcessor,
108
109
  Scale: () => Scale,
110
+ Sp3EphemerisSource: () => Sp3EphemerisSource,
109
111
  WGS84_ECCENTRICITY_SQUARED: () => WGS84_ECCENTRICITY_SQUARED,
110
112
  WGS84_FLATTENING: () => WGS84_FLATTENING,
111
113
  WGS84_SEMI_MAJOR_AXIS: () => WGS84_SEMI_MAJOR_AXIS,
@@ -116,6 +118,7 @@ __export(src_exports, {
116
118
  analyzeQuality: () => analyzeQuality,
117
119
  applyHelmert: () => applyHelmert,
118
120
  applyIonoDcb: () => applyIonoDcb,
121
+ applyOrbitClock: () => applyOrbitClock,
119
122
  biasMetres: () => biasMetres,
120
123
  binexCrc16: () => binexCrc16,
121
124
  binexCrc32: () => binexCrc32,
@@ -240,6 +243,7 @@ __export(src_exports, {
240
243
  glonassPosition: () => glonassPosition,
241
244
  gmst: () => gmst,
242
245
  greatCircleMidpoint: () => greatCircleMidpoint,
246
+ hasToOrbitClock: () => hasToOrbitClock,
243
247
  hdrLine: () => hdrLine,
244
248
  horizonDistance: () => horizonDistance,
245
249
  ionoFree: () => ionoFree,
@@ -4069,7 +4073,7 @@ function parseBiasSinex(text) {
4069
4073
  const endMs = parseEpoch(line.slice(50, 64), Infinity);
4070
4074
  const tail = line.slice(65).trim().split(/\s+/);
4071
4075
  if (tail.length < 2) continue;
4072
- const unit = tail[0];
4076
+ const unit2 = tail[0];
4073
4077
  const value = Number(tail[1]);
4074
4078
  if (!Number.isFinite(value)) continue;
4075
4079
  records.push({
@@ -4081,7 +4085,7 @@ function parseBiasSinex(text) {
4081
4085
  startMs,
4082
4086
  endMs,
4083
4087
  value,
4084
- unit
4088
+ unit: unit2
4085
4089
  });
4086
4090
  }
4087
4091
  const bySat = /* @__PURE__ */ new Map();
@@ -6568,7 +6572,8 @@ function updateStreamStats(stats, frames, rawBytes) {
6568
6572
  stats.satellites.set(obs.prn, {
6569
6573
  prn: obs.prn,
6570
6574
  system: obs.system,
6571
- cn0: bestCn0,
6575
+ cn0: bestCn0 > 0 ? bestCn0 : null,
6576
+ // null = no C/N0 reported
6572
6577
  lastSeen: now,
6573
6578
  signals
6574
6579
  });
@@ -9115,14 +9120,14 @@ function parseOrbitBlock(c, masks) {
9115
9120
  const gnssIod = c.u(IOD_BITS[sys.system]);
9116
9121
  const rad = c.s(13);
9117
9122
  const inT = c.s(12);
9118
- const cross2 = c.s(12);
9123
+ const cross3 = c.s(12);
9119
9124
  corrections.push({
9120
9125
  system: sys.system,
9121
9126
  prn,
9122
9127
  gnssIod,
9123
9128
  deltaRadial: rad === -4096 ? null : rad * 25e-4,
9124
9129
  deltaInTrack: inT === -2048 ? null : inT * 8e-3,
9125
- deltaCrossTrack: cross2 === -2048 ? null : cross2 * 8e-3
9130
+ deltaCrossTrack: cross3 === -2048 ? null : cross3 * 8e-3
9126
9131
  });
9127
9132
  }
9128
9133
  }
@@ -14837,6 +14842,55 @@ var SbasProcessor = class {
14837
14842
  }
14838
14843
  };
14839
14844
 
14845
+ // src/positioning/ssr-apply.ts
14846
+ var cross = (a, b) => [
14847
+ a[1] * b[2] - a[2] * b[1],
14848
+ a[2] * b[0] - a[0] * b[2],
14849
+ a[0] * b[1] - a[1] * b[0]
14850
+ ];
14851
+ var norm = (a) => Math.hypot(a[0], a[1], a[2]);
14852
+ var unit = (a) => {
14853
+ const n = norm(a);
14854
+ return n > 0 ? [a[0] / n, a[1] / n, a[2] / n] : [0, 0, 0];
14855
+ };
14856
+ function applyOrbitClock(eph, corr, timeMs) {
14857
+ if (eph.system === "R" || eph.system === "S") return null;
14858
+ if (eph.iode !== corr.iod) return null;
14859
+ const age = (timeMs - corr.refTimeMs) / 1e3;
14860
+ if (corr.validitySeconds != null && (age < -1 || age > corr.validitySeconds))
14861
+ return null;
14862
+ const s = computeSatPosition(eph, timeMs);
14863
+ if (!Number.isFinite(s.x)) return null;
14864
+ const p = [s.x, s.y, s.z];
14865
+ const v = [s.vx, s.vy, s.vz];
14866
+ const er = unit(p);
14867
+ const en = unit(cross(p, v));
14868
+ const ea = cross(en, er);
14869
+ const dr = corr.radial + (corr.dotRadial ?? 0) * age;
14870
+ const da = corr.along + (corr.dotAlong ?? 0) * age;
14871
+ const dc = corr.cross + (corr.dotCross ?? 0) * age;
14872
+ const x = s.x + dr * er[0] + da * ea[0] + dc * en[0];
14873
+ const y = s.y + dr * er[1] + da * ea[1] + dc * en[1];
14874
+ const z = s.z + dr * er[2] + da * ea[2] + dc * en[2];
14875
+ const clockM = corr.clockC0 + (corr.clockC1 ?? 0) * age + (corr.clockC2 ?? 0) * age * age + (corr.highRateClock ?? 0);
14876
+ return { x, y, z, clockOffsetS: clockM / C_LIGHT };
14877
+ }
14878
+ function hasToOrbitClock(orbit, clock, refTimeMs, validitySeconds) {
14879
+ if (orbit.deltaRadial == null || orbit.deltaInTrack == null || orbit.deltaCrossTrack == null)
14880
+ return null;
14881
+ const clockC0 = clock && clock.deltaClock != null && !clock.notUsable ? clock.deltaClock : 0;
14882
+ return {
14883
+ prn: orbit.prn,
14884
+ iod: orbit.gnssIod,
14885
+ refTimeMs,
14886
+ radial: orbit.deltaRadial,
14887
+ along: orbit.deltaInTrack,
14888
+ cross: orbit.deltaCrossTrack,
14889
+ clockC0,
14890
+ validitySeconds
14891
+ };
14892
+ }
14893
+
14840
14894
  // src/positioning/ppp-tropo.ts
14841
14895
  var HYD_AVG_A = [
14842
14896
  0.0012769934,
@@ -15094,14 +15148,14 @@ function buildPppAntenna(antex) {
15094
15148
  }
15095
15149
  };
15096
15150
  }
15097
- function cross(a, b) {
15151
+ function cross2(a, b) {
15098
15152
  return [
15099
15153
  a[1] * b[2] - a[2] * b[1],
15100
15154
  a[2] * b[0] - a[0] * b[2],
15101
15155
  a[0] * b[1] - a[1] * b[0]
15102
15156
  ];
15103
15157
  }
15104
- function norm(a) {
15158
+ function norm2(a) {
15105
15159
  const n = Math.hypot(a[0], a[1], a[2]) || 1;
15106
15160
  return [a[0] / n, a[1] / n, a[2] / n];
15107
15161
  }
@@ -15109,14 +15163,14 @@ function dot(a, b) {
15109
15163
  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
15110
15164
  }
15111
15165
  function satBodyFrame(satEcef, sunEcef2) {
15112
- const z = norm([-satEcef[0], -satEcef[1], -satEcef[2]]);
15113
- const eSun = norm([
15166
+ const z = norm2([-satEcef[0], -satEcef[1], -satEcef[2]]);
15167
+ const eSun = norm2([
15114
15168
  sunEcef2[0] - satEcef[0],
15115
15169
  sunEcef2[1] - satEcef[1],
15116
15170
  sunEcef2[2] - satEcef[2]
15117
15171
  ]);
15118
- const y = norm(cross(z, eSun));
15119
- const x = norm(cross(y, z));
15172
+ const y = norm2(cross2(z, eSun));
15173
+ const x = norm2(cross2(y, z));
15120
15174
  return { x, y, z };
15121
15175
  }
15122
15176
  function satPcoToEcef(pcoBody, frame) {
@@ -15157,7 +15211,7 @@ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
15157
15211
  const kx = satFrame.x;
15158
15212
  const ky = satFrame.y;
15159
15213
  const kdotx = dot(k, kx);
15160
- const kcrossY = cross(k, ky);
15214
+ const kcrossY = cross2(k, ky);
15161
15215
  const dS = [
15162
15216
  kx[0] - k[0] * kdotx - kcrossY[0],
15163
15217
  kx[1] - k[1] * kdotx - kcrossY[1],
@@ -15166,7 +15220,7 @@ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
15166
15220
  const north = enuToEcef([0, 1, 0], latRad, lonRad);
15167
15221
  const east = enuToEcef([1, 0, 0], latRad, lonRad);
15168
15222
  const kdotn = dot(k, north);
15169
- const kcrossE = cross(k, east);
15223
+ const kcrossE = cross2(k, east);
15170
15224
  const dR = [
15171
15225
  north[0] - k[0] * kdotn + kcrossE[0],
15172
15226
  north[1] - k[1] * kdotn + kcrossE[1],
@@ -15176,7 +15230,7 @@ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
15176
15230
  const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
15177
15231
  let cosPhi = dot(dS, dR) / (nS * nR);
15178
15232
  cosPhi = Math.max(-1, Math.min(1, cosPhi));
15179
- const zeta = cross(dS, dR);
15233
+ const zeta = cross2(dS, dR);
15180
15234
  const sign = dot(k, zeta) >= 0 ? 1 : -1;
15181
15235
  const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
15182
15236
  const prev = state.get(prn);
@@ -15314,13 +15368,35 @@ function ifCoeffs(f1, f2) {
15314
15368
  const lambdaIf = C_LIGHT / (f1 + f2);
15315
15369
  return { g, lambdaIf };
15316
15370
  }
15371
+ var Sp3EphemerisSource = class {
15372
+ constructor(sp3, clk) {
15373
+ this.sp3 = sp3;
15374
+ this.clk = clk;
15375
+ }
15376
+ satState(prn, tEmitMs) {
15377
+ const p = sp3Position(this.sp3, prn, tEmitMs);
15378
+ if (!p || p.clk == null) return null;
15379
+ const pPlus = sp3Position(this.sp3, prn, tEmitMs + 500);
15380
+ const pMinus = sp3Position(this.sp3, prn, tEmitMs - 500);
15381
+ let vx = 0;
15382
+ let vy = 0;
15383
+ let vz = 0;
15384
+ if (pPlus && pMinus) {
15385
+ vx = pPlus.x - pMinus.x;
15386
+ vy = pPlus.y - pMinus.y;
15387
+ vz = pPlus.z - pMinus.z;
15388
+ }
15389
+ const clkS = (this.clk ? clkBias(this.clk, prn, tEmitMs) : null) ?? p.clk;
15390
+ return { x: p.x, y: p.y, z: p.z, vx, vy, vz, clkS };
15391
+ }
15392
+ };
15317
15393
  function sagnacRotate(x, y, z, travelS) {
15318
15394
  const theta = OMEGA_E * travelS;
15319
15395
  const c = Math.cos(theta);
15320
15396
  const s = Math.sin(theta);
15321
15397
  return [x * c + y * s, -x * s + y * c, z];
15322
15398
  }
15323
- function satStateAtEmission(sp3, prn, recvTimeMs, rcv, clk) {
15399
+ function satStateAtEmission(source, prn, recvTimeMs, rcv) {
15324
15400
  let travelS = 0.075;
15325
15401
  let sx = 0;
15326
15402
  let sy = 0;
@@ -15329,18 +15405,11 @@ function satStateAtEmission(sp3, prn, recvTimeMs, rcv, clk) {
15329
15405
  let velDotPos = 0;
15330
15406
  for (let iter = 0; iter < 3; iter++) {
15331
15407
  const tEmit = recvTimeMs - travelS * 1e3;
15332
- const p = sp3Position(sp3, prn, tEmit);
15333
- if (!p || p.clk == null) return null;
15334
- const pPlus = sp3Position(sp3, prn, tEmit + 500);
15335
- const pMinus = sp3Position(sp3, prn, tEmit - 500);
15336
- if (pPlus && pMinus) {
15337
- const vx = (pPlus.x - pMinus.x) / 1;
15338
- const vy = (pPlus.y - pMinus.y) / 1;
15339
- const vz = (pPlus.z - pMinus.z) / 1;
15340
- velDotPos = p.x * vx + p.y * vy + p.z * vz;
15341
- }
15342
- clkS = (clk && clkBias(clk, prn, tEmit)) ?? p.clk;
15343
- const [rx, ry, rz] = sagnacRotate(p.x, p.y, p.z, travelS);
15408
+ const s = source.satState(prn, tEmit);
15409
+ if (!s) return null;
15410
+ velDotPos = s.x * s.vx + s.y * s.vy + s.z * s.vz;
15411
+ clkS = s.clkS;
15412
+ const [rx, ry, rz] = sagnacRotate(s.x, s.y, s.z, travelS);
15344
15413
  sx = rx;
15345
15414
  sy = ry;
15346
15415
  sz = rz;
@@ -15362,39 +15431,72 @@ var IDX_ZWD = 3;
15362
15431
  var NBASE = 4;
15363
15432
  var CLK_VAR = 1e10;
15364
15433
  var POS_VAR = 60 * 60;
15365
- function solvePpp(epochs, sp3, opts) {
15366
- const elevMask = (opts.elevationMaskDeg ?? 10) * Math.PI / 180;
15367
- const codeSigma = opts.codeSigma ?? 3;
15368
- const phaseSigma = opts.phaseSigma ?? 0.01;
15369
- const ztdQ = opts.ztdProcessNoise ?? 1e-8;
15370
- const kinematic = opts.mode === "kinematic";
15371
- const corrections = opts.corrections;
15372
- const x = [
15373
- opts.aprioriPos[0],
15374
- opts.aprioriPos[1],
15375
- opts.aprioriPos[2],
15376
- 0.1
15377
- // zenith wet delay (m) a priori
15378
- ];
15379
- const P = [];
15380
- for (let i = 0; i < NBASE; i++) {
15381
- P.push(new Array(NBASE).fill(0));
15382
- }
15383
- P[IDX_X][IDX_X] = 100 * 100;
15384
- P[IDX_Y][IDX_Y] = 100 * 100;
15385
- P[IDX_Z][IDX_Z] = 100 * 100;
15386
- P[IDX_ZWD][IDX_ZWD] = 0.5 * 0.5;
15387
- const clkIdx = /* @__PURE__ */ new Map();
15388
- const clockSeeded = /* @__PURE__ */ new Set();
15389
- const ambIdx = /* @__PURE__ */ new Map();
15390
- const lastSeen = /* @__PURE__ */ new Map();
15391
- const arcs = [];
15392
- const arcSnap = /* @__PURE__ */ new Map();
15393
- const flushArc = (prn, minEpochs = 10) => {
15394
- const s = arcSnap.get(prn);
15395
- arcSnap.delete(prn);
15434
+ var PppEngine = class {
15435
+ source;
15436
+ opts;
15437
+ elevMask;
15438
+ codeSigma;
15439
+ phaseSigma;
15440
+ ztdQ;
15441
+ kinematic;
15442
+ corrections;
15443
+ truth;
15444
+ // Filter state (reset() initialises; mutated in place across epochs).
15445
+ x;
15446
+ P;
15447
+ clkIdx;
15448
+ clockSeeded;
15449
+ ambIdx;
15450
+ lastSeen;
15451
+ arcs;
15452
+ arcSnap;
15453
+ mwState;
15454
+ series;
15455
+ ei = 0;
15456
+ t0 = 0;
15457
+ convergenceSec = null;
15458
+ convergedStreak = 0;
15459
+ constructor(opts, source) {
15460
+ this.opts = opts;
15461
+ this.source = source;
15462
+ this.elevMask = (opts.elevationMaskDeg ?? 10) * Math.PI / 180;
15463
+ this.codeSigma = opts.codeSigma ?? 3;
15464
+ this.phaseSigma = opts.phaseSigma ?? 0.01;
15465
+ this.ztdQ = opts.ztdProcessNoise ?? 1e-8;
15466
+ this.kinematic = opts.mode === "kinematic";
15467
+ this.corrections = opts.corrections;
15468
+ this.truth = opts.groundTruth;
15469
+ this.reset();
15470
+ }
15471
+ /** Reinitialise the filter (fresh session). */
15472
+ reset() {
15473
+ const { opts } = this;
15474
+ this.x = [opts.aprioriPos[0], opts.aprioriPos[1], opts.aprioriPos[2], 0.1];
15475
+ const P = [];
15476
+ for (let i = 0; i < NBASE; i++) P.push(new Array(NBASE).fill(0));
15477
+ P[IDX_X][IDX_X] = 100 * 100;
15478
+ P[IDX_Y][IDX_Y] = 100 * 100;
15479
+ P[IDX_Z][IDX_Z] = 100 * 100;
15480
+ P[IDX_ZWD][IDX_ZWD] = 0.5 * 0.5;
15481
+ this.P = P;
15482
+ this.clkIdx = /* @__PURE__ */ new Map();
15483
+ this.clockSeeded = /* @__PURE__ */ new Set();
15484
+ this.ambIdx = /* @__PURE__ */ new Map();
15485
+ this.lastSeen = /* @__PURE__ */ new Map();
15486
+ this.arcs = [];
15487
+ this.arcSnap = /* @__PURE__ */ new Map();
15488
+ this.mwState = /* @__PURE__ */ new Map();
15489
+ this.series = [];
15490
+ this.ei = 0;
15491
+ this.t0 = 0;
15492
+ this.convergenceSec = null;
15493
+ this.convergedStreak = 0;
15494
+ }
15495
+ flushArc(prn, minEpochs = 10) {
15496
+ const s = this.arcSnap.get(prn);
15497
+ this.arcSnap.delete(prn);
15396
15498
  if (!s || s.nEpochs < minEpochs) return;
15397
- arcs.push({
15499
+ this.arcs.push({
15398
15500
  prn,
15399
15501
  aIF: s.aIF,
15400
15502
  mwCyc: s.mwCyc,
@@ -15405,64 +15507,88 @@ function solvePpp(epochs, sp3, opts) {
15405
15507
  startMs: s.startMs,
15406
15508
  endMs: s.endMs
15407
15509
  });
15408
- };
15409
- const mwState = /* @__PURE__ */ new Map();
15410
- const detectSlip = (o, ei) => {
15510
+ }
15511
+ /** Detect a cycle slip via the Melbourne–Wübbena wide-lane ambiguity. */
15512
+ detectSlip(o, ei) {
15411
15513
  const lamW = C_LIGHT / (o.f1 - o.f2);
15412
15514
  const lw = o.l1 - o.l2;
15413
15515
  const pn = (o.f1 * o.c1 + o.f2 * o.c2) / (o.f1 + o.f2);
15414
15516
  const mw = lw - pn / lamW;
15415
- const st = mwState.get(o.prn);
15517
+ const st = this.mwState.get(o.prn);
15416
15518
  let slip = o.slip;
15417
15519
  if (!st || ei - st.lastEi > 2)
15418
15520
  slip = true;
15419
15521
  else if (Math.abs(mw - st.mean) > 4) slip = true;
15420
- if (slip) mwState.set(o.prn, { mean: mw, n: 1, lastEi: ei });
15522
+ if (slip) this.mwState.set(o.prn, { mean: mw, n: 1, lastEi: ei });
15421
15523
  else {
15422
15524
  const n = st.n + 1;
15423
- mwState.set(o.prn, {
15525
+ this.mwState.set(o.prn, {
15424
15526
  mean: st.mean + (mw - st.mean) / Math.min(n, 100),
15425
15527
  n,
15426
15528
  lastEi: ei
15427
15529
  });
15428
15530
  }
15429
15531
  return slip;
15430
- };
15431
- const growState = (initVal, initVar) => {
15432
- const idx = x.length;
15433
- x.push(initVal);
15434
- for (const row of P) row.push(0);
15435
- P.push(new Array(x.length).fill(0));
15436
- P[idx][idx] = initVar;
15532
+ }
15533
+ growState(initVal, initVar) {
15534
+ const idx = this.x.length;
15535
+ this.x.push(initVal);
15536
+ for (const row of this.P) row.push(0);
15537
+ this.P.push(new Array(this.x.length).fill(0));
15538
+ this.P[idx][idx] = initVar;
15437
15539
  return idx;
15438
- };
15439
- const dropState = (idx) => {
15440
- x.splice(idx, 1);
15441
- P.splice(idx, 1);
15442
- for (const row of P) row.splice(idx, 1);
15443
- for (const [prn, i] of ambIdx) {
15444
- if (i === idx) ambIdx.delete(prn);
15445
- else if (i > idx) ambIdx.set(prn, i - 1);
15540
+ }
15541
+ dropState(idx) {
15542
+ this.x.splice(idx, 1);
15543
+ this.P.splice(idx, 1);
15544
+ for (const row of this.P) row.splice(idx, 1);
15545
+ for (const [prn, i] of this.ambIdx) {
15546
+ if (i === idx) this.ambIdx.delete(prn);
15547
+ else if (i > idx) this.ambIdx.set(prn, i - 1);
15446
15548
  }
15447
- for (const [sys, i] of clkIdx) {
15448
- if (i > idx) clkIdx.set(sys, i - 1);
15549
+ for (const [sys, i] of this.clkIdx) {
15550
+ if (i > idx) this.clkIdx.set(sys, i - 1);
15449
15551
  }
15450
- };
15451
- const clockOf = (sys) => {
15452
- let i = clkIdx.get(sys);
15552
+ }
15553
+ /** State index of a constellation's receiver clock, creating it lazily. */
15554
+ clockOf(sys) {
15555
+ let i = this.clkIdx.get(sys);
15453
15556
  if (i === void 0) {
15454
- i = growState(0, CLK_VAR);
15455
- clkIdx.set(sys, i);
15557
+ i = this.growState(0, CLK_VAR);
15558
+ this.clkIdx.set(sys, i);
15456
15559
  }
15457
15560
  return i;
15458
- };
15459
- const series = [];
15460
- const truth = opts.groundTruth;
15461
- const t0 = epochs[0]?.timeMs ?? 0;
15462
- let convergenceSec = null;
15463
- let convergedStreak = 0;
15464
- for (let ei = 0; ei < epochs.length; ei++) {
15465
- const epoch = epochs[ei];
15561
+ }
15562
+ /** Process one epoch, carrying the filter forward; returns its result. */
15563
+ process(epoch) {
15564
+ const {
15565
+ source,
15566
+ opts,
15567
+ elevMask,
15568
+ codeSigma,
15569
+ phaseSigma,
15570
+ ztdQ,
15571
+ kinematic,
15572
+ corrections,
15573
+ truth,
15574
+ x,
15575
+ P,
15576
+ clkIdx,
15577
+ clockSeeded,
15578
+ ambIdx,
15579
+ lastSeen,
15580
+ arcSnap,
15581
+ mwState,
15582
+ series
15583
+ } = this;
15584
+ const flushArc = this.flushArc.bind(this);
15585
+ const detectSlip = this.detectSlip.bind(this);
15586
+ const growState = this.growState.bind(this);
15587
+ const dropState = this.dropState.bind(this);
15588
+ const clockOf = this.clockOf.bind(this);
15589
+ const ei = this.ei;
15590
+ if (ei === 0) this.t0 = epoch.timeMs;
15591
+ const t0 = this.t0;
15466
15592
  if (kinematic && ei > 0) {
15467
15593
  for (const p of [IDX_X, IDX_Y, IDX_Z]) {
15468
15594
  for (let j = 0; j < x.length; j++) {
@@ -15483,7 +15609,7 @@ function solvePpp(epochs, sp3, opts) {
15483
15609
  for (const o of epoch.obs) {
15484
15610
  if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
15485
15611
  const slip = detectSlip(o, ei);
15486
- const sat = satStateAtEmission(sp3, o.prn, epoch.timeMs, rcv, opts.clk);
15612
+ const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
15487
15613
  if (!sat) continue;
15488
15614
  const [elRad, azRad] = getAer(
15489
15615
  sat.state.x,
@@ -15503,7 +15629,9 @@ function solvePpp(epochs, sp3, opts) {
15503
15629
  const lam1 = C_LIGHT / o.f1;
15504
15630
  const lam2 = C_LIGHT / o.f2;
15505
15631
  if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
15506
- const pIf = g * o.c1 - (g - 1) * o.c2;
15632
+ const b1 = source.codeBias?.(o.prn, o.code1 ?? "", epoch.timeMs) ?? 0;
15633
+ const b2 = source.codeBias?.(o.prn, o.code2 ?? "", epoch.timeMs) ?? 0;
15634
+ const pIf = g * (o.c1 - b1) - (g - 1) * (o.c2 - b2);
15507
15635
  const lIf = g * (o.l1 * lam1) - (g - 1) * (o.l2 * lam2);
15508
15636
  if (!Number.isFinite(pIf) || !Number.isFinite(lIf)) continue;
15509
15637
  const trop = opts.troposphere ?? true ? niellMapping(elRad, latRad, heightM, epoch.timeMs) : { zhd: 0, mh: 0, mw: 0 };
@@ -15690,16 +15818,16 @@ function solvePpp(epochs, sp3, opts) {
15690
15818
  pos[2] - truth[2]
15691
15819
  );
15692
15820
  if (error3d < 0.1) {
15693
- convergedStreak++;
15694
- if (convergedStreak >= 10 && convergenceSec == null) {
15695
- convergenceSec = (epoch.timeMs - t0) / 1e3;
15821
+ this.convergedStreak++;
15822
+ if (this.convergedStreak >= 10 && this.convergenceSec == null) {
15823
+ this.convergenceSec = (epoch.timeMs - t0) / 1e3;
15696
15824
  }
15697
15825
  } else {
15698
- convergedStreak = 0;
15699
- convergenceSec = null;
15826
+ this.convergedStreak = 0;
15827
+ this.convergenceSec = null;
15700
15828
  }
15701
15829
  }
15702
- series.push({
15830
+ const result = {
15703
15831
  timeMs: epoch.timeMs,
15704
15832
  enu,
15705
15833
  error3d,
@@ -15708,50 +15836,68 @@ function solvePpp(epochs, sp3, opts) {
15708
15836
  ztdWet: x[IDX_ZWD],
15709
15837
  ztdHydrostatic: epochZhd,
15710
15838
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
15711
- });
15712
- }
15713
- let finalState;
15714
- if (opts.exposeState) {
15715
- const ambs = [];
15716
- for (const [prn, index] of ambIdx) {
15717
- const s = arcSnap.get(prn);
15718
- if (!s || s.nEpochs < 10) continue;
15719
- ambs.push({
15720
- prn,
15721
- index,
15722
- aIF: x[index],
15723
- mwCyc: mwState.get(prn)?.mean ?? s.mwCyc,
15724
- f1: s.f1,
15725
- f2: s.f2,
15726
- elevDeg: s.elevSum / s.nEpochs
15727
- });
15839
+ };
15840
+ series.push(result);
15841
+ this.ei = ei + 1;
15842
+ return result;
15843
+ }
15844
+ /** Finalise the session: capture the AR state, flush open arcs, assemble. */
15845
+ solution() {
15846
+ const { opts, x, P, ambIdx, arcSnap, mwState, arcs, series, truth } = this;
15847
+ const flushArc = this.flushArc.bind(this);
15848
+ let finalState;
15849
+ if (opts.exposeState) {
15850
+ const ambs = [];
15851
+ for (const [prn, index] of ambIdx) {
15852
+ const s = arcSnap.get(prn);
15853
+ if (!s || s.nEpochs < 10) continue;
15854
+ ambs.push({
15855
+ prn,
15856
+ index,
15857
+ aIF: x[index],
15858
+ mwCyc: mwState.get(prn)?.mean ?? s.mwCyc,
15859
+ f1: s.f1,
15860
+ f2: s.f2,
15861
+ elevDeg: s.elevSum / s.nEpochs
15862
+ });
15863
+ }
15864
+ finalState = {
15865
+ position: [x[IDX_X], x[IDX_Y], x[IDX_Z]],
15866
+ covariance: P.map((row) => row.slice()),
15867
+ ambiguities: ambs
15868
+ };
15728
15869
  }
15729
- finalState = {
15730
- position: [x[IDX_X], x[IDX_Y], x[IDX_Z]],
15731
- covariance: P.map((row) => row.slice()),
15732
- ambiguities: ambs
15870
+ for (const prn of [...arcSnap.keys()]) flushArc(prn);
15871
+ const finalPos = [
15872
+ x[IDX_X],
15873
+ x[IDX_Y],
15874
+ x[IDX_Z]
15875
+ ];
15876
+ const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
15877
+ const finalError3d = truth ? Math.hypot(
15878
+ finalPos[0] - truth[0],
15879
+ finalPos[1] - truth[1],
15880
+ finalPos[2] - truth[2]
15881
+ ) : null;
15882
+ return {
15883
+ position: finalPos,
15884
+ llh,
15885
+ series,
15886
+ ztdWet: x[IDX_ZWD],
15887
+ ztdHydrostatic: series.length ? series[series.length - 1].ztdHydrostatic : 0,
15888
+ epochsUsed: series.length,
15889
+ convergenceSec: this.convergenceSec,
15890
+ finalError3d,
15891
+ arcs,
15892
+ finalState
15733
15893
  };
15734
15894
  }
15735
- for (const prn of [...arcSnap.keys()]) flushArc(prn);
15736
- const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
15737
- const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
15738
- const finalError3d = truth ? Math.hypot(
15739
- finalPos[0] - truth[0],
15740
- finalPos[1] - truth[1],
15741
- finalPos[2] - truth[2]
15742
- ) : null;
15743
- return {
15744
- position: finalPos,
15745
- llh,
15746
- series,
15747
- ztdWet: x[IDX_ZWD],
15748
- ztdHydrostatic: series.length ? series[series.length - 1].ztdHydrostatic : 0,
15749
- epochsUsed: epochs.length,
15750
- convergenceSec,
15751
- finalError3d,
15752
- arcs,
15753
- finalState
15754
- };
15895
+ };
15896
+ function solvePpp(epochs, sp3OrSource, opts) {
15897
+ const source = "satState" in sp3OrSource ? sp3OrSource : new Sp3EphemerisSource(sp3OrSource, opts.clk);
15898
+ const eng = new PppEngine(opts, source);
15899
+ for (const epoch of epochs) eng.process(epoch);
15900
+ return eng.solution();
15755
15901
  }
15756
15902
 
15757
15903
  // src/positioning/ppp-ar.ts
@@ -18806,6 +18952,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
18806
18952
  MultipathAccumulator,
18807
18953
  OEM4_HLEN,
18808
18954
  OMEGA_E,
18955
+ PppEngine,
18809
18956
  REFERENCE_FRAMES,
18810
18957
  RINEX_CODES,
18811
18958
  RTCM2_MESSAGE_NAMES,
@@ -18833,6 +18980,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
18833
18980
  SYS_SHORT,
18834
18981
  SbasProcessor,
18835
18982
  Scale,
18983
+ Sp3EphemerisSource,
18836
18984
  WGS84_ECCENTRICITY_SQUARED,
18837
18985
  WGS84_FLATTENING,
18838
18986
  WGS84_SEMI_MAJOR_AXIS,
@@ -18843,6 +18991,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
18843
18991
  analyzeQuality,
18844
18992
  applyHelmert,
18845
18993
  applyIonoDcb,
18994
+ applyOrbitClock,
18846
18995
  biasMetres,
18847
18996
  binexCrc16,
18848
18997
  binexCrc32,
@@ -18967,6 +19116,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
18967
19116
  glonassPosition,
18968
19117
  gmst,
18969
19118
  greatCircleMidpoint,
19119
+ hasToOrbitClock,
18970
19120
  hdrLine,
18971
19121
  horizonDistance,
18972
19122
  ionoFree,