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