gnss-js 1.58.0 → 1.60.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.
@@ -1213,7 +1213,7 @@ var D2R = Math.PI / 180;
1213
1213
  var R2D = 180 / Math.PI;
1214
1214
  var SEC_PER_WEEK = 604800;
1215
1215
  var MAXSBSAGEF = 30;
1216
- var MAXSBSAGEL = 1800;
1216
+ var MAXSBSAGEL = 360;
1217
1217
  var MAXBAND = 10;
1218
1218
  var two = (n) => String(n).padStart(2, "0");
1219
1219
  var VAR_FCORR = [
@@ -1270,6 +1270,44 @@ var DEGF = [
1270
1270
  58e-4
1271
1271
  ];
1272
1272
  var degfcorr = (ai) => ai > 0 && ai <= 15 ? DEGF[ai] : 58e-4;
1273
+ var DELTA_UDRE = [
1274
+ 1,
1275
+ 1.1,
1276
+ 1.25,
1277
+ 1.5,
1278
+ 2,
1279
+ 3,
1280
+ 4,
1281
+ 5,
1282
+ 6,
1283
+ 8,
1284
+ 10,
1285
+ 20,
1286
+ 30,
1287
+ 40,
1288
+ 50,
1289
+ 100
1290
+ ];
1291
+ function pointInRegion(lat, lon, rg) {
1292
+ const poly = rg.square ? [
1293
+ [rg.lat1, rg.lon1],
1294
+ [rg.lat1, rg.lon2],
1295
+ [rg.lat2, rg.lon2],
1296
+ [rg.lat2, rg.lon1]
1297
+ ] : [
1298
+ [rg.lat1, rg.lon1],
1299
+ [rg.lat2, rg.lon2],
1300
+ [rg.lat1, rg.lon2]
1301
+ ];
1302
+ let inside = false;
1303
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
1304
+ const [yi, xi] = poly[i];
1305
+ const [yj, xj] = poly[j];
1306
+ if (yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi)
1307
+ inside = !inside;
1308
+ }
1309
+ return inside;
1310
+ }
1273
1311
  function sbasLongTermDeg(d, vel, ageSec) {
1274
1312
  if (vel) {
1275
1313
  if (ageSec > 0 && ageSec < d.iltcV1) return 0;
@@ -1335,6 +1373,7 @@ var SbasProcessor = class {
1335
1373
  iodp = -1;
1336
1374
  tlat = 0;
1337
1375
  degr = null;
1376
+ mt27 = null;
1338
1377
  ion = [];
1339
1378
  /** GEO PRN(s) whose messages have been ingested. */
1340
1379
  geoPrns = /* @__PURE__ */ new Set();
@@ -1363,6 +1402,8 @@ var SbasProcessor = class {
1363
1402
  return this.decodeDegradation(msg) ? type : -1;
1364
1403
  case 10:
1365
1404
  return this.decodeMt10(msg) ? type : -1;
1405
+ case 27:
1406
+ return this.decodeMt27(msg) ? type : -1;
1366
1407
  case 18:
1367
1408
  return this.decodeIgpMask(msg) ? type : -1;
1368
1409
  case 24:
@@ -1455,6 +1496,54 @@ var SbasProcessor = class {
1455
1496
  };
1456
1497
  return true;
1457
1498
  }
1499
+ /** MT27 SBAS service message (DO-229D §A.4.4.13, Table A-20): regions with a
1500
+ * δUDRE that inflates σ_UDRE for users inside/outside them. */
1501
+ decodeMt27(msg) {
1502
+ const iods = getBitU(msg, 14, 3);
1503
+ const svcNum = getBitU(msg, 20, 3) + 1;
1504
+ const nRegions = getBitU(msg, 23, 3);
1505
+ const priority = getBitU(msg, 26, 2);
1506
+ const udreInside = getBitU(msg, 28, 4);
1507
+ const udreOutside = getBitU(msg, 32, 4);
1508
+ if (!this.mt27 || this.mt27.iods !== iods)
1509
+ this.mt27 = { iods, udreOutside, msgs: /* @__PURE__ */ new Map() };
1510
+ this.mt27.udreOutside = udreOutside;
1511
+ const regions = [];
1512
+ for (let r = 0; r < nRegions && r < 5; r++) {
1513
+ const p = 36 + r * 35;
1514
+ regions.push({
1515
+ lat1: getBitS(msg, p, 8),
1516
+ lon1: getBitS(msg, p + 8, 9),
1517
+ lat2: getBitS(msg, p + 17, 8),
1518
+ lon2: getBitS(msg, p + 25, 9),
1519
+ square: getBitU(msg, p + 34, 1) === 1
1520
+ });
1521
+ }
1522
+ this.mt27.msgs.set(svcNum, { priority, udreInside, regions });
1523
+ return true;
1524
+ }
1525
+ /** δUDRE multiplier (DO-229D Table A-21) for a user location from the MT27
1526
+ * set: the highest-priority containing region wins (ties → lower δUDRE),
1527
+ * else the outside value. 1 when no MT27 has been received. */
1528
+ deltaUdre(latDeg, lonDeg) {
1529
+ const s = this.mt27;
1530
+ if (!s) return 1;
1531
+ let best = null;
1532
+ let bestPri = -1;
1533
+ for (const m of s.msgs.values()) {
1534
+ for (const rg of m.regions) {
1535
+ if (!pointInRegion(latDeg, lonDeg, rg)) continue;
1536
+ const val = DELTA_UDRE[m.udreInside] ?? 1;
1537
+ if (m.priority > bestPri) {
1538
+ bestPri = m.priority;
1539
+ best = val;
1540
+ } else if (m.priority === bestPri && best !== null) {
1541
+ best = Math.min(best, val);
1542
+ }
1543
+ }
1544
+ }
1545
+ return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
1546
+ }
1458
1547
  decodeMixed(msg, week, tow) {
1459
1548
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
1460
1549
  const blk = getBitU(msg, 112, 2);
@@ -1626,7 +1715,7 @@ var SbasProcessor = class {
1626
1715
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
1627
1716
  * to the satellite ECEF position and `dClkS` to its clock bias.
1628
1717
  */
1629
- satCorrection(prn, week, tow) {
1718
+ satCorrection(prn, week, tow, userLatRad, userLonRad) {
1630
1719
  const idx = this.satIdx.get(prn);
1631
1720
  if (idx === void 0) return null;
1632
1721
  const s = this.sats[idx];
@@ -1648,7 +1737,11 @@ var SbasProcessor = class {
1648
1737
  const tf = now - s.fcorr.t0s + this.tlat;
1649
1738
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
1650
1739
  const prc = s.fcorr.prc;
1651
- const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre));
1740
+ const dudre = userLatRad != null && userLonRad != null ? this.deltaUdre(
1741
+ userLatRad * 180 / Math.PI,
1742
+ userLonRad * 180 / Math.PI
1743
+ ) : 1;
1744
+ const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre)) * dudre;
1652
1745
  const efc = degfcorr(s.fcorr.ai) * tf * tf / 2;
1653
1746
  const eltc = this.degr && s.lcorr && s.lcorr.t0s !== 0 ? sbasLongTermDeg(this.degr, s.lcorr.vel, now - s.lcorr.t0s) : 0;
1654
1747
  const varM2 = this.degr?.rssUdre ? sigUdre * sigUdre + efc * efc + eltc * eltc : (sigUdre + efc + eltc) ** 2;
@@ -3387,7 +3480,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3387
3480
  used.push(prn);
3388
3481
  azels.push(azel);
3389
3482
  if (sbas) {
3390
- const sc = sbas.satCorrection(prn, gpsWeek, gpsTow);
3483
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon);
3391
3484
  const si = sbas.ionoDelay(
3392
3485
  gpsWeek,
3393
3486
  gpsTow,
package/dist/index.cjs CHANGED
@@ -13423,7 +13423,7 @@ var D2R = Math.PI / 180;
13423
13423
  var R2D = 180 / Math.PI;
13424
13424
  var SEC_PER_WEEK12 = 604800;
13425
13425
  var MAXSBSAGEF = 30;
13426
- var MAXSBSAGEL = 1800;
13426
+ var MAXSBSAGEL = 360;
13427
13427
  var MAXBAND = 10;
13428
13428
  var two8 = (n) => String(n).padStart(2, "0");
13429
13429
  var VAR_FCORR = [
@@ -13480,6 +13480,44 @@ var DEGF = [
13480
13480
  58e-4
13481
13481
  ];
13482
13482
  var degfcorr = (ai) => ai > 0 && ai <= 15 ? DEGF[ai] : 58e-4;
13483
+ var DELTA_UDRE = [
13484
+ 1,
13485
+ 1.1,
13486
+ 1.25,
13487
+ 1.5,
13488
+ 2,
13489
+ 3,
13490
+ 4,
13491
+ 5,
13492
+ 6,
13493
+ 8,
13494
+ 10,
13495
+ 20,
13496
+ 30,
13497
+ 40,
13498
+ 50,
13499
+ 100
13500
+ ];
13501
+ function pointInRegion(lat, lon, rg) {
13502
+ const poly = rg.square ? [
13503
+ [rg.lat1, rg.lon1],
13504
+ [rg.lat1, rg.lon2],
13505
+ [rg.lat2, rg.lon2],
13506
+ [rg.lat2, rg.lon1]
13507
+ ] : [
13508
+ [rg.lat1, rg.lon1],
13509
+ [rg.lat2, rg.lon2],
13510
+ [rg.lat1, rg.lon2]
13511
+ ];
13512
+ let inside = false;
13513
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
13514
+ const [yi, xi] = poly[i];
13515
+ const [yj, xj] = poly[j];
13516
+ if (yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi)
13517
+ inside = !inside;
13518
+ }
13519
+ return inside;
13520
+ }
13483
13521
  function sbasLongTermDeg(d, vel, ageSec) {
13484
13522
  if (vel) {
13485
13523
  if (ageSec > 0 && ageSec < d.iltcV1) return 0;
@@ -13545,6 +13583,7 @@ var SbasProcessor = class {
13545
13583
  iodp = -1;
13546
13584
  tlat = 0;
13547
13585
  degr = null;
13586
+ mt27 = null;
13548
13587
  ion = [];
13549
13588
  /** GEO PRN(s) whose messages have been ingested. */
13550
13589
  geoPrns = /* @__PURE__ */ new Set();
@@ -13573,6 +13612,8 @@ var SbasProcessor = class {
13573
13612
  return this.decodeDegradation(msg) ? type : -1;
13574
13613
  case 10:
13575
13614
  return this.decodeMt10(msg) ? type : -1;
13615
+ case 27:
13616
+ return this.decodeMt27(msg) ? type : -1;
13576
13617
  case 18:
13577
13618
  return this.decodeIgpMask(msg) ? type : -1;
13578
13619
  case 24:
@@ -13665,6 +13706,54 @@ var SbasProcessor = class {
13665
13706
  };
13666
13707
  return true;
13667
13708
  }
13709
+ /** MT27 SBAS service message (DO-229D §A.4.4.13, Table A-20): regions with a
13710
+ * δUDRE that inflates σ_UDRE for users inside/outside them. */
13711
+ decodeMt27(msg) {
13712
+ const iods = getBitU(msg, 14, 3);
13713
+ const svcNum = getBitU(msg, 20, 3) + 1;
13714
+ const nRegions = getBitU(msg, 23, 3);
13715
+ const priority = getBitU(msg, 26, 2);
13716
+ const udreInside = getBitU(msg, 28, 4);
13717
+ const udreOutside = getBitU(msg, 32, 4);
13718
+ if (!this.mt27 || this.mt27.iods !== iods)
13719
+ this.mt27 = { iods, udreOutside, msgs: /* @__PURE__ */ new Map() };
13720
+ this.mt27.udreOutside = udreOutside;
13721
+ const regions = [];
13722
+ for (let r = 0; r < nRegions && r < 5; r++) {
13723
+ const p = 36 + r * 35;
13724
+ regions.push({
13725
+ lat1: getBitS(msg, p, 8),
13726
+ lon1: getBitS(msg, p + 8, 9),
13727
+ lat2: getBitS(msg, p + 17, 8),
13728
+ lon2: getBitS(msg, p + 25, 9),
13729
+ square: getBitU(msg, p + 34, 1) === 1
13730
+ });
13731
+ }
13732
+ this.mt27.msgs.set(svcNum, { priority, udreInside, regions });
13733
+ return true;
13734
+ }
13735
+ /** δUDRE multiplier (DO-229D Table A-21) for a user location from the MT27
13736
+ * set: the highest-priority containing region wins (ties → lower δUDRE),
13737
+ * else the outside value. 1 when no MT27 has been received. */
13738
+ deltaUdre(latDeg, lonDeg) {
13739
+ const s = this.mt27;
13740
+ if (!s) return 1;
13741
+ let best = null;
13742
+ let bestPri = -1;
13743
+ for (const m of s.msgs.values()) {
13744
+ for (const rg of m.regions) {
13745
+ if (!pointInRegion(latDeg, lonDeg, rg)) continue;
13746
+ const val = DELTA_UDRE[m.udreInside] ?? 1;
13747
+ if (m.priority > bestPri) {
13748
+ bestPri = m.priority;
13749
+ best = val;
13750
+ } else if (m.priority === bestPri && best !== null) {
13751
+ best = Math.min(best, val);
13752
+ }
13753
+ }
13754
+ }
13755
+ return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
13756
+ }
13668
13757
  decodeMixed(msg, week, tow) {
13669
13758
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
13670
13759
  const blk = getBitU(msg, 112, 2);
@@ -13836,7 +13925,7 @@ var SbasProcessor = class {
13836
13925
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
13837
13926
  * to the satellite ECEF position and `dClkS` to its clock bias.
13838
13927
  */
13839
- satCorrection(prn, week, tow) {
13928
+ satCorrection(prn, week, tow, userLatRad, userLonRad) {
13840
13929
  const idx = this.satIdx.get(prn);
13841
13930
  if (idx === void 0) return null;
13842
13931
  const s = this.sats[idx];
@@ -13858,7 +13947,11 @@ var SbasProcessor = class {
13858
13947
  const tf = now - s.fcorr.t0s + this.tlat;
13859
13948
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
13860
13949
  const prc = s.fcorr.prc;
13861
- const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre));
13950
+ const dudre = userLatRad != null && userLonRad != null ? this.deltaUdre(
13951
+ userLatRad * 180 / Math.PI,
13952
+ userLonRad * 180 / Math.PI
13953
+ ) : 1;
13954
+ const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre)) * dudre;
13862
13955
  const efc = degfcorr(s.fcorr.ai) * tf * tf / 2;
13863
13956
  const eltc = this.degr && s.lcorr && s.lcorr.t0s !== 0 ? sbasLongTermDeg(this.degr, s.lcorr.vel, now - s.lcorr.t0s) : 0;
13864
13957
  const varM2 = this.degr?.rssUdre ? sigUdre * sigUdre + efc * efc + eltc * eltc : (sigUdre + efc + eltc) ** 2;
@@ -15597,7 +15690,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
15597
15690
  used.push(prn);
15598
15691
  azels.push(azel);
15599
15692
  if (sbas) {
15600
- const sc = sbas.satCorrection(prn, gpsWeek, gpsTow);
15693
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon);
15601
15694
  const si = sbas.ionoDelay(
15602
15695
  gpsWeek,
15603
15696
  gpsTow,
package/dist/index.js CHANGED
@@ -178,7 +178,7 @@ import {
178
178
  sunEcef,
179
179
  toRtkEpoch,
180
180
  wlWavelength
181
- } from "./chunk-SWVIBUHS.js";
181
+ } from "./chunk-4WNIGDNR.js";
182
182
  import "./chunk-WQX354KD.js";
183
183
  import {
184
184
  almanacEpochMs,
@@ -1689,7 +1689,7 @@ var D2R = Math.PI / 180;
1689
1689
  var R2D = 180 / Math.PI;
1690
1690
  var SEC_PER_WEEK2 = 604800;
1691
1691
  var MAXSBSAGEF = 30;
1692
- var MAXSBSAGEL = 1800;
1692
+ var MAXSBSAGEL = 360;
1693
1693
  var MAXBAND = 10;
1694
1694
  var two = (n) => String(n).padStart(2, "0");
1695
1695
  var VAR_FCORR = [
@@ -1746,6 +1746,44 @@ var DEGF = [
1746
1746
  58e-4
1747
1747
  ];
1748
1748
  var degfcorr = (ai) => ai > 0 && ai <= 15 ? DEGF[ai] : 58e-4;
1749
+ var DELTA_UDRE = [
1750
+ 1,
1751
+ 1.1,
1752
+ 1.25,
1753
+ 1.5,
1754
+ 2,
1755
+ 3,
1756
+ 4,
1757
+ 5,
1758
+ 6,
1759
+ 8,
1760
+ 10,
1761
+ 20,
1762
+ 30,
1763
+ 40,
1764
+ 50,
1765
+ 100
1766
+ ];
1767
+ function pointInRegion(lat, lon, rg) {
1768
+ const poly = rg.square ? [
1769
+ [rg.lat1, rg.lon1],
1770
+ [rg.lat1, rg.lon2],
1771
+ [rg.lat2, rg.lon2],
1772
+ [rg.lat2, rg.lon1]
1773
+ ] : [
1774
+ [rg.lat1, rg.lon1],
1775
+ [rg.lat2, rg.lon2],
1776
+ [rg.lat1, rg.lon2]
1777
+ ];
1778
+ let inside = false;
1779
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
1780
+ const [yi, xi] = poly[i];
1781
+ const [yj, xj] = poly[j];
1782
+ if (yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi)
1783
+ inside = !inside;
1784
+ }
1785
+ return inside;
1786
+ }
1749
1787
  function sbasLongTermDeg(d, vel, ageSec) {
1750
1788
  if (vel) {
1751
1789
  if (ageSec > 0 && ageSec < d.iltcV1) return 0;
@@ -1811,6 +1849,7 @@ var SbasProcessor = class {
1811
1849
  iodp = -1;
1812
1850
  tlat = 0;
1813
1851
  degr = null;
1852
+ mt27 = null;
1814
1853
  ion = [];
1815
1854
  /** GEO PRN(s) whose messages have been ingested. */
1816
1855
  geoPrns = /* @__PURE__ */ new Set();
@@ -1839,6 +1878,8 @@ var SbasProcessor = class {
1839
1878
  return this.decodeDegradation(msg) ? type : -1;
1840
1879
  case 10:
1841
1880
  return this.decodeMt10(msg) ? type : -1;
1881
+ case 27:
1882
+ return this.decodeMt27(msg) ? type : -1;
1842
1883
  case 18:
1843
1884
  return this.decodeIgpMask(msg) ? type : -1;
1844
1885
  case 24:
@@ -1931,6 +1972,54 @@ var SbasProcessor = class {
1931
1972
  };
1932
1973
  return true;
1933
1974
  }
1975
+ /** MT27 SBAS service message (DO-229D §A.4.4.13, Table A-20): regions with a
1976
+ * δUDRE that inflates σ_UDRE for users inside/outside them. */
1977
+ decodeMt27(msg) {
1978
+ const iods = getBitU(msg, 14, 3);
1979
+ const svcNum = getBitU(msg, 20, 3) + 1;
1980
+ const nRegions = getBitU(msg, 23, 3);
1981
+ const priority = getBitU(msg, 26, 2);
1982
+ const udreInside = getBitU(msg, 28, 4);
1983
+ const udreOutside = getBitU(msg, 32, 4);
1984
+ if (!this.mt27 || this.mt27.iods !== iods)
1985
+ this.mt27 = { iods, udreOutside, msgs: /* @__PURE__ */ new Map() };
1986
+ this.mt27.udreOutside = udreOutside;
1987
+ const regions = [];
1988
+ for (let r = 0; r < nRegions && r < 5; r++) {
1989
+ const p = 36 + r * 35;
1990
+ regions.push({
1991
+ lat1: getBitS(msg, p, 8),
1992
+ lon1: getBitS(msg, p + 8, 9),
1993
+ lat2: getBitS(msg, p + 17, 8),
1994
+ lon2: getBitS(msg, p + 25, 9),
1995
+ square: getBitU(msg, p + 34, 1) === 1
1996
+ });
1997
+ }
1998
+ this.mt27.msgs.set(svcNum, { priority, udreInside, regions });
1999
+ return true;
2000
+ }
2001
+ /** δUDRE multiplier (DO-229D Table A-21) for a user location from the MT27
2002
+ * set: the highest-priority containing region wins (ties → lower δUDRE),
2003
+ * else the outside value. 1 when no MT27 has been received. */
2004
+ deltaUdre(latDeg, lonDeg) {
2005
+ const s = this.mt27;
2006
+ if (!s) return 1;
2007
+ let best = null;
2008
+ let bestPri = -1;
2009
+ for (const m of s.msgs.values()) {
2010
+ for (const rg of m.regions) {
2011
+ if (!pointInRegion(latDeg, lonDeg, rg)) continue;
2012
+ const val = DELTA_UDRE[m.udreInside] ?? 1;
2013
+ if (m.priority > bestPri) {
2014
+ bestPri = m.priority;
2015
+ best = val;
2016
+ } else if (m.priority === bestPri && best !== null) {
2017
+ best = Math.min(best, val);
2018
+ }
2019
+ }
2020
+ }
2021
+ return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
2022
+ }
1934
2023
  decodeMixed(msg, week, tow) {
1935
2024
  if (this.iodp !== getBitU(msg, 110, 2)) return false;
1936
2025
  const blk = getBitU(msg, 112, 2);
@@ -2102,7 +2191,7 @@ var SbasProcessor = class {
2102
2191
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
2103
2192
  * to the satellite ECEF position and `dClkS` to its clock bias.
2104
2193
  */
2105
- satCorrection(prn, week, tow) {
2194
+ satCorrection(prn, week, tow, userLatRad, userLonRad) {
2106
2195
  const idx = this.satIdx.get(prn);
2107
2196
  if (idx === void 0) return null;
2108
2197
  const s = this.sats[idx];
@@ -2124,7 +2213,11 @@ var SbasProcessor = class {
2124
2213
  const tf = now - s.fcorr.t0s + this.tlat;
2125
2214
  if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
2126
2215
  const prc = s.fcorr.prc;
2127
- const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre));
2216
+ const dudre = userLatRad != null && userLonRad != null ? this.deltaUdre(
2217
+ userLatRad * 180 / Math.PI,
2218
+ userLonRad * 180 / Math.PI
2219
+ ) : 1;
2220
+ const sigUdre = Math.sqrt(varfcorr(s.fcorr.udre)) * dudre;
2128
2221
  const efc = degfcorr(s.fcorr.ai) * tf * tf / 2;
2129
2222
  const eltc = this.degr && s.lcorr && s.lcorr.t0s !== 0 ? sbasLongTermDeg(this.degr, s.lcorr.vel, now - s.lcorr.t0s) : 0;
2130
2223
  const varM2 = this.degr?.rssUdre ? sigUdre * sigUdre + efc * efc + eltc * eltc : (sigUdre + efc + eltc) ** 2;
@@ -3935,7 +4028,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3935
4028
  used.push(prn);
3936
4029
  azels.push(azel);
3937
4030
  if (sbas) {
3938
- const sc = sbas.satCorrection(prn, gpsWeek, gpsTow);
4031
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon);
3939
4032
  const si = sbas.ionoDelay(
3940
4033
  gpsWeek,
3941
4034
  gpsTow,
@@ -605,6 +605,7 @@ declare class SbasProcessor {
605
605
  private iodp;
606
606
  private tlat;
607
607
  private degr;
608
+ private mt27;
608
609
  private ion;
609
610
  /** GEO PRN(s) whose messages have been ingested. */
610
611
  readonly geoPrns: Set<string>;
@@ -622,6 +623,13 @@ declare class SbasProcessor {
622
623
  * preamble + 6-bit type (start bit 14), MSB-first, with the listed LSBs.
623
624
  * Iiono / Iltc_v0 of 0 must be read as 1 (Table A-9, Note 3). */
624
625
  private decodeMt10;
626
+ /** MT27 SBAS service message (DO-229D §A.4.4.13, Table A-20): regions with a
627
+ * δUDRE that inflates σ_UDRE for users inside/outside them. */
628
+ private decodeMt27;
629
+ /** δUDRE multiplier (DO-229D Table A-21) for a user location from the MT27
630
+ * set: the highest-priority containing region wins (ties → lower δUDRE),
631
+ * else the outside value. 1 when no MT27 has been received. */
632
+ deltaUdre(latDeg: number, lonDeg: number): number;
625
633
  private decodeMixed;
626
634
  private decodeLongCorrHalf;
627
635
  private decodeLong0;
@@ -657,7 +665,7 @@ declare class SbasProcessor {
657
665
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
658
666
  * to the satellite ECEF position and `dClkS` to its clock bias.
659
667
  */
660
- satCorrection(prn: string, week: number, tow: number): SbasSatCorrection | null;
668
+ satCorrection(prn: string, week: number, tow: number, userLatRad?: number, userLonRad?: number): SbasSatCorrection | null;
661
669
  /** The last-decoded MT10 degradation factors, or null if none seen. */
662
670
  get degradation(): Readonly<Degradation> | null;
663
671
  /**
@@ -1292,7 +1300,10 @@ interface SppOptions {
1292
1300
  /** The two SBAS corrections `solveSpp` consumes — {@link ./sbas.SbasProcessor}
1293
1301
  * satisfies this structurally. */
1294
1302
  interface SbasCorrectionSource {
1295
- satCorrection(prn: string, week: number, tow: number): {
1303
+ satCorrection(prn: string, week: number, tow: number,
1304
+ /** User geodetic lat/lon (rad) for the MT27 service-region δUDRE, when
1305
+ * known (the final protection-level pass). Omitted during iteration. */
1306
+ userLatRad?: number, userLonRad?: number): {
1296
1307
  dPos: [number, number, number];
1297
1308
  dClkS: number;
1298
1309
  varM2: number;
@@ -605,6 +605,7 @@ declare class SbasProcessor {
605
605
  private iodp;
606
606
  private tlat;
607
607
  private degr;
608
+ private mt27;
608
609
  private ion;
609
610
  /** GEO PRN(s) whose messages have been ingested. */
610
611
  readonly geoPrns: Set<string>;
@@ -622,6 +623,13 @@ declare class SbasProcessor {
622
623
  * preamble + 6-bit type (start bit 14), MSB-first, with the listed LSBs.
623
624
  * Iiono / Iltc_v0 of 0 must be read as 1 (Table A-9, Note 3). */
624
625
  private decodeMt10;
626
+ /** MT27 SBAS service message (DO-229D §A.4.4.13, Table A-20): regions with a
627
+ * δUDRE that inflates σ_UDRE for users inside/outside them. */
628
+ private decodeMt27;
629
+ /** δUDRE multiplier (DO-229D Table A-21) for a user location from the MT27
630
+ * set: the highest-priority containing region wins (ties → lower δUDRE),
631
+ * else the outside value. 1 when no MT27 has been received. */
632
+ deltaUdre(latDeg: number, lonDeg: number): number;
625
633
  private decodeMixed;
626
634
  private decodeLongCorrHalf;
627
635
  private decodeLong0;
@@ -657,7 +665,7 @@ declare class SbasProcessor {
657
665
  * no valid (unexpired) long-term + fast correction is available. Add `dPos`
658
666
  * to the satellite ECEF position and `dClkS` to its clock bias.
659
667
  */
660
- satCorrection(prn: string, week: number, tow: number): SbasSatCorrection | null;
668
+ satCorrection(prn: string, week: number, tow: number, userLatRad?: number, userLonRad?: number): SbasSatCorrection | null;
661
669
  /** The last-decoded MT10 degradation factors, or null if none seen. */
662
670
  get degradation(): Readonly<Degradation> | null;
663
671
  /**
@@ -1292,7 +1300,10 @@ interface SppOptions {
1292
1300
  /** The two SBAS corrections `solveSpp` consumes — {@link ./sbas.SbasProcessor}
1293
1301
  * satisfies this structurally. */
1294
1302
  interface SbasCorrectionSource {
1295
- satCorrection(prn: string, week: number, tow: number): {
1303
+ satCorrection(prn: string, week: number, tow: number,
1304
+ /** User geodetic lat/lon (rad) for the MT27 service-region δUDRE, when
1305
+ * known (the final protection-level pass). Omitted during iteration. */
1306
+ userLatRad?: number, userLonRad?: number): {
1296
1307
  dPos: [number, number, number];
1297
1308
  dClkS: number;
1298
1309
  varM2: number;
@@ -32,7 +32,7 @@ import {
32
32
  sunEcef,
33
33
  toRtkEpoch,
34
34
  wlWavelength
35
- } from "./chunk-SWVIBUHS.js";
35
+ } from "./chunk-4WNIGDNR.js";
36
36
  import "./chunk-WQX354KD.js";
37
37
  import "./chunk-MHGNKSSH.js";
38
38
  import "./chunk-MIIM4LDY.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.58.0",
3
+ "version": "1.60.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,