gnss-js 2.2.0 → 2.4.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
@@ -116,6 +116,7 @@ __export(src_exports, {
116
116
  almanacEpochMs: () => almanacEpochMs,
117
117
  almanacSatPosition: () => almanacSatPosition,
118
118
  analyzeQuality: () => analyzeQuality,
119
+ antexFreqOfGroup: () => antexFreqOfGroup,
119
120
  applyHelmert: () => applyHelmert,
120
121
  applyIonoDcb: () => applyIonoDcb,
121
122
  applyOrbitClock: () => applyOrbitClock,
@@ -127,6 +128,7 @@ __export(src_exports, {
127
128
  buildGloChannelMap: () => buildGloChannelMap,
128
129
  buildObsIndices: () => buildObsIndices,
129
130
  buildPppAntenna: () => buildPppAntenna,
131
+ buildRtkAntenna: () => buildRtkAntenna,
130
132
  clampUnit: () => clampUnit,
131
133
  clkBias: () => clkBias,
132
134
  computeAllPositions: () => computeAllPositions,
@@ -309,6 +311,7 @@ __export(src_exports, {
309
311
  postProcessRtk: () => postProcessRtk,
310
312
  rad2deg: () => rad2deg,
311
313
  rangeRate: () => rangeRate,
314
+ rcvAntennaRangeM: () => rcvAntennaRangeM,
312
315
  readString: () => readString,
313
316
  reportDecodeError: () => reportDecodeError,
314
317
  resetGloFreqCache: () => resetGloFreqCache,
@@ -13226,6 +13229,211 @@ function lambdaReduction(Q, n) {
13226
13229
  return Z2;
13227
13230
  }
13228
13231
 
13232
+ // src/positioning/ppp-antenna.ts
13233
+ function parseAntexTime(s) {
13234
+ const p = s.trim().split(/\s+/).map(Number);
13235
+ if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
13236
+ return Date.UTC(
13237
+ p[0],
13238
+ (p[1] || 1) - 1,
13239
+ p[2] || 1,
13240
+ p[3] ?? 0,
13241
+ p[4] ?? 0,
13242
+ Math.floor(p[5] ?? 0)
13243
+ );
13244
+ }
13245
+ function freqOf(entry, code) {
13246
+ return entry.frequencies.find((f) => f.frequency === code);
13247
+ }
13248
+ function combineIf(fA, fB, f1, f2) {
13249
+ const g = f1 * f1 / (f1 * f1 - f2 * f2);
13250
+ const mm = 1e-3;
13251
+ const pco = [
13252
+ (g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
13253
+ (g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
13254
+ (g * fA.pcoU - (g - 1) * fB.pcoU) * mm
13255
+ ];
13256
+ const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
13257
+ const pcvNoazi = new Array(nz);
13258
+ for (let i = 0; i < nz; i++) {
13259
+ pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
13260
+ }
13261
+ return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
13262
+ }
13263
+ function buildPppAntenna(antex) {
13264
+ const sats = /* @__PURE__ */ new Map();
13265
+ const rcvs = /* @__PURE__ */ new Map();
13266
+ for (const a of antex.antennas) {
13267
+ if (a.isSatellite) {
13268
+ const list = sats.get(a.serialNo) ?? [];
13269
+ list.push(a);
13270
+ sats.set(a.serialNo, list);
13271
+ } else if (!rcvs.has(a.type)) {
13272
+ rcvs.set(a.type, a);
13273
+ }
13274
+ }
13275
+ const withZen = (entry, off) => ({
13276
+ ...off,
13277
+ pcvZen1Deg: entry.zen1,
13278
+ pcvDzenDeg: entry.dzen
13279
+ });
13280
+ return {
13281
+ satOffset(prn, dateMs, band1, band2, f1, f2) {
13282
+ const list = sats.get(prn);
13283
+ if (!list) return null;
13284
+ const entry = list.find((e) => {
13285
+ const from = parseAntexTime(e.validFrom);
13286
+ const until = parseAntexTime(e.validUntil);
13287
+ return (from == null || dateMs >= from) && (until == null || dateMs <= until);
13288
+ });
13289
+ if (!entry) return null;
13290
+ const a = freqOf(entry, band1);
13291
+ const b = freqOf(entry, band2);
13292
+ if (!a || !b) return null;
13293
+ return withZen(entry, combineIf(a, b, f1, f2));
13294
+ },
13295
+ rcvOffset(antType, band1, band2, f1, f2) {
13296
+ const entry = rcvs.get(antType);
13297
+ if (!entry) return null;
13298
+ const a = freqOf(entry, band1);
13299
+ const b = freqOf(entry, band2);
13300
+ if (!a || !b) return null;
13301
+ return withZen(entry, combineIf(a, b, f1, f2));
13302
+ }
13303
+ };
13304
+ }
13305
+ function cross(a, b) {
13306
+ return [
13307
+ a[1] * b[2] - a[2] * b[1],
13308
+ a[2] * b[0] - a[0] * b[2],
13309
+ a[0] * b[1] - a[1] * b[0]
13310
+ ];
13311
+ }
13312
+ function norm(a) {
13313
+ const n = Math.hypot(a[0], a[1], a[2]) || 1;
13314
+ return [a[0] / n, a[1] / n, a[2] / n];
13315
+ }
13316
+ function dot(a, b) {
13317
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
13318
+ }
13319
+ function satBodyFrame(satEcef, sunEcef2) {
13320
+ const z = norm([-satEcef[0], -satEcef[1], -satEcef[2]]);
13321
+ const eSun = norm([
13322
+ sunEcef2[0] - satEcef[0],
13323
+ sunEcef2[1] - satEcef[1],
13324
+ sunEcef2[2] - satEcef[2]
13325
+ ]);
13326
+ const y = norm(cross(z, eSun));
13327
+ const x = norm(cross(y, z));
13328
+ return { x, y, z };
13329
+ }
13330
+ function satPcoToEcef(pcoBody, frame) {
13331
+ return [
13332
+ pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
13333
+ pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
13334
+ pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
13335
+ ];
13336
+ }
13337
+ function enuToEcef(enu, latRad, lonRad) {
13338
+ const sl = Math.sin(latRad);
13339
+ const cl = Math.cos(latRad);
13340
+ const so = Math.sin(lonRad);
13341
+ const co = Math.cos(lonRad);
13342
+ const [e, n, u] = enu;
13343
+ return [
13344
+ -so * e - sl * co * n + cl * co * u,
13345
+ co * e - sl * so * n + cl * so * u,
13346
+ cl * n + sl * u
13347
+ ];
13348
+ }
13349
+ function interpPcv(off, angleDeg) {
13350
+ const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
13351
+ if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
13352
+ const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
13353
+ if (x <= 0) return pcvNoazi[0];
13354
+ if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
13355
+ const i = Math.floor(x);
13356
+ const t = x - i;
13357
+ return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
13358
+ }
13359
+ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
13360
+ const k = [
13361
+ -losRcvToSat[0],
13362
+ -losRcvToSat[1],
13363
+ -losRcvToSat[2]
13364
+ ];
13365
+ const kx = satFrame.x;
13366
+ const ky = satFrame.y;
13367
+ const kdotx = dot(k, kx);
13368
+ const kcrossY = cross(k, ky);
13369
+ const dS = [
13370
+ kx[0] - k[0] * kdotx - kcrossY[0],
13371
+ kx[1] - k[1] * kdotx - kcrossY[1],
13372
+ kx[2] - k[2] * kdotx - kcrossY[2]
13373
+ ];
13374
+ const north = enuToEcef([0, 1, 0], latRad, lonRad);
13375
+ const east = enuToEcef([1, 0, 0], latRad, lonRad);
13376
+ const kdotn = dot(k, north);
13377
+ const kcrossE = cross(k, east);
13378
+ const dR = [
13379
+ north[0] - k[0] * kdotn + kcrossE[0],
13380
+ north[1] - k[1] * kdotn + kcrossE[1],
13381
+ north[2] - k[2] * kdotn + kcrossE[2]
13382
+ ];
13383
+ const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
13384
+ const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
13385
+ let cosPhi = dot(dS, dR) / (nS * nR);
13386
+ cosPhi = Math.max(-1, Math.min(1, cosPhi));
13387
+ const zeta = cross(dS, dR);
13388
+ const sign = dot(k, zeta) >= 0 ? 1 : -1;
13389
+ const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
13390
+ const prev = state.get(prn);
13391
+ const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
13392
+ state.set(prn, wu);
13393
+ return wu;
13394
+ }
13395
+
13396
+ // src/positioning/rtk-antenna.ts
13397
+ function freqOf2(entry, code) {
13398
+ return entry.frequencies.find((f) => f.frequency === code);
13399
+ }
13400
+ function buildRtkAntenna(antex) {
13401
+ const rcvs = /* @__PURE__ */ new Map();
13402
+ for (const a of antex.antennas) {
13403
+ if (!a.isSatellite && !rcvs.has(a.type)) rcvs.set(a.type, a);
13404
+ }
13405
+ const mm = 1e-3;
13406
+ return {
13407
+ rcvOffset(antType, freq) {
13408
+ const entry = rcvs.get(antType);
13409
+ if (!entry) return null;
13410
+ const f = freqOf2(entry, freq);
13411
+ if (!f) return null;
13412
+ return {
13413
+ pco: [f.pcoN * mm, f.pcoE * mm, f.pcoU * mm],
13414
+ pcvZen1Deg: entry.zen1,
13415
+ pcvDzenDeg: entry.dzen,
13416
+ pcvNoazi: f.pcvNoazi.map((v) => v * mm)
13417
+ };
13418
+ }
13419
+ };
13420
+ }
13421
+ function rcvAntennaRangeM(off, losRcvToSat, latRad, lonRad, elRad, antDeltaEnu = [0, 0, 0]) {
13422
+ const total = [
13423
+ off.pco[0] + antDeltaEnu[0],
13424
+ off.pco[1] + antDeltaEnu[1],
13425
+ off.pco[2] + antDeltaEnu[2]
13426
+ ];
13427
+ const ecef = enuToEcef(total, latRad, lonRad);
13428
+ let m = -dot(losRcvToSat, ecef);
13429
+ const zenithDeg = 90 - elRad * 180 / Math.PI;
13430
+ m += interpPcv(off, zenithDeg);
13431
+ return m;
13432
+ }
13433
+ function antexFreqOfGroup(group) {
13434
+ return `${group[0]}0${group[1]}`;
13435
+ }
13436
+
13229
13437
  // src/positioning/rtk.ts
13230
13438
  var L1_CODES = {
13231
13439
  G: ["1C"],
@@ -13291,8 +13499,16 @@ function resolveEphemeris(src, prn, timeMs) {
13291
13499
  return selectEphemeris(src, prn, timeMs);
13292
13500
  return src.get(prn) ?? null;
13293
13501
  }
13294
- function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere) {
13502
+ var ZERO_DELTA = [0, 0, 0];
13503
+ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere, antenna = null) {
13295
13504
  const out = [];
13505
+ let baseLatRad = 0;
13506
+ let baseLonRad = 0;
13507
+ if (antenna?.base) {
13508
+ const [latDeg, lonDeg] = ecefToGeodetic(basePos[0], basePos[1], basePos[2]);
13509
+ baseLatRad = latDeg * Math.PI / 180;
13510
+ baseLonRad = lonDeg * Math.PI / 180;
13511
+ }
13296
13512
  for (const [prn, mR] of rover) {
13297
13513
  const sys = prn[0];
13298
13514
  if (!"GERCJ".includes(sys)) continue;
@@ -13317,14 +13533,43 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
13317
13533
  satB.z - basePos[2]
13318
13534
  ) / C_LIGHT;
13319
13535
  const [bx, by, bz] = sagnac(satB, travelB);
13320
- const rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
13536
+ let rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
13321
13537
  const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
13322
13538
  if (elB < maskRad) continue;
13539
+ const group = sys + mR.code;
13540
+ let offR = null;
13541
+ let roverDelta = ZERO_DELTA;
13542
+ if (antenna) {
13543
+ const antexFreq = antexFreqOfGroup(group);
13544
+ if (antenna.base) {
13545
+ const offB = antenna.model.rcvOffset(antenna.base.type, antexFreq);
13546
+ if (offB) {
13547
+ const inv = 1 / rhoB;
13548
+ const losB = [
13549
+ (bx - basePos[0]) * inv,
13550
+ (by - basePos[1]) * inv,
13551
+ (bz - basePos[2]) * inv
13552
+ ];
13553
+ rhoB += rcvAntennaRangeM(
13554
+ offB,
13555
+ losB,
13556
+ baseLatRad,
13557
+ baseLonRad,
13558
+ elB,
13559
+ antenna.base.deltaEnu
13560
+ );
13561
+ }
13562
+ }
13563
+ if (antenna.rover) {
13564
+ offR = antenna.model.rcvOffset(antenna.rover.type, antexFreq);
13565
+ roverDelta = antenna.rover.deltaEnu ?? ZERO_DELTA;
13566
+ }
13567
+ }
13323
13568
  const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
13324
13569
  const freq = carrierFreqHz(sys, mR.code, gloK);
13325
13570
  out.push({
13326
13571
  prn,
13327
- group: sys + mR.code,
13572
+ group,
13328
13573
  lambda: freq > 0 ? C_LIGHT / freq : 0,
13329
13574
  satR,
13330
13575
  rhoB,
@@ -13335,7 +13580,9 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
13335
13580
  cpR: mR.cp ?? null,
13336
13581
  cpB: mB.cp ?? null,
13337
13582
  lockR: mR.lockTimeMs,
13338
- lockB: mB.lockTimeMs
13583
+ lockB: mB.lockTimeMs,
13584
+ offR,
13585
+ roverDelta
13339
13586
  });
13340
13587
  }
13341
13588
  return out;
@@ -13343,17 +13590,29 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
13343
13590
  function roverTerms(g, x, y, z, troposphere) {
13344
13591
  const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
13345
13592
  const [sx, sy, sz] = sagnac(g.satR, travel);
13346
- const rho = Math.hypot(sx - x, sy - y, sz - z);
13593
+ let rho = Math.hypot(sx - x, sy - y, sz - z);
13347
13594
  const u = [
13348
13595
  (x - sx) / rho,
13349
13596
  (y - sy) / rho,
13350
13597
  (z - sz) / rho
13351
13598
  ];
13599
+ const needEl = troposphere || g.offR != null;
13600
+ const elR = needEl ? ecefToAzEl(x, y, z, sx, sy, sz).el : 0;
13352
13601
  let dTropo = 0;
13353
13602
  if (troposphere) {
13354
- const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
13355
13603
  dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
13356
13604
  }
13605
+ if (g.offR) {
13606
+ const [latDeg, lonDeg] = ecefToGeodetic(x, y, z);
13607
+ rho += rcvAntennaRangeM(
13608
+ g.offR,
13609
+ [-u[0], -u[1], -u[2]],
13610
+ latDeg * Math.PI / 180,
13611
+ lonDeg * Math.PI / 180,
13612
+ elR,
13613
+ g.roverDelta
13614
+ );
13615
+ }
13357
13616
  return { rho, u, dTropo };
13358
13617
  }
13359
13618
  function sdVariance(sigmaM, elRad) {
@@ -13592,7 +13851,8 @@ var RtkFloatEngine = class {
13592
13851
  arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
13593
13852
  glonassAr: opts.glonassAr ?? false,
13594
13853
  partialFixing: opts.partialFixing ?? true,
13595
- minFixAmbiguities: opts.minFixAmbiguities ?? 4
13854
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4,
13855
+ antenna: opts.antenna ?? null
13596
13856
  };
13597
13857
  }
13598
13858
  /** Clear all filter state (position, ambiguities, lock history). */
@@ -13715,7 +13975,8 @@ var RtkFloatEngine = class {
13715
13975
  this.ephemerides,
13716
13976
  timeMs,
13717
13977
  o.elevationMaskDeg * Math.PI / 180,
13718
- o.troposphere
13978
+ o.troposphere,
13979
+ o.antenna
13719
13980
  );
13720
13981
  const byPrn = new Map(geom.map((g) => [g.prn, g]));
13721
13982
  const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
@@ -14315,6 +14576,10 @@ var SbasProcessor = class {
14315
14576
  tlat = 0;
14316
14577
  degr = null;
14317
14578
  mt27 = null;
14579
+ /** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
14580
+ * factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
14581
+ * scale exponent. Keyed by PRN; cleared on a mask change. */
14582
+ mt28 = /* @__PURE__ */ new Map();
14318
14583
  ion = [];
14319
14584
  /** GEO PRN(s) whose messages have been ingested. */
14320
14585
  geoPrns = /* @__PURE__ */ new Set();
@@ -14345,6 +14610,8 @@ var SbasProcessor = class {
14345
14610
  return this.decodeMt10(msg) ? type : -1;
14346
14611
  case 27:
14347
14612
  return this.decodeMt27(msg) ? type : -1;
14613
+ case 28:
14614
+ return this.decodeMt28(msg) ? type : -1;
14348
14615
  case 18:
14349
14616
  return this.decodeIgpMask(msg) ? type : -1;
14350
14617
  case 24:
@@ -14371,6 +14638,7 @@ var SbasProcessor = class {
14371
14638
  return true;
14372
14639
  this.iodp = iodp;
14373
14640
  this.sats = prns.map((prn) => ({ prn }));
14641
+ this.mt28.clear();
14374
14642
  this.satIdx.clear();
14375
14643
  this.sats.forEach((s, k) => {
14376
14644
  if (s.prn) this.satIdx.set(s.prn, k);
@@ -14436,7 +14704,9 @@ var SbasProcessor = class {
14436
14704
  iiono: u(117, 9) || 1,
14437
14705
  cionoRamp: u(126, 10) * 5e-6,
14438
14706
  rssUdre: u(136, 1) === 1,
14439
- rssIono: u(137, 1) === 1
14707
+ rssIono: u(137, 1) === 1,
14708
+ ccovariance: u(138, 7) * 0.1
14709
+ // Table B-49: 0..12.7 m, 0.1 m
14440
14710
  };
14441
14711
  return true;
14442
14712
  }
@@ -14488,6 +14758,51 @@ var SbasProcessor = class {
14488
14758
  }
14489
14759
  return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
14490
14760
  }
14761
+ /** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
14762
+ * two satellites per message, each an upper-triangular Cholesky factor E and a
14763
+ * scale exponent that together define the relative clock-ephemeris covariance
14764
+ * used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
14765
+ * the current MT1 mask, so a mismatched IODP is dropped. */
14766
+ decodeMt28(msg) {
14767
+ if (this.iodp !== getBitU(msg, 14, 2)) return false;
14768
+ for (const base of [16, 121]) {
14769
+ const slot = getBitU(msg, base, 6);
14770
+ const prn = this.sats[slot]?.prn;
14771
+ const scaleExp = getBitU(msg, base + 6, 3);
14772
+ const e = [
14773
+ getBitU(msg, base + 9, 9),
14774
+ getBitU(msg, base + 18, 9),
14775
+ getBitU(msg, base + 27, 9),
14776
+ getBitU(msg, base + 36, 9),
14777
+ getBitS(msg, base + 45, 10),
14778
+ getBitS(msg, base + 55, 10),
14779
+ getBitS(msg, base + 65, 10),
14780
+ getBitS(msg, base + 75, 10),
14781
+ getBitS(msg, base + 85, 10),
14782
+ getBitS(msg, base + 95, 10)
14783
+ ];
14784
+ if (prn && e.some((v) => v !== 0)) this.mt28.set(prn, { scaleExp, e });
14785
+ }
14786
+ return true;
14787
+ }
14788
+ /** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
14789
+ * δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
14790
+ * I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
14791
+ * Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
14792
+ mt28DeltaUdre(prn, los) {
14793
+ const m = this.mt28.get(prn);
14794
+ if (!m) return null;
14795
+ const sf = 2 ** (m.scaleExp - 5);
14796
+ const [e11, e22, e33, e44, e12, e13, e14, e23, e24, e34] = m.e;
14797
+ const [ix, iy, iz] = los;
14798
+ const v0 = (e11 * ix + e12 * iy + e13 * iz + e14) * sf;
14799
+ const v1 = (e22 * iy + e23 * iz + e24) * sf;
14800
+ const v2 = (e33 * iz + e34) * sf;
14801
+ const v3 = e44 * sf;
14802
+ const iCi = v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
14803
+ const ec = (this.degr?.ccovariance ?? 0) * sf;
14804
+ return Math.sqrt(iCi) + ec;
14805
+ }
14491
14806
  decodeMixed(msg, week, tow) {
14492
14807
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
14493
14808
  const blk = getBitU(msg, 112, 2);
@@ -14692,7 +15007,7 @@ var SbasProcessor = class {
14692
15007
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
14693
15008
  * to the satellite ECEF position and `dClkS` to its clock bias.
14694
15009
  */
14695
- satCorrection(prn, week, tow, userLatRad, userLonRad) {
15010
+ satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
14696
15011
  const idx = this.satIdx.get(prn);
14697
15012
  if (idx === void 0) return null;
14698
15013
  const s = this.sats[idx];
@@ -14714,7 +15029,8 @@ var SbasProcessor = class {
14714
15029
  const tf = now - s.fcorr.t0s + this.tlat;
14715
15030
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
14716
15031
  const prc = s.fcorr.prc;
14717
- const dudre = userLatRad != null && userLonRad != null ? this.deltaUdre(
15032
+ const mt28 = losUnitEcef ? this.mt28DeltaUdre(prn, losUnitEcef) : null;
15033
+ const dudre = mt28 != null ? mt28 : userLatRad != null && userLonRad != null ? this.deltaUdre(
14718
15034
  userLatRad * 180 / Math.PI,
14719
15035
  userLonRad * 180 / Math.PI
14720
15036
  ) : 1;
@@ -14843,14 +15159,14 @@ var SbasProcessor = class {
14843
15159
  };
14844
15160
 
14845
15161
  // src/positioning/ssr-apply.ts
14846
- var cross = (a, b) => [
15162
+ var cross2 = (a, b) => [
14847
15163
  a[1] * b[2] - a[2] * b[1],
14848
15164
  a[2] * b[0] - a[0] * b[2],
14849
15165
  a[0] * b[1] - a[1] * b[0]
14850
15166
  ];
14851
- var norm = (a) => Math.hypot(a[0], a[1], a[2]);
15167
+ var norm2 = (a) => Math.hypot(a[0], a[1], a[2]);
14852
15168
  var unit = (a) => {
14853
- const n = norm(a);
15169
+ const n = norm2(a);
14854
15170
  return n > 0 ? [a[0] / n, a[1] / n, a[2] / n] : [0, 0, 0];
14855
15171
  };
14856
15172
  function applyOrbitClock(eph, corr, timeMs) {
@@ -14864,8 +15180,8 @@ function applyOrbitClock(eph, corr, timeMs) {
14864
15180
  const p = [s.x, s.y, s.z];
14865
15181
  const v = [s.vx, s.vy, s.vz];
14866
15182
  const er = unit(p);
14867
- const en = unit(cross(p, v));
14868
- const ea = cross(en, er);
15183
+ const en = unit(cross2(p, v));
15184
+ const ea = cross2(en, er);
14869
15185
  const dr = corr.radial + (corr.dotRadial ?? 0) * age;
14870
15186
  const da = corr.along + (corr.dotAlong ?? 0) * age;
14871
15187
  const dc = corr.cross + (corr.dotCross ?? 0) * age;
@@ -15075,170 +15391,6 @@ function solidEarthTide(rcv, sun, moon) {
15075
15391
  return disp;
15076
15392
  }
15077
15393
 
15078
- // src/positioning/ppp-antenna.ts
15079
- function parseAntexTime(s) {
15080
- const p = s.trim().split(/\s+/).map(Number);
15081
- if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
15082
- return Date.UTC(
15083
- p[0],
15084
- (p[1] || 1) - 1,
15085
- p[2] || 1,
15086
- p[3] ?? 0,
15087
- p[4] ?? 0,
15088
- Math.floor(p[5] ?? 0)
15089
- );
15090
- }
15091
- function freqOf(entry, code) {
15092
- return entry.frequencies.find((f) => f.frequency === code);
15093
- }
15094
- function combineIf(fA, fB, f1, f2) {
15095
- const g = f1 * f1 / (f1 * f1 - f2 * f2);
15096
- const mm = 1e-3;
15097
- const pco = [
15098
- (g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
15099
- (g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
15100
- (g * fA.pcoU - (g - 1) * fB.pcoU) * mm
15101
- ];
15102
- const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
15103
- const pcvNoazi = new Array(nz);
15104
- for (let i = 0; i < nz; i++) {
15105
- pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
15106
- }
15107
- return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
15108
- }
15109
- function buildPppAntenna(antex) {
15110
- const sats = /* @__PURE__ */ new Map();
15111
- const rcvs = /* @__PURE__ */ new Map();
15112
- for (const a of antex.antennas) {
15113
- if (a.isSatellite) {
15114
- const list = sats.get(a.serialNo) ?? [];
15115
- list.push(a);
15116
- sats.set(a.serialNo, list);
15117
- } else if (!rcvs.has(a.type)) {
15118
- rcvs.set(a.type, a);
15119
- }
15120
- }
15121
- const withZen = (entry, off) => ({
15122
- ...off,
15123
- pcvZen1Deg: entry.zen1,
15124
- pcvDzenDeg: entry.dzen
15125
- });
15126
- return {
15127
- satOffset(prn, dateMs, band1, band2, f1, f2) {
15128
- const list = sats.get(prn);
15129
- if (!list) return null;
15130
- const entry = list.find((e) => {
15131
- const from = parseAntexTime(e.validFrom);
15132
- const until = parseAntexTime(e.validUntil);
15133
- return (from == null || dateMs >= from) && (until == null || dateMs <= until);
15134
- });
15135
- if (!entry) return null;
15136
- const a = freqOf(entry, band1);
15137
- const b = freqOf(entry, band2);
15138
- if (!a || !b) return null;
15139
- return withZen(entry, combineIf(a, b, f1, f2));
15140
- },
15141
- rcvOffset(antType, band1, band2, f1, f2) {
15142
- const entry = rcvs.get(antType);
15143
- if (!entry) return null;
15144
- const a = freqOf(entry, band1);
15145
- const b = freqOf(entry, band2);
15146
- if (!a || !b) return null;
15147
- return withZen(entry, combineIf(a, b, f1, f2));
15148
- }
15149
- };
15150
- }
15151
- function cross2(a, b) {
15152
- return [
15153
- a[1] * b[2] - a[2] * b[1],
15154
- a[2] * b[0] - a[0] * b[2],
15155
- a[0] * b[1] - a[1] * b[0]
15156
- ];
15157
- }
15158
- function norm2(a) {
15159
- const n = Math.hypot(a[0], a[1], a[2]) || 1;
15160
- return [a[0] / n, a[1] / n, a[2] / n];
15161
- }
15162
- function dot(a, b) {
15163
- return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
15164
- }
15165
- function satBodyFrame(satEcef, sunEcef2) {
15166
- const z = norm2([-satEcef[0], -satEcef[1], -satEcef[2]]);
15167
- const eSun = norm2([
15168
- sunEcef2[0] - satEcef[0],
15169
- sunEcef2[1] - satEcef[1],
15170
- sunEcef2[2] - satEcef[2]
15171
- ]);
15172
- const y = norm2(cross2(z, eSun));
15173
- const x = norm2(cross2(y, z));
15174
- return { x, y, z };
15175
- }
15176
- function satPcoToEcef(pcoBody, frame) {
15177
- return [
15178
- pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
15179
- pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
15180
- pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
15181
- ];
15182
- }
15183
- function enuToEcef(enu, latRad, lonRad) {
15184
- const sl = Math.sin(latRad);
15185
- const cl = Math.cos(latRad);
15186
- const so = Math.sin(lonRad);
15187
- const co = Math.cos(lonRad);
15188
- const [e, n, u] = enu;
15189
- return [
15190
- -so * e - sl * co * n + cl * co * u,
15191
- co * e - sl * so * n + cl * so * u,
15192
- cl * n + sl * u
15193
- ];
15194
- }
15195
- function interpPcv(off, angleDeg) {
15196
- const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
15197
- if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
15198
- const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
15199
- if (x <= 0) return pcvNoazi[0];
15200
- if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
15201
- const i = Math.floor(x);
15202
- const t = x - i;
15203
- return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
15204
- }
15205
- function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
15206
- const k = [
15207
- -losRcvToSat[0],
15208
- -losRcvToSat[1],
15209
- -losRcvToSat[2]
15210
- ];
15211
- const kx = satFrame.x;
15212
- const ky = satFrame.y;
15213
- const kdotx = dot(k, kx);
15214
- const kcrossY = cross2(k, ky);
15215
- const dS = [
15216
- kx[0] - k[0] * kdotx - kcrossY[0],
15217
- kx[1] - k[1] * kdotx - kcrossY[1],
15218
- kx[2] - k[2] * kdotx - kcrossY[2]
15219
- ];
15220
- const north = enuToEcef([0, 1, 0], latRad, lonRad);
15221
- const east = enuToEcef([1, 0, 0], latRad, lonRad);
15222
- const kdotn = dot(k, north);
15223
- const kcrossE = cross2(k, east);
15224
- const dR = [
15225
- north[0] - k[0] * kdotn + kcrossE[0],
15226
- north[1] - k[1] * kdotn + kcrossE[1],
15227
- north[2] - k[2] * kdotn + kcrossE[2]
15228
- ];
15229
- const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
15230
- const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
15231
- let cosPhi = dot(dS, dR) / (nS * nR);
15232
- cosPhi = Math.max(-1, Math.min(1, cosPhi));
15233
- const zeta = cross2(dS, dR);
15234
- const sign = dot(k, zeta) >= 0 ? 1 : -1;
15235
- const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
15236
- const prev = state.get(prn);
15237
- const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
15238
- state.set(prn, wu);
15239
- return wu;
15240
- }
15241
-
15242
15394
  // src/positioning/ppp-corrections.ts
15243
15395
  function applyCorrections(corrections, geom, ctx) {
15244
15396
  if (corrections.evaluate) return corrections.evaluate(geom, ctx);
@@ -16598,7 +16750,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
16598
16750
  used.push(prn);
16599
16751
  azels.push(azel);
16600
16752
  if (sbas) {
16601
- const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon);
16753
+ const dx = sat.x - x, dy = sat.y - y, dz = sat.z - z;
16754
+ const dr = Math.hypot(dx, dy, dz) || 1;
16755
+ const los = [dx / dr, dy / dr, dz / dr];
16756
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon, los);
16602
16757
  const si = sbas.ionoDelay(
16603
16758
  gpsWeek,
16604
16759
  gpsTow,
@@ -18989,6 +19144,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
18989
19144
  almanacEpochMs,
18990
19145
  almanacSatPosition,
18991
19146
  analyzeQuality,
19147
+ antexFreqOfGroup,
18992
19148
  applyHelmert,
18993
19149
  applyIonoDcb,
18994
19150
  applyOrbitClock,
@@ -19000,6 +19156,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19000
19156
  buildGloChannelMap,
19001
19157
  buildObsIndices,
19002
19158
  buildPppAntenna,
19159
+ buildRtkAntenna,
19003
19160
  clampUnit,
19004
19161
  clkBias,
19005
19162
  computeAllPositions,
@@ -19182,6 +19339,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19182
19339
  postProcessRtk,
19183
19340
  rad2deg,
19184
19341
  rangeRate,
19342
+ rcvAntennaRangeM,
19185
19343
  readString,
19186
19344
  reportDecodeError,
19187
19345
  resetGloFreqCache,