gnss-js 2.3.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.
@@ -1649,6 +1649,10 @@ var SbasProcessor = class {
1649
1649
  tlat = 0;
1650
1650
  degr = null;
1651
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();
1652
1656
  ion = [];
1653
1657
  /** GEO PRN(s) whose messages have been ingested. */
1654
1658
  geoPrns = /* @__PURE__ */ new Set();
@@ -1679,6 +1683,8 @@ var SbasProcessor = class {
1679
1683
  return this.decodeMt10(msg) ? type : -1;
1680
1684
  case 27:
1681
1685
  return this.decodeMt27(msg) ? type : -1;
1686
+ case 28:
1687
+ return this.decodeMt28(msg) ? type : -1;
1682
1688
  case 18:
1683
1689
  return this.decodeIgpMask(msg) ? type : -1;
1684
1690
  case 24:
@@ -1705,6 +1711,7 @@ var SbasProcessor = class {
1705
1711
  return true;
1706
1712
  this.iodp = iodp;
1707
1713
  this.sats = prns.map((prn) => ({ prn }));
1714
+ this.mt28.clear();
1708
1715
  this.satIdx.clear();
1709
1716
  this.sats.forEach((s, k) => {
1710
1717
  if (s.prn) this.satIdx.set(s.prn, k);
@@ -1770,7 +1777,9 @@ var SbasProcessor = class {
1770
1777
  iiono: u(117, 9) || 1,
1771
1778
  cionoRamp: u(126, 10) * 5e-6,
1772
1779
  rssUdre: u(136, 1) === 1,
1773
- 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
1774
1783
  };
1775
1784
  return true;
1776
1785
  }
@@ -1822,6 +1831,51 @@ var SbasProcessor = class {
1822
1831
  }
1823
1832
  return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
1824
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
+ }
1825
1879
  decodeMixed(msg, week, tow) {
1826
1880
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
1827
1881
  const blk = getBitU(msg, 112, 2);
@@ -2026,7 +2080,7 @@ var SbasProcessor = class {
2026
2080
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
2027
2081
  * to the satellite ECEF position and `dClkS` to its clock bias.
2028
2082
  */
2029
- satCorrection(prn, week, tow, userLatRad, userLonRad) {
2083
+ satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
2030
2084
  const idx = this.satIdx.get(prn);
2031
2085
  if (idx === void 0) return null;
2032
2086
  const s = this.sats[idx];
@@ -2048,7 +2102,8 @@ var SbasProcessor = class {
2048
2102
  const tf = now - s.fcorr.t0s + this.tlat;
2049
2103
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
2050
2104
  const prc = s.fcorr.prc;
2051
- 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(
2052
2107
  userLatRad * 180 / Math.PI,
2053
2108
  userLonRad * 180 / Math.PI
2054
2109
  ) : 1;
@@ -3768,7 +3823,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3768
3823
  used.push(prn);
3769
3824
  azels.push(azel);
3770
3825
  if (sbas) {
3771
- 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);
3772
3830
  const si = sbas.ionoDelay(
3773
3831
  gpsWeek,
3774
3832
  gpsTow,
package/dist/index.cjs CHANGED
@@ -14576,6 +14576,10 @@ var SbasProcessor = class {
14576
14576
  tlat = 0;
14577
14577
  degr = null;
14578
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();
14579
14583
  ion = [];
14580
14584
  /** GEO PRN(s) whose messages have been ingested. */
14581
14585
  geoPrns = /* @__PURE__ */ new Set();
@@ -14606,6 +14610,8 @@ var SbasProcessor = class {
14606
14610
  return this.decodeMt10(msg) ? type : -1;
14607
14611
  case 27:
14608
14612
  return this.decodeMt27(msg) ? type : -1;
14613
+ case 28:
14614
+ return this.decodeMt28(msg) ? type : -1;
14609
14615
  case 18:
14610
14616
  return this.decodeIgpMask(msg) ? type : -1;
14611
14617
  case 24:
@@ -14632,6 +14638,7 @@ var SbasProcessor = class {
14632
14638
  return true;
14633
14639
  this.iodp = iodp;
14634
14640
  this.sats = prns.map((prn) => ({ prn }));
14641
+ this.mt28.clear();
14635
14642
  this.satIdx.clear();
14636
14643
  this.sats.forEach((s, k) => {
14637
14644
  if (s.prn) this.satIdx.set(s.prn, k);
@@ -14697,7 +14704,9 @@ var SbasProcessor = class {
14697
14704
  iiono: u(117, 9) || 1,
14698
14705
  cionoRamp: u(126, 10) * 5e-6,
14699
14706
  rssUdre: u(136, 1) === 1,
14700
- 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
14701
14710
  };
14702
14711
  return true;
14703
14712
  }
@@ -14749,6 +14758,51 @@ var SbasProcessor = class {
14749
14758
  }
14750
14759
  return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
14751
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
+ }
14752
14806
  decodeMixed(msg, week, tow) {
14753
14807
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
14754
14808
  const blk = getBitU(msg, 112, 2);
@@ -14953,7 +15007,7 @@ var SbasProcessor = class {
14953
15007
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
14954
15008
  * to the satellite ECEF position and `dClkS` to its clock bias.
14955
15009
  */
14956
- satCorrection(prn, week, tow, userLatRad, userLonRad) {
15010
+ satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
14957
15011
  const idx = this.satIdx.get(prn);
14958
15012
  if (idx === void 0) return null;
14959
15013
  const s = this.sats[idx];
@@ -14975,7 +15029,8 @@ var SbasProcessor = class {
14975
15029
  const tf = now - s.fcorr.t0s + this.tlat;
14976
15030
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
14977
15031
  const prc = s.fcorr.prc;
14978
- 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(
14979
15034
  userLatRad * 180 / Math.PI,
14980
15035
  userLonRad * 180 / Math.PI
14981
15036
  ) : 1;
@@ -16695,7 +16750,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
16695
16750
  used.push(prn);
16696
16751
  azels.push(azel);
16697
16752
  if (sbas) {
16698
- 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);
16699
16757
  const si = sbas.ionoDelay(
16700
16758
  gpsWeek,
16701
16759
  gpsTow,
package/dist/index.js CHANGED
@@ -209,7 +209,7 @@ import {
209
209
  sunEcef,
210
210
  toRtkEpoch,
211
211
  wlWavelength
212
- } from "./chunk-TYJNN77C.js";
212
+ } from "./chunk-B2XWO4RP.js";
213
213
  import "./chunk-WQX354KD.js";
214
214
  import {
215
215
  almanacEpochMs,
@@ -2132,6 +2132,10 @@ var SbasProcessor = class {
2132
2132
  tlat = 0;
2133
2133
  degr = null;
2134
2134
  mt27 = null;
2135
+ /** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
2136
+ * factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
2137
+ * scale exponent. Keyed by PRN; cleared on a mask change. */
2138
+ mt28 = /* @__PURE__ */ new Map();
2135
2139
  ion = [];
2136
2140
  /** GEO PRN(s) whose messages have been ingested. */
2137
2141
  geoPrns = /* @__PURE__ */ new Set();
@@ -2162,6 +2166,8 @@ var SbasProcessor = class {
2162
2166
  return this.decodeMt10(msg) ? type : -1;
2163
2167
  case 27:
2164
2168
  return this.decodeMt27(msg) ? type : -1;
2169
+ case 28:
2170
+ return this.decodeMt28(msg) ? type : -1;
2165
2171
  case 18:
2166
2172
  return this.decodeIgpMask(msg) ? type : -1;
2167
2173
  case 24:
@@ -2188,6 +2194,7 @@ var SbasProcessor = class {
2188
2194
  return true;
2189
2195
  this.iodp = iodp;
2190
2196
  this.sats = prns.map((prn) => ({ prn }));
2197
+ this.mt28.clear();
2191
2198
  this.satIdx.clear();
2192
2199
  this.sats.forEach((s, k) => {
2193
2200
  if (s.prn) this.satIdx.set(s.prn, k);
@@ -2253,7 +2260,9 @@ var SbasProcessor = class {
2253
2260
  iiono: u(117, 9) || 1,
2254
2261
  cionoRamp: u(126, 10) * 5e-6,
2255
2262
  rssUdre: u(136, 1) === 1,
2256
- rssIono: u(137, 1) === 1
2263
+ rssIono: u(137, 1) === 1,
2264
+ ccovariance: u(138, 7) * 0.1
2265
+ // Table B-49: 0..12.7 m, 0.1 m
2257
2266
  };
2258
2267
  return true;
2259
2268
  }
@@ -2305,6 +2314,51 @@ var SbasProcessor = class {
2305
2314
  }
2306
2315
  return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
2307
2316
  }
2317
+ /** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
2318
+ * two satellites per message, each an upper-triangular Cholesky factor E and a
2319
+ * scale exponent that together define the relative clock-ephemeris covariance
2320
+ * used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
2321
+ * the current MT1 mask, so a mismatched IODP is dropped. */
2322
+ decodeMt28(msg) {
2323
+ if (this.iodp !== getBitU(msg, 14, 2)) return false;
2324
+ for (const base of [16, 121]) {
2325
+ const slot = getBitU(msg, base, 6);
2326
+ const prn = this.sats[slot]?.prn;
2327
+ const scaleExp = getBitU(msg, base + 6, 3);
2328
+ const e = [
2329
+ getBitU(msg, base + 9, 9),
2330
+ getBitU(msg, base + 18, 9),
2331
+ getBitU(msg, base + 27, 9),
2332
+ getBitU(msg, base + 36, 9),
2333
+ getBitS(msg, base + 45, 10),
2334
+ getBitS(msg, base + 55, 10),
2335
+ getBitS(msg, base + 65, 10),
2336
+ getBitS(msg, base + 75, 10),
2337
+ getBitS(msg, base + 85, 10),
2338
+ getBitS(msg, base + 95, 10)
2339
+ ];
2340
+ if (prn && e.some((v) => v !== 0)) this.mt28.set(prn, { scaleExp, e });
2341
+ }
2342
+ return true;
2343
+ }
2344
+ /** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
2345
+ * δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
2346
+ * I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
2347
+ * Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
2348
+ mt28DeltaUdre(prn, los) {
2349
+ const m = this.mt28.get(prn);
2350
+ if (!m) return null;
2351
+ const sf = 2 ** (m.scaleExp - 5);
2352
+ const [e11, e22, e33, e44, e12, e13, e14, e23, e24, e34] = m.e;
2353
+ const [ix, iy, iz] = los;
2354
+ const v0 = (e11 * ix + e12 * iy + e13 * iz + e14) * sf;
2355
+ const v1 = (e22 * iy + e23 * iz + e24) * sf;
2356
+ const v2 = (e33 * iz + e34) * sf;
2357
+ const v3 = e44 * sf;
2358
+ const iCi = v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
2359
+ const ec = (this.degr?.ccovariance ?? 0) * sf;
2360
+ return Math.sqrt(iCi) + ec;
2361
+ }
2308
2362
  decodeMixed(msg, week, tow) {
2309
2363
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
2310
2364
  const blk = getBitU(msg, 112, 2);
@@ -2509,7 +2563,7 @@ var SbasProcessor = class {
2509
2563
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
2510
2564
  * to the satellite ECEF position and `dClkS` to its clock bias.
2511
2565
  */
2512
- satCorrection(prn, week, tow, userLatRad, userLonRad) {
2566
+ satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
2513
2567
  const idx = this.satIdx.get(prn);
2514
2568
  if (idx === void 0) return null;
2515
2569
  const s = this.sats[idx];
@@ -2531,7 +2585,8 @@ var SbasProcessor = class {
2531
2585
  const tf = now - s.fcorr.t0s + this.tlat;
2532
2586
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
2533
2587
  const prc = s.fcorr.prc;
2534
- const dudre = userLatRad != null && userLonRad != null ? this.deltaUdre(
2588
+ const mt28 = losUnitEcef ? this.mt28DeltaUdre(prn, losUnitEcef) : null;
2589
+ const dudre = mt28 != null ? mt28 : userLatRad != null && userLonRad != null ? this.deltaUdre(
2535
2590
  userLatRad * 180 / Math.PI,
2536
2591
  userLonRad * 180 / Math.PI
2537
2592
  ) : 1;
@@ -4323,7 +4378,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
4323
4378
  used.push(prn);
4324
4379
  azels.push(azel);
4325
4380
  if (sbas) {
4326
- const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon);
4381
+ const dx = sat.x - x, dy = sat.y - y, dz = sat.z - z;
4382
+ const dr = Math.hypot(dx, dy, dz) || 1;
4383
+ const los = [dx / dr, dy / dr, dz / dr];
4384
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon, los);
4327
4385
  const si = sbas.ionoDelay(
4328
4386
  gpsWeek,
4329
4387
  gpsTow,
@@ -668,6 +668,8 @@ interface Degradation {
668
668
  cionoRamp: number;
669
669
  rssUdre: boolean;
670
670
  rssIono: boolean;
671
+ /** Cᶜᵒᵛᵃʳⁱᵃⁿᶜᵉ — the MT28 δUDRE quantization term (m), 0 when no MT28. */
672
+ ccovariance: number;
671
673
  }
672
674
  /** A per-satellite SBAS correction, ready to apply in an SPP solve. */
673
675
  interface SbasSatCorrection {
@@ -715,6 +717,10 @@ declare class SbasProcessor {
715
717
  private tlat;
716
718
  private degr;
717
719
  private mt27;
720
+ /** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
721
+ * factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
722
+ * scale exponent. Keyed by PRN; cleared on a mask change. */
723
+ private mt28;
718
724
  private ion;
719
725
  /** GEO PRN(s) whose messages have been ingested. */
720
726
  readonly geoPrns: Set<string>;
@@ -739,6 +745,17 @@ declare class SbasProcessor {
739
745
  * set: the highest-priority containing region wins (ties → lower δUDRE),
740
746
  * else the outside value. 1 when no MT27 has been received. */
741
747
  deltaUdre(latDeg: number, lonDeg: number): number;
748
+ /** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
749
+ * two satellites per message, each an upper-triangular Cholesky factor E and a
750
+ * scale exponent that together define the relative clock-ephemeris covariance
751
+ * used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
752
+ * the current MT1 mask, so a mismatched IODP is dropped. */
753
+ private decodeMt28;
754
+ /** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
755
+ * δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
756
+ * I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
757
+ * Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
758
+ private mt28DeltaUdre;
742
759
  private decodeMixed;
743
760
  private decodeLongCorrHalf;
744
761
  private decodeLong0;
@@ -782,7 +799,11 @@ declare class SbasProcessor {
782
799
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
783
800
  * to the satellite ECEF position and `dClkS` to its clock bias.
784
801
  */
785
- satCorrection(prn: string, week: number, tow: number, userLatRad?: number, userLonRad?: number): SbasSatCorrection | null;
802
+ satCorrection(prn: string, week: number, tow: number, userLatRad?: number, userLonRad?: number,
803
+ /** Unit line-of-sight vector user→satellite in ECEF, for the MT28
804
+ * geometry-dependent δUDRE (final protection-level pass). When given and
805
+ * MT28 data exists for the PRN, it supersedes the MT27 region δUDRE. */
806
+ losUnitEcef?: readonly [number, number, number]): SbasSatCorrection | null;
786
807
  /** The last-decoded MT10 degradation factors, or null if none seen. */
787
808
  get degradation(): Readonly<Degradation> | null;
788
809
  /**
@@ -1557,7 +1578,10 @@ interface SbasCorrectionSource {
1557
1578
  satCorrection(prn: string, week: number, tow: number,
1558
1579
  /** User geodetic lat/lon (rad) for the MT27 service-region δUDRE, when
1559
1580
  * known (the final protection-level pass). Omitted during iteration. */
1560
- userLatRad?: number, userLonRad?: number): {
1581
+ userLatRad?: number, userLonRad?: number,
1582
+ /** Unit LOS user→satellite (ECEF) for the MT28 geometry-dependent δUDRE
1583
+ * (final protection-level pass). */
1584
+ losUnitEcef?: readonly [number, number, number]): {
1561
1585
  dPos: [number, number, number];
1562
1586
  dClkS: number;
1563
1587
  varM2: number;
@@ -668,6 +668,8 @@ interface Degradation {
668
668
  cionoRamp: number;
669
669
  rssUdre: boolean;
670
670
  rssIono: boolean;
671
+ /** Cᶜᵒᵛᵃʳⁱᵃⁿᶜᵉ — the MT28 δUDRE quantization term (m), 0 when no MT28. */
672
+ ccovariance: number;
671
673
  }
672
674
  /** A per-satellite SBAS correction, ready to apply in an SPP solve. */
673
675
  interface SbasSatCorrection {
@@ -715,6 +717,10 @@ declare class SbasProcessor {
715
717
  private tlat;
716
718
  private degr;
717
719
  private mt27;
720
+ /** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
721
+ * factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
722
+ * scale exponent. Keyed by PRN; cleared on a mask change. */
723
+ private mt28;
718
724
  private ion;
719
725
  /** GEO PRN(s) whose messages have been ingested. */
720
726
  readonly geoPrns: Set<string>;
@@ -739,6 +745,17 @@ declare class SbasProcessor {
739
745
  * set: the highest-priority containing region wins (ties → lower δUDRE),
740
746
  * else the outside value. 1 when no MT27 has been received. */
741
747
  deltaUdre(latDeg: number, lonDeg: number): number;
748
+ /** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
749
+ * two satellites per message, each an upper-triangular Cholesky factor E and a
750
+ * scale exponent that together define the relative clock-ephemeris covariance
751
+ * used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
752
+ * the current MT1 mask, so a mismatched IODP is dropped. */
753
+ private decodeMt28;
754
+ /** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
755
+ * δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
756
+ * I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
757
+ * Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
758
+ private mt28DeltaUdre;
742
759
  private decodeMixed;
743
760
  private decodeLongCorrHalf;
744
761
  private decodeLong0;
@@ -782,7 +799,11 @@ declare class SbasProcessor {
782
799
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
783
800
  * to the satellite ECEF position and `dClkS` to its clock bias.
784
801
  */
785
- satCorrection(prn: string, week: number, tow: number, userLatRad?: number, userLonRad?: number): SbasSatCorrection | null;
802
+ satCorrection(prn: string, week: number, tow: number, userLatRad?: number, userLonRad?: number,
803
+ /** Unit line-of-sight vector user→satellite in ECEF, for the MT28
804
+ * geometry-dependent δUDRE (final protection-level pass). When given and
805
+ * MT28 data exists for the PRN, it supersedes the MT27 region δUDRE. */
806
+ losUnitEcef?: readonly [number, number, number]): SbasSatCorrection | null;
786
807
  /** The last-decoded MT10 degradation factors, or null if none seen. */
787
808
  get degradation(): Readonly<Degradation> | null;
788
809
  /**
@@ -1557,7 +1578,10 @@ interface SbasCorrectionSource {
1557
1578
  satCorrection(prn: string, week: number, tow: number,
1558
1579
  /** User geodetic lat/lon (rad) for the MT27 service-region δUDRE, when
1559
1580
  * known (the final protection-level pass). Omitted during iteration. */
1560
- userLatRad?: number, userLonRad?: number): {
1581
+ userLatRad?: number, userLonRad?: number,
1582
+ /** Unit LOS user→satellite (ECEF) for the MT28 geometry-dependent δUDRE
1583
+ * (final protection-level pass). */
1584
+ losUnitEcef?: readonly [number, number, number]): {
1561
1585
  dPos: [number, number, number];
1562
1586
  dClkS: number;
1563
1587
  varM2: number;
@@ -39,7 +39,7 @@ import {
39
39
  sunEcef,
40
40
  toRtkEpoch,
41
41
  wlWavelength
42
- } from "./chunk-TYJNN77C.js";
42
+ } from "./chunk-B2XWO4RP.js";
43
43
  import "./chunk-WQX354KD.js";
44
44
  import "./chunk-JYWYQ7DW.js";
45
45
  import "./chunk-HVXYFUCB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
5
5
  "type": "module",
6
6
  "sideEffects": false,