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.
@@ -302,6 +302,211 @@ function lambdaReduction(Q, n) {
302
302
  return Z;
303
303
  }
304
304
 
305
+ // src/positioning/ppp-antenna.ts
306
+ function parseAntexTime(s) {
307
+ const p = s.trim().split(/\s+/).map(Number);
308
+ if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
309
+ return Date.UTC(
310
+ p[0],
311
+ (p[1] || 1) - 1,
312
+ p[2] || 1,
313
+ p[3] ?? 0,
314
+ p[4] ?? 0,
315
+ Math.floor(p[5] ?? 0)
316
+ );
317
+ }
318
+ function freqOf(entry, code) {
319
+ return entry.frequencies.find((f) => f.frequency === code);
320
+ }
321
+ function combineIf(fA, fB, f1, f2) {
322
+ const g = f1 * f1 / (f1 * f1 - f2 * f2);
323
+ const mm = 1e-3;
324
+ const pco = [
325
+ (g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
326
+ (g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
327
+ (g * fA.pcoU - (g - 1) * fB.pcoU) * mm
328
+ ];
329
+ const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
330
+ const pcvNoazi = new Array(nz);
331
+ for (let i = 0; i < nz; i++) {
332
+ pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
333
+ }
334
+ return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
335
+ }
336
+ function buildPppAntenna(antex) {
337
+ const sats = /* @__PURE__ */ new Map();
338
+ const rcvs = /* @__PURE__ */ new Map();
339
+ for (const a of antex.antennas) {
340
+ if (a.isSatellite) {
341
+ const list = sats.get(a.serialNo) ?? [];
342
+ list.push(a);
343
+ sats.set(a.serialNo, list);
344
+ } else if (!rcvs.has(a.type)) {
345
+ rcvs.set(a.type, a);
346
+ }
347
+ }
348
+ const withZen = (entry, off) => ({
349
+ ...off,
350
+ pcvZen1Deg: entry.zen1,
351
+ pcvDzenDeg: entry.dzen
352
+ });
353
+ return {
354
+ satOffset(prn, dateMs, band1, band2, f1, f2) {
355
+ const list = sats.get(prn);
356
+ if (!list) return null;
357
+ const entry = list.find((e) => {
358
+ const from = parseAntexTime(e.validFrom);
359
+ const until = parseAntexTime(e.validUntil);
360
+ return (from == null || dateMs >= from) && (until == null || dateMs <= until);
361
+ });
362
+ if (!entry) return null;
363
+ const a = freqOf(entry, band1);
364
+ const b = freqOf(entry, band2);
365
+ if (!a || !b) return null;
366
+ return withZen(entry, combineIf(a, b, f1, f2));
367
+ },
368
+ rcvOffset(antType, band1, band2, f1, f2) {
369
+ const entry = rcvs.get(antType);
370
+ if (!entry) return null;
371
+ const a = freqOf(entry, band1);
372
+ const b = freqOf(entry, band2);
373
+ if (!a || !b) return null;
374
+ return withZen(entry, combineIf(a, b, f1, f2));
375
+ }
376
+ };
377
+ }
378
+ function cross(a, b) {
379
+ return [
380
+ a[1] * b[2] - a[2] * b[1],
381
+ a[2] * b[0] - a[0] * b[2],
382
+ a[0] * b[1] - a[1] * b[0]
383
+ ];
384
+ }
385
+ function norm(a) {
386
+ const n = Math.hypot(a[0], a[1], a[2]) || 1;
387
+ return [a[0] / n, a[1] / n, a[2] / n];
388
+ }
389
+ function dot(a, b) {
390
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
391
+ }
392
+ function satBodyFrame(satEcef, sunEcef2) {
393
+ const z = norm([-satEcef[0], -satEcef[1], -satEcef[2]]);
394
+ const eSun = norm([
395
+ sunEcef2[0] - satEcef[0],
396
+ sunEcef2[1] - satEcef[1],
397
+ sunEcef2[2] - satEcef[2]
398
+ ]);
399
+ const y = norm(cross(z, eSun));
400
+ const x = norm(cross(y, z));
401
+ return { x, y, z };
402
+ }
403
+ function satPcoToEcef(pcoBody, frame) {
404
+ return [
405
+ pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
406
+ pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
407
+ pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
408
+ ];
409
+ }
410
+ function enuToEcef(enu, latRad, lonRad) {
411
+ const sl = Math.sin(latRad);
412
+ const cl = Math.cos(latRad);
413
+ const so = Math.sin(lonRad);
414
+ const co = Math.cos(lonRad);
415
+ const [e, n, u] = enu;
416
+ return [
417
+ -so * e - sl * co * n + cl * co * u,
418
+ co * e - sl * so * n + cl * so * u,
419
+ cl * n + sl * u
420
+ ];
421
+ }
422
+ function interpPcv(off, angleDeg) {
423
+ const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
424
+ if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
425
+ const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
426
+ if (x <= 0) return pcvNoazi[0];
427
+ if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
428
+ const i = Math.floor(x);
429
+ const t = x - i;
430
+ return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
431
+ }
432
+ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
433
+ const k = [
434
+ -losRcvToSat[0],
435
+ -losRcvToSat[1],
436
+ -losRcvToSat[2]
437
+ ];
438
+ const kx = satFrame.x;
439
+ const ky = satFrame.y;
440
+ const kdotx = dot(k, kx);
441
+ const kcrossY = cross(k, ky);
442
+ const dS = [
443
+ kx[0] - k[0] * kdotx - kcrossY[0],
444
+ kx[1] - k[1] * kdotx - kcrossY[1],
445
+ kx[2] - k[2] * kdotx - kcrossY[2]
446
+ ];
447
+ const north = enuToEcef([0, 1, 0], latRad, lonRad);
448
+ const east = enuToEcef([1, 0, 0], latRad, lonRad);
449
+ const kdotn = dot(k, north);
450
+ const kcrossE = cross(k, east);
451
+ const dR = [
452
+ north[0] - k[0] * kdotn + kcrossE[0],
453
+ north[1] - k[1] * kdotn + kcrossE[1],
454
+ north[2] - k[2] * kdotn + kcrossE[2]
455
+ ];
456
+ const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
457
+ const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
458
+ let cosPhi = dot(dS, dR) / (nS * nR);
459
+ cosPhi = Math.max(-1, Math.min(1, cosPhi));
460
+ const zeta = cross(dS, dR);
461
+ const sign = dot(k, zeta) >= 0 ? 1 : -1;
462
+ const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
463
+ const prev = state.get(prn);
464
+ const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
465
+ state.set(prn, wu);
466
+ return wu;
467
+ }
468
+
469
+ // src/positioning/rtk-antenna.ts
470
+ function freqOf2(entry, code) {
471
+ return entry.frequencies.find((f) => f.frequency === code);
472
+ }
473
+ function buildRtkAntenna(antex) {
474
+ const rcvs = /* @__PURE__ */ new Map();
475
+ for (const a of antex.antennas) {
476
+ if (!a.isSatellite && !rcvs.has(a.type)) rcvs.set(a.type, a);
477
+ }
478
+ const mm = 1e-3;
479
+ return {
480
+ rcvOffset(antType, freq) {
481
+ const entry = rcvs.get(antType);
482
+ if (!entry) return null;
483
+ const f = freqOf2(entry, freq);
484
+ if (!f) return null;
485
+ return {
486
+ pco: [f.pcoN * mm, f.pcoE * mm, f.pcoU * mm],
487
+ pcvZen1Deg: entry.zen1,
488
+ pcvDzenDeg: entry.dzen,
489
+ pcvNoazi: f.pcvNoazi.map((v) => v * mm)
490
+ };
491
+ }
492
+ };
493
+ }
494
+ function rcvAntennaRangeM(off, losRcvToSat, latRad, lonRad, elRad, antDeltaEnu = [0, 0, 0]) {
495
+ const total = [
496
+ off.pco[0] + antDeltaEnu[0],
497
+ off.pco[1] + antDeltaEnu[1],
498
+ off.pco[2] + antDeltaEnu[2]
499
+ ];
500
+ const ecef = enuToEcef(total, latRad, lonRad);
501
+ let m = -dot(losRcvToSat, ecef);
502
+ const zenithDeg = 90 - elRad * 180 / Math.PI;
503
+ m += interpPcv(off, zenithDeg);
504
+ return m;
505
+ }
506
+ function antexFreqOfGroup(group) {
507
+ return `${group[0]}0${group[1]}`;
508
+ }
509
+
305
510
  // src/positioning/rtk.ts
306
511
  var L1_CODES = {
307
512
  G: ["1C"],
@@ -367,8 +572,16 @@ function resolveEphemeris(src, prn, timeMs) {
367
572
  return selectEphemeris(src, prn, timeMs);
368
573
  return src.get(prn) ?? null;
369
574
  }
370
- function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere) {
575
+ var ZERO_DELTA = [0, 0, 0];
576
+ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere, antenna = null) {
371
577
  const out = [];
578
+ let baseLatRad = 0;
579
+ let baseLonRad = 0;
580
+ if (antenna?.base) {
581
+ const [latDeg, lonDeg] = ecefToGeodetic(basePos[0], basePos[1], basePos[2]);
582
+ baseLatRad = latDeg * Math.PI / 180;
583
+ baseLonRad = lonDeg * Math.PI / 180;
584
+ }
372
585
  for (const [prn, mR] of rover) {
373
586
  const sys = prn[0];
374
587
  if (!"GERCJ".includes(sys)) continue;
@@ -393,14 +606,43 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
393
606
  satB.z - basePos[2]
394
607
  ) / C_LIGHT;
395
608
  const [bx, by, bz] = sagnac(satB, travelB);
396
- const rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
609
+ let rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
397
610
  const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
398
611
  if (elB < maskRad) continue;
612
+ const group = sys + mR.code;
613
+ let offR = null;
614
+ let roverDelta = ZERO_DELTA;
615
+ if (antenna) {
616
+ const antexFreq = antexFreqOfGroup(group);
617
+ if (antenna.base) {
618
+ const offB = antenna.model.rcvOffset(antenna.base.type, antexFreq);
619
+ if (offB) {
620
+ const inv = 1 / rhoB;
621
+ const losB = [
622
+ (bx - basePos[0]) * inv,
623
+ (by - basePos[1]) * inv,
624
+ (bz - basePos[2]) * inv
625
+ ];
626
+ rhoB += rcvAntennaRangeM(
627
+ offB,
628
+ losB,
629
+ baseLatRad,
630
+ baseLonRad,
631
+ elB,
632
+ antenna.base.deltaEnu
633
+ );
634
+ }
635
+ }
636
+ if (antenna.rover) {
637
+ offR = antenna.model.rcvOffset(antenna.rover.type, antexFreq);
638
+ roverDelta = antenna.rover.deltaEnu ?? ZERO_DELTA;
639
+ }
640
+ }
399
641
  const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
400
642
  const freq = carrierFreqHz(sys, mR.code, gloK);
401
643
  out.push({
402
644
  prn,
403
- group: sys + mR.code,
645
+ group,
404
646
  lambda: freq > 0 ? C_LIGHT / freq : 0,
405
647
  satR,
406
648
  rhoB,
@@ -411,7 +653,9 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
411
653
  cpR: mR.cp ?? null,
412
654
  cpB: mB.cp ?? null,
413
655
  lockR: mR.lockTimeMs,
414
- lockB: mB.lockTimeMs
656
+ lockB: mB.lockTimeMs,
657
+ offR,
658
+ roverDelta
415
659
  });
416
660
  }
417
661
  return out;
@@ -419,17 +663,29 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
419
663
  function roverTerms(g, x, y, z, troposphere) {
420
664
  const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
421
665
  const [sx, sy, sz] = sagnac(g.satR, travel);
422
- const rho = Math.hypot(sx - x, sy - y, sz - z);
666
+ let rho = Math.hypot(sx - x, sy - y, sz - z);
423
667
  const u = [
424
668
  (x - sx) / rho,
425
669
  (y - sy) / rho,
426
670
  (z - sz) / rho
427
671
  ];
672
+ const needEl = troposphere || g.offR != null;
673
+ const elR = needEl ? ecefToAzEl(x, y, z, sx, sy, sz).el : 0;
428
674
  let dTropo = 0;
429
675
  if (troposphere) {
430
- const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
431
676
  dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
432
677
  }
678
+ if (g.offR) {
679
+ const [latDeg, lonDeg] = ecefToGeodetic(x, y, z);
680
+ rho += rcvAntennaRangeM(
681
+ g.offR,
682
+ [-u[0], -u[1], -u[2]],
683
+ latDeg * Math.PI / 180,
684
+ lonDeg * Math.PI / 180,
685
+ elR,
686
+ g.roverDelta
687
+ );
688
+ }
433
689
  return { rho, u, dTropo };
434
690
  }
435
691
  function sdVariance(sigmaM, elRad) {
@@ -668,7 +924,8 @@ var RtkFloatEngine = class {
668
924
  arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
669
925
  glonassAr: opts.glonassAr ?? false,
670
926
  partialFixing: opts.partialFixing ?? true,
671
- minFixAmbiguities: opts.minFixAmbiguities ?? 4
927
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4,
928
+ antenna: opts.antenna ?? null
672
929
  };
673
930
  }
674
931
  /** Clear all filter state (position, ambiguities, lock history). */
@@ -791,7 +1048,8 @@ var RtkFloatEngine = class {
791
1048
  this.ephemerides,
792
1049
  timeMs,
793
1050
  o.elevationMaskDeg * Math.PI / 180,
794
- o.troposphere
1051
+ o.troposphere,
1052
+ o.antenna
795
1053
  );
796
1054
  const byPrn = new Map(geom.map((g) => [g.prn, g]));
797
1055
  const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
@@ -1391,6 +1649,10 @@ var SbasProcessor = class {
1391
1649
  tlat = 0;
1392
1650
  degr = null;
1393
1651
  mt27 = null;
1652
+ /** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
1653
+ * factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
1654
+ * scale exponent. Keyed by PRN; cleared on a mask change. */
1655
+ mt28 = /* @__PURE__ */ new Map();
1394
1656
  ion = [];
1395
1657
  /** GEO PRN(s) whose messages have been ingested. */
1396
1658
  geoPrns = /* @__PURE__ */ new Set();
@@ -1421,6 +1683,8 @@ var SbasProcessor = class {
1421
1683
  return this.decodeMt10(msg) ? type : -1;
1422
1684
  case 27:
1423
1685
  return this.decodeMt27(msg) ? type : -1;
1686
+ case 28:
1687
+ return this.decodeMt28(msg) ? type : -1;
1424
1688
  case 18:
1425
1689
  return this.decodeIgpMask(msg) ? type : -1;
1426
1690
  case 24:
@@ -1447,6 +1711,7 @@ var SbasProcessor = class {
1447
1711
  return true;
1448
1712
  this.iodp = iodp;
1449
1713
  this.sats = prns.map((prn) => ({ prn }));
1714
+ this.mt28.clear();
1450
1715
  this.satIdx.clear();
1451
1716
  this.sats.forEach((s, k) => {
1452
1717
  if (s.prn) this.satIdx.set(s.prn, k);
@@ -1512,7 +1777,9 @@ var SbasProcessor = class {
1512
1777
  iiono: u(117, 9) || 1,
1513
1778
  cionoRamp: u(126, 10) * 5e-6,
1514
1779
  rssUdre: u(136, 1) === 1,
1515
- rssIono: u(137, 1) === 1
1780
+ rssIono: u(137, 1) === 1,
1781
+ ccovariance: u(138, 7) * 0.1
1782
+ // Table B-49: 0..12.7 m, 0.1 m
1516
1783
  };
1517
1784
  return true;
1518
1785
  }
@@ -1564,6 +1831,51 @@ var SbasProcessor = class {
1564
1831
  }
1565
1832
  return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
1566
1833
  }
1834
+ /** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
1835
+ * two satellites per message, each an upper-triangular Cholesky factor E and a
1836
+ * scale exponent that together define the relative clock-ephemeris covariance
1837
+ * used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
1838
+ * the current MT1 mask, so a mismatched IODP is dropped. */
1839
+ decodeMt28(msg) {
1840
+ if (this.iodp !== getBitU(msg, 14, 2)) return false;
1841
+ for (const base of [16, 121]) {
1842
+ const slot = getBitU(msg, base, 6);
1843
+ const prn = this.sats[slot]?.prn;
1844
+ const scaleExp = getBitU(msg, base + 6, 3);
1845
+ const e = [
1846
+ getBitU(msg, base + 9, 9),
1847
+ getBitU(msg, base + 18, 9),
1848
+ getBitU(msg, base + 27, 9),
1849
+ getBitU(msg, base + 36, 9),
1850
+ getBitS(msg, base + 45, 10),
1851
+ getBitS(msg, base + 55, 10),
1852
+ getBitS(msg, base + 65, 10),
1853
+ getBitS(msg, base + 75, 10),
1854
+ getBitS(msg, base + 85, 10),
1855
+ getBitS(msg, base + 95, 10)
1856
+ ];
1857
+ if (prn && e.some((v) => v !== 0)) this.mt28.set(prn, { scaleExp, e });
1858
+ }
1859
+ return true;
1860
+ }
1861
+ /** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
1862
+ * δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
1863
+ * I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
1864
+ * Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
1865
+ mt28DeltaUdre(prn, los) {
1866
+ const m = this.mt28.get(prn);
1867
+ if (!m) return null;
1868
+ const sf = 2 ** (m.scaleExp - 5);
1869
+ const [e11, e22, e33, e44, e12, e13, e14, e23, e24, e34] = m.e;
1870
+ const [ix, iy, iz] = los;
1871
+ const v0 = (e11 * ix + e12 * iy + e13 * iz + e14) * sf;
1872
+ const v1 = (e22 * iy + e23 * iz + e24) * sf;
1873
+ const v2 = (e33 * iz + e34) * sf;
1874
+ const v3 = e44 * sf;
1875
+ const iCi = v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
1876
+ const ec = (this.degr?.ccovariance ?? 0) * sf;
1877
+ return Math.sqrt(iCi) + ec;
1878
+ }
1567
1879
  decodeMixed(msg, week, tow) {
1568
1880
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
1569
1881
  const blk = getBitU(msg, 112, 2);
@@ -1768,7 +2080,7 @@ var SbasProcessor = class {
1768
2080
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
1769
2081
  * to the satellite ECEF position and `dClkS` to its clock bias.
1770
2082
  */
1771
- satCorrection(prn, week, tow, userLatRad, userLonRad) {
2083
+ satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
1772
2084
  const idx = this.satIdx.get(prn);
1773
2085
  if (idx === void 0) return null;
1774
2086
  const s = this.sats[idx];
@@ -1790,7 +2102,8 @@ var SbasProcessor = class {
1790
2102
  const tf = now - s.fcorr.t0s + this.tlat;
1791
2103
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
1792
2104
  const prc = s.fcorr.prc;
1793
- const dudre = userLatRad != null && userLonRad != null ? this.deltaUdre(
2105
+ const mt28 = losUnitEcef ? this.mt28DeltaUdre(prn, losUnitEcef) : null;
2106
+ const dudre = mt28 != null ? mt28 : userLatRad != null && userLonRad != null ? this.deltaUdre(
1794
2107
  userLatRad * 180 / Math.PI,
1795
2108
  userLonRad * 180 / Math.PI
1796
2109
  ) : 1;
@@ -1919,14 +2232,14 @@ var SbasProcessor = class {
1919
2232
  };
1920
2233
 
1921
2234
  // src/positioning/ssr-apply.ts
1922
- var cross = (a, b) => [
2235
+ var cross2 = (a, b) => [
1923
2236
  a[1] * b[2] - a[2] * b[1],
1924
2237
  a[2] * b[0] - a[0] * b[2],
1925
2238
  a[0] * b[1] - a[1] * b[0]
1926
2239
  ];
1927
- var norm = (a) => Math.hypot(a[0], a[1], a[2]);
2240
+ var norm2 = (a) => Math.hypot(a[0], a[1], a[2]);
1928
2241
  var unit = (a) => {
1929
- const n = norm(a);
2242
+ const n = norm2(a);
1930
2243
  return n > 0 ? [a[0] / n, a[1] / n, a[2] / n] : [0, 0, 0];
1931
2244
  };
1932
2245
  function applyOrbitClock(eph, corr, timeMs) {
@@ -1940,8 +2253,8 @@ function applyOrbitClock(eph, corr, timeMs) {
1940
2253
  const p = [s.x, s.y, s.z];
1941
2254
  const v = [s.vx, s.vy, s.vz];
1942
2255
  const er = unit(p);
1943
- const en = unit(cross(p, v));
1944
- const ea = cross(en, er);
2256
+ const en = unit(cross2(p, v));
2257
+ const ea = cross2(en, er);
1945
2258
  const dr = corr.radial + (corr.dotRadial ?? 0) * age;
1946
2259
  const da = corr.along + (corr.dotAlong ?? 0) * age;
1947
2260
  const dc = corr.cross + (corr.dotCross ?? 0) * age;
@@ -2151,170 +2464,6 @@ function solidEarthTide(rcv, sun, moon) {
2151
2464
  return disp;
2152
2465
  }
2153
2466
 
2154
- // src/positioning/ppp-antenna.ts
2155
- function parseAntexTime(s) {
2156
- const p = s.trim().split(/\s+/).map(Number);
2157
- if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
2158
- return Date.UTC(
2159
- p[0],
2160
- (p[1] || 1) - 1,
2161
- p[2] || 1,
2162
- p[3] ?? 0,
2163
- p[4] ?? 0,
2164
- Math.floor(p[5] ?? 0)
2165
- );
2166
- }
2167
- function freqOf(entry, code) {
2168
- return entry.frequencies.find((f) => f.frequency === code);
2169
- }
2170
- function combineIf(fA, fB, f1, f2) {
2171
- const g = f1 * f1 / (f1 * f1 - f2 * f2);
2172
- const mm = 1e-3;
2173
- const pco = [
2174
- (g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
2175
- (g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
2176
- (g * fA.pcoU - (g - 1) * fB.pcoU) * mm
2177
- ];
2178
- const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
2179
- const pcvNoazi = new Array(nz);
2180
- for (let i = 0; i < nz; i++) {
2181
- pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
2182
- }
2183
- return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
2184
- }
2185
- function buildPppAntenna(antex) {
2186
- const sats = /* @__PURE__ */ new Map();
2187
- const rcvs = /* @__PURE__ */ new Map();
2188
- for (const a of antex.antennas) {
2189
- if (a.isSatellite) {
2190
- const list = sats.get(a.serialNo) ?? [];
2191
- list.push(a);
2192
- sats.set(a.serialNo, list);
2193
- } else if (!rcvs.has(a.type)) {
2194
- rcvs.set(a.type, a);
2195
- }
2196
- }
2197
- const withZen = (entry, off) => ({
2198
- ...off,
2199
- pcvZen1Deg: entry.zen1,
2200
- pcvDzenDeg: entry.dzen
2201
- });
2202
- return {
2203
- satOffset(prn, dateMs, band1, band2, f1, f2) {
2204
- const list = sats.get(prn);
2205
- if (!list) return null;
2206
- const entry = list.find((e) => {
2207
- const from = parseAntexTime(e.validFrom);
2208
- const until = parseAntexTime(e.validUntil);
2209
- return (from == null || dateMs >= from) && (until == null || dateMs <= until);
2210
- });
2211
- if (!entry) return null;
2212
- const a = freqOf(entry, band1);
2213
- const b = freqOf(entry, band2);
2214
- if (!a || !b) return null;
2215
- return withZen(entry, combineIf(a, b, f1, f2));
2216
- },
2217
- rcvOffset(antType, band1, band2, f1, f2) {
2218
- const entry = rcvs.get(antType);
2219
- if (!entry) return null;
2220
- const a = freqOf(entry, band1);
2221
- const b = freqOf(entry, band2);
2222
- if (!a || !b) return null;
2223
- return withZen(entry, combineIf(a, b, f1, f2));
2224
- }
2225
- };
2226
- }
2227
- function cross2(a, b) {
2228
- return [
2229
- a[1] * b[2] - a[2] * b[1],
2230
- a[2] * b[0] - a[0] * b[2],
2231
- a[0] * b[1] - a[1] * b[0]
2232
- ];
2233
- }
2234
- function norm2(a) {
2235
- const n = Math.hypot(a[0], a[1], a[2]) || 1;
2236
- return [a[0] / n, a[1] / n, a[2] / n];
2237
- }
2238
- function dot(a, b) {
2239
- return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
2240
- }
2241
- function satBodyFrame(satEcef, sunEcef2) {
2242
- const z = norm2([-satEcef[0], -satEcef[1], -satEcef[2]]);
2243
- const eSun = norm2([
2244
- sunEcef2[0] - satEcef[0],
2245
- sunEcef2[1] - satEcef[1],
2246
- sunEcef2[2] - satEcef[2]
2247
- ]);
2248
- const y = norm2(cross2(z, eSun));
2249
- const x = norm2(cross2(y, z));
2250
- return { x, y, z };
2251
- }
2252
- function satPcoToEcef(pcoBody, frame) {
2253
- return [
2254
- pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
2255
- pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
2256
- pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
2257
- ];
2258
- }
2259
- function enuToEcef(enu, latRad, lonRad) {
2260
- const sl = Math.sin(latRad);
2261
- const cl = Math.cos(latRad);
2262
- const so = Math.sin(lonRad);
2263
- const co = Math.cos(lonRad);
2264
- const [e, n, u] = enu;
2265
- return [
2266
- -so * e - sl * co * n + cl * co * u,
2267
- co * e - sl * so * n + cl * so * u,
2268
- cl * n + sl * u
2269
- ];
2270
- }
2271
- function interpPcv(off, angleDeg) {
2272
- const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
2273
- if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
2274
- const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
2275
- if (x <= 0) return pcvNoazi[0];
2276
- if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
2277
- const i = Math.floor(x);
2278
- const t = x - i;
2279
- return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
2280
- }
2281
- function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
2282
- const k = [
2283
- -losRcvToSat[0],
2284
- -losRcvToSat[1],
2285
- -losRcvToSat[2]
2286
- ];
2287
- const kx = satFrame.x;
2288
- const ky = satFrame.y;
2289
- const kdotx = dot(k, kx);
2290
- const kcrossY = cross2(k, ky);
2291
- const dS = [
2292
- kx[0] - k[0] * kdotx - kcrossY[0],
2293
- kx[1] - k[1] * kdotx - kcrossY[1],
2294
- kx[2] - k[2] * kdotx - kcrossY[2]
2295
- ];
2296
- const north = enuToEcef([0, 1, 0], latRad, lonRad);
2297
- const east = enuToEcef([1, 0, 0], latRad, lonRad);
2298
- const kdotn = dot(k, north);
2299
- const kcrossE = cross2(k, east);
2300
- const dR = [
2301
- north[0] - k[0] * kdotn + kcrossE[0],
2302
- north[1] - k[1] * kdotn + kcrossE[1],
2303
- north[2] - k[2] * kdotn + kcrossE[2]
2304
- ];
2305
- const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
2306
- const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
2307
- let cosPhi = dot(dS, dR) / (nS * nR);
2308
- cosPhi = Math.max(-1, Math.min(1, cosPhi));
2309
- const zeta = cross2(dS, dR);
2310
- const sign = dot(k, zeta) >= 0 ? 1 : -1;
2311
- const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
2312
- const prev = state.get(prn);
2313
- const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
2314
- state.set(prn, wu);
2315
- return wu;
2316
- }
2317
-
2318
2467
  // src/positioning/ppp-corrections.ts
2319
2468
  function applyCorrections(corrections, geom, ctx) {
2320
2469
  if (corrections.evaluate) return corrections.evaluate(geom, ctx);
@@ -3674,7 +3823,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3674
3823
  used.push(prn);
3675
3824
  azels.push(azel);
3676
3825
  if (sbas) {
3677
- const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon);
3826
+ const dx = sat.x - x, dy = sat.y - y, dz = sat.z - z;
3827
+ const dr = Math.hypot(dx, dy, dz) || 1;
3828
+ const los = [dx / dr, dy / dr, dz / dr];
3829
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon, los);
3678
3830
  const si = sbas.ionoDelay(
3679
3831
  gpsWeek,
3680
3832
  gpsTow,
@@ -3714,6 +3866,10 @@ export {
3714
3866
  gimSlantIonoDelayL1,
3715
3867
  lambdaSearch,
3716
3868
  lambdaReduction,
3869
+ buildPppAntenna,
3870
+ buildRtkAntenna,
3871
+ rcvAntennaRangeM,
3872
+ antexFreqOfGroup,
3717
3873
  toRtkEpoch,
3718
3874
  solveDgnss,
3719
3875
  RtkFloatEngine,
@@ -3728,7 +3884,6 @@ export {
3728
3884
  sunEcef,
3729
3885
  moonEcef,
3730
3886
  solidEarthTide,
3731
- buildPppAntenna,
3732
3887
  createPppCorrections,
3733
3888
  Sp3EphemerisSource,
3734
3889
  PppEngine,