gnss-js 1.48.0 → 1.50.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.
@@ -22,6 +22,7 @@ var positioning_exports = {};
22
22
  __export(positioning_exports, {
23
23
  IONO_L1_M_PER_TECU: () => IONO_L1_M_PER_TECU,
24
24
  RtkFloatEngine: () => RtkFloatEngine,
25
+ SbasProcessor: () => SbasProcessor,
25
26
  buildPppAntenna: () => buildPppAntenna,
26
27
  createPppCorrections: () => createPppCorrections,
27
28
  estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
@@ -450,8 +451,8 @@ function computeSatPosition(eph, timeMs) {
450
451
  return glonassPosition(eph, (timeMs - leapMs) / 1e3);
451
452
  }
452
453
  const GPS_EPOCH = START_GPS_TIME.getTime();
453
- const gpsSeconds = (timeMs - GPS_EPOCH) / 1e3;
454
- let tow = gpsSeconds % (7 * 86400);
454
+ const gpsSeconds2 = (timeMs - GPS_EPOCH) / 1e3;
455
+ let tow = gpsSeconds2 % (7 * 86400);
455
456
  if (eph.system === "C") tow = (tow - 14 + 604800) % 604800;
456
457
  return keplerPosition(eph, tow);
457
458
  }
@@ -501,8 +502,8 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
501
502
  const F = 1 + 16 * Math.pow(0.53 - E, 3);
502
503
  const x = 2 * Math.PI * (t - 50400) / per;
503
504
  if (Math.abs(x) >= 1.57) return F * 5e-9;
504
- const x2 = x * x;
505
- return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
505
+ const x22 = x * x;
506
+ return F * (5e-9 + amp * (1 - x22 / 2 + x22 * x22 / 24));
506
507
  }
507
508
 
508
509
  // src/positioning/gim.ts
@@ -1654,6 +1655,515 @@ function postProcessRtk(base, rover, ephemerides, baseEcef, opts = {}) {
1654
1655
  return { track, solved, unmatched, fixRate: solved ? fixed / solved : 0 };
1655
1656
  }
1656
1657
 
1658
+ // src/navbits/index.ts
1659
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
1660
+ var SEC_PER_WEEK = 7 * 86400;
1661
+ function getBitU(buff, pos, len) {
1662
+ let bits = 0;
1663
+ for (let i = pos; i < pos + len; i++) {
1664
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
1665
+ }
1666
+ return bits;
1667
+ }
1668
+ function getBitS(buff, pos, len) {
1669
+ const bits = getBitU(buff, pos, len);
1670
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
1671
+ return bits - 2 ** len;
1672
+ }
1673
+
1674
+ // src/navbits/sbas.ts
1675
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
1676
+ var MS_PER_WEEK2 = 7 * 86400 * 1e3;
1677
+ function sbasMessageType(msg) {
1678
+ return getBitU(msg, 8, 6);
1679
+ }
1680
+
1681
+ // src/positioning/sbas.ts
1682
+ var P2_11 = 2 ** -11;
1683
+ var P2_31 = 2 ** -31;
1684
+ var P2_39 = 2 ** -39;
1685
+ var D2R = Math.PI / 180;
1686
+ var R2D = 180 / Math.PI;
1687
+ var SEC_PER_WEEK2 = 604800;
1688
+ var MAXSBSAGEF = 30;
1689
+ var MAXSBSAGEL = 1800;
1690
+ var MAXBAND = 10;
1691
+ var two = (n) => String(n).padStart(2, "0");
1692
+ var VAR_FCORR = [
1693
+ 0.052,
1694
+ 0.0924,
1695
+ 0.1444,
1696
+ 0.283,
1697
+ 0.4678,
1698
+ 0.8315,
1699
+ 1.2992,
1700
+ 1.8709,
1701
+ 2.5465,
1702
+ 3.326,
1703
+ 5.1968,
1704
+ 20.787,
1705
+ 230.9661,
1706
+ 2078.695
1707
+ ];
1708
+ var varfcorr = (udre) => udre > 0 && udre <= 14 ? VAR_FCORR[udre - 1] : 0;
1709
+ var VAR_ICORR = [
1710
+ 84e-4,
1711
+ 0.0333,
1712
+ 0.0749,
1713
+ 0.1331,
1714
+ 0.2079,
1715
+ 0.2994,
1716
+ 0.4075,
1717
+ 0.5322,
1718
+ 0.6735,
1719
+ 0.8315,
1720
+ 1.1974,
1721
+ 1.8709,
1722
+ 3.326,
1723
+ 20.787,
1724
+ 187.0826
1725
+ ];
1726
+ var varicorr = (give) => give > 0 && give <= 15 ? VAR_ICORR[give - 1] : 0;
1727
+ var DEGF = [
1728
+ 0,
1729
+ 5e-5,
1730
+ 9e-5,
1731
+ 12e-5,
1732
+ 15e-5,
1733
+ 2e-4,
1734
+ 3e-4,
1735
+ 45e-5,
1736
+ 6e-4,
1737
+ 9e-4,
1738
+ 15e-4,
1739
+ 21e-4,
1740
+ 27e-4,
1741
+ 33e-4,
1742
+ 46e-4,
1743
+ 58e-4
1744
+ ];
1745
+ var degfcorr = (ai) => ai > 0 && ai <= 15 ? DEGF[ai] : 58e-4;
1746
+ var x1 = [-75, -65, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 65, 75, 85];
1747
+ var x2 = [-55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
1748
+ var x3 = [-75, -65, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 65, 75];
1749
+ var x4 = [-85, -75, -65, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 65, 75];
1750
+ var x5 = [-180, -175, -170, -165, -160, -155, -150, -145, -140, -135, -130, -125, -120, -115, -110, -105, -100, -95, -90, -85, -80, -75, -70, -65, -60, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170, 175];
1751
+ var x6 = [-180, -170, -160, -150, -140, -130, -120, -110, -100, -90, -80, -70, -60, -50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170];
1752
+ var x7 = [-180, -150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150];
1753
+ var x8 = [-170, -140, -110, -80, -50, -20, 10, 40, 70, 100, 130, 160];
1754
+ var igpband1 = [
1755
+ [{ coord: -180, arr: x1, bits: 1, bite: 28 }, { coord: -175, arr: x2, bits: 29, bite: 51 }, { coord: -170, arr: x3, bits: 52, bite: 78 }, { coord: -165, arr: x2, bits: 79, bite: 101 }, { coord: -160, arr: x3, bits: 102, bite: 128 }, { coord: -155, arr: x2, bits: 129, bite: 151 }, { coord: -150, arr: x3, bits: 152, bite: 178 }, { coord: -145, arr: x2, bits: 179, bite: 201 }],
1756
+ [{ coord: -140, arr: x4, bits: 1, bite: 28 }, { coord: -135, arr: x2, bits: 29, bite: 51 }, { coord: -130, arr: x3, bits: 52, bite: 78 }, { coord: -125, arr: x2, bits: 79, bite: 101 }, { coord: -120, arr: x3, bits: 102, bite: 128 }, { coord: -115, arr: x2, bits: 129, bite: 151 }, { coord: -110, arr: x3, bits: 152, bite: 178 }, { coord: -105, arr: x2, bits: 179, bite: 201 }],
1757
+ [{ coord: -100, arr: x3, bits: 1, bite: 27 }, { coord: -95, arr: x2, bits: 28, bite: 50 }, { coord: -90, arr: x1, bits: 51, bite: 78 }, { coord: -85, arr: x2, bits: 79, bite: 101 }, { coord: -80, arr: x3, bits: 102, bite: 128 }, { coord: -75, arr: x2, bits: 129, bite: 151 }, { coord: -70, arr: x3, bits: 152, bite: 178 }, { coord: -65, arr: x2, bits: 179, bite: 201 }],
1758
+ [{ coord: -60, arr: x3, bits: 1, bite: 27 }, { coord: -55, arr: x2, bits: 28, bite: 50 }, { coord: -50, arr: x4, bits: 51, bite: 78 }, { coord: -45, arr: x2, bits: 79, bite: 101 }, { coord: -40, arr: x3, bits: 102, bite: 128 }, { coord: -35, arr: x2, bits: 129, bite: 151 }, { coord: -30, arr: x3, bits: 152, bite: 178 }, { coord: -25, arr: x2, bits: 179, bite: 201 }],
1759
+ [{ coord: -20, arr: x3, bits: 1, bite: 27 }, { coord: -15, arr: x2, bits: 28, bite: 50 }, { coord: -10, arr: x3, bits: 51, bite: 77 }, { coord: -5, arr: x2, bits: 78, bite: 100 }, { coord: 0, arr: x1, bits: 101, bite: 128 }, { coord: 5, arr: x2, bits: 129, bite: 151 }, { coord: 10, arr: x3, bits: 152, bite: 178 }, { coord: 15, arr: x2, bits: 179, bite: 201 }],
1760
+ [{ coord: 20, arr: x3, bits: 1, bite: 27 }, { coord: 25, arr: x2, bits: 28, bite: 50 }, { coord: 30, arr: x3, bits: 51, bite: 77 }, { coord: 35, arr: x2, bits: 78, bite: 100 }, { coord: 40, arr: x4, bits: 101, bite: 128 }, { coord: 45, arr: x2, bits: 129, bite: 151 }, { coord: 50, arr: x3, bits: 152, bite: 178 }, { coord: 55, arr: x2, bits: 179, bite: 201 }],
1761
+ [{ coord: 60, arr: x3, bits: 1, bite: 27 }, { coord: 65, arr: x2, bits: 28, bite: 50 }, { coord: 70, arr: x3, bits: 51, bite: 77 }, { coord: 75, arr: x2, bits: 78, bite: 100 }, { coord: 80, arr: x3, bits: 101, bite: 127 }, { coord: 85, arr: x2, bits: 128, bite: 150 }, { coord: 90, arr: x1, bits: 151, bite: 178 }, { coord: 95, arr: x2, bits: 179, bite: 201 }],
1762
+ [{ coord: 100, arr: x3, bits: 1, bite: 27 }, { coord: 105, arr: x2, bits: 28, bite: 50 }, { coord: 110, arr: x3, bits: 51, bite: 77 }, { coord: 115, arr: x2, bits: 78, bite: 100 }, { coord: 120, arr: x3, bits: 101, bite: 127 }, { coord: 125, arr: x2, bits: 128, bite: 150 }, { coord: 130, arr: x4, bits: 151, bite: 178 }, { coord: 135, arr: x2, bits: 179, bite: 201 }],
1763
+ [{ coord: 140, arr: x3, bits: 1, bite: 27 }, { coord: 145, arr: x2, bits: 28, bite: 50 }, { coord: 150, arr: x3, bits: 51, bite: 77 }, { coord: 155, arr: x2, bits: 78, bite: 100 }, { coord: 160, arr: x3, bits: 101, bite: 127 }, { coord: 165, arr: x2, bits: 128, bite: 150 }, { coord: 170, arr: x3, bits: 151, bite: 177 }, { coord: 175, arr: x2, bits: 178, bite: 200 }]
1764
+ ];
1765
+ var igpband2 = [
1766
+ [{ coord: 60, arr: x5, bits: 1, bite: 72 }, { coord: 65, arr: x6, bits: 73, bite: 108 }, { coord: 70, arr: x6, bits: 109, bite: 144 }, { coord: 75, arr: x6, bits: 145, bite: 180 }, { coord: 85, arr: x7, bits: 181, bite: 192 }],
1767
+ [{ coord: -60, arr: x5, bits: 1, bite: 72 }, { coord: -65, arr: x6, bits: 73, bite: 108 }, { coord: -70, arr: x6, bits: 109, bite: 144 }, { coord: -75, arr: x6, bits: 145, bite: 180 }, { coord: -85, arr: x8, bits: 181, bite: 192 }]
1768
+ ];
1769
+ var gpsSeconds = (week, tow) => week * SEC_PER_WEEK2 + tow;
1770
+ function maskPrn(i) {
1771
+ if (i >= 1 && i <= 37) return `G${two(i)}`;
1772
+ if (i >= 38 && i <= 61) return `R${two(i - 37)}`;
1773
+ if (i >= 120 && i <= 138) return `S${two(i - 100)}`;
1774
+ if (i >= 193 && i <= 202) return `J${two(i - 192)}`;
1775
+ return null;
1776
+ }
1777
+ function ionppp(latRad, lonRad, azRad, elRad, re, hion) {
1778
+ const rp = re / (re + hion) * Math.cos(elRad);
1779
+ const ap = Math.PI / 2 - elRad - Math.asin(rp);
1780
+ const sinap = Math.sin(ap);
1781
+ const tanap = Math.tan(ap);
1782
+ const cosaz = Math.cos(azRad);
1783
+ const latP = Math.asin(
1784
+ Math.sin(latRad) * Math.cos(ap) + Math.cos(latRad) * sinap * cosaz
1785
+ );
1786
+ let lonP;
1787
+ if (latRad > 70 * D2R && tanap * cosaz > Math.tan(Math.PI / 2 - latRad) || latRad < -70 * D2R && -tanap * cosaz > Math.tan(Math.PI / 2 + latRad)) {
1788
+ lonP = lonRad + Math.PI - Math.asin(sinap * Math.sin(azRad) / Math.cos(latP));
1789
+ } else {
1790
+ lonP = lonRad + Math.asin(sinap * Math.sin(azRad) / Math.cos(latP));
1791
+ }
1792
+ return { fp: 1 / Math.sqrt(1 - rp * rp), latP, lonP };
1793
+ }
1794
+ var SbasProcessor = class {
1795
+ sats = [];
1796
+ satIdx = /* @__PURE__ */ new Map();
1797
+ iodp = -1;
1798
+ tlat = 0;
1799
+ ion = [];
1800
+ /** GEO PRN(s) whose messages have been ingested. */
1801
+ geoPrns = /* @__PURE__ */ new Set();
1802
+ /**
1803
+ * Ingest one decoded SBAS L1 message (250-bit, MSB-first) received at the
1804
+ * given GPS week / time-of-week. Returns the message type, or -1 if it was
1805
+ * not a recognised/applicable correction message.
1806
+ */
1807
+ update(msg, week, tow, geoPrn) {
1808
+ if (week === 0) return -1;
1809
+ const type = sbasMessageType(msg);
1810
+ if (geoPrn != null && geoPrn >= 120 && geoPrn <= 158)
1811
+ this.geoPrns.add(`S${two(geoPrn - 100)}`);
1812
+ switch (type) {
1813
+ case 1:
1814
+ return this.decodeMask(msg) ? type : -1;
1815
+ case 0:
1816
+ case 2:
1817
+ case 3:
1818
+ case 4:
1819
+ case 5:
1820
+ return this.decodeFast(msg, type, week, tow) ? type : -1;
1821
+ case 6:
1822
+ return this.decodeIntegrity(msg) ? type : -1;
1823
+ case 7:
1824
+ return this.decodeDegradation(msg) ? type : -1;
1825
+ case 18:
1826
+ return this.decodeIgpMask(msg) ? type : -1;
1827
+ case 24:
1828
+ return this.decodeMixed(msg, week, tow) ? type : -1;
1829
+ case 25:
1830
+ return this.decodeLongCorrHalf(msg, 14, week, tow) && this.decodeLongCorrHalf(msg, 120, week, tow) ? type : -1;
1831
+ case 26:
1832
+ return this.decodeIgpDelay(msg, week, tow) ? type : -1;
1833
+ case 9:
1834
+ // GEO nav — handled by decodeSbasGeoNav elsewhere
1835
+ case 63:
1836
+ return type;
1837
+ default:
1838
+ return -1;
1839
+ }
1840
+ }
1841
+ decodeMask(msg) {
1842
+ const sats = [];
1843
+ for (let i = 1; i <= 210; i++) {
1844
+ if (getBitU(msg, 13 + i, 1)) sats.push({ prn: maskPrn(i) });
1845
+ }
1846
+ this.iodp = getBitU(msg, 224, 2);
1847
+ this.sats = sats;
1848
+ this.satIdx.clear();
1849
+ sats.forEach((s, k) => {
1850
+ if (s.prn) this.satIdx.set(s.prn, k);
1851
+ });
1852
+ return true;
1853
+ }
1854
+ decodeFast(msg, type, week, tow) {
1855
+ if (this.iodp !== getBitU(msg, 16, 2)) return false;
1856
+ const iodf = getBitU(msg, 14, 2);
1857
+ const t0s = gpsSeconds(week, tow);
1858
+ for (let i = 0; i < 13; i++) {
1859
+ const j = 13 * ((type === 0 ? 2 : type) - 2) + i;
1860
+ if (j >= this.sats.length) break;
1861
+ const udre = getBitU(msg, 174 + 4 * i, 4);
1862
+ const prc = getBitS(msg, 18 + i * 12, 12) * 0.125;
1863
+ const prev = this.sats[j].fcorr;
1864
+ this.sats[j].fcorr = {
1865
+ prc,
1866
+ udre: udre + 1,
1867
+ ai: prev?.ai ?? 0,
1868
+ iodf,
1869
+ t0s
1870
+ };
1871
+ }
1872
+ return true;
1873
+ }
1874
+ decodeIntegrity(msg) {
1875
+ const iodf = [0, 1, 2, 3].map((i) => getBitU(msg, 14 + i * 2, 2));
1876
+ for (let i = 0; i < this.sats.length; i++) {
1877
+ const f = this.sats[i].fcorr;
1878
+ if (!f || f.iodf !== iodf[Math.floor(i / 13)]) continue;
1879
+ f.udre = getBitU(msg, 22 + i * 4, 4) + 1;
1880
+ }
1881
+ return true;
1882
+ }
1883
+ decodeDegradation(msg) {
1884
+ if (this.iodp !== getBitU(msg, 18, 2)) return false;
1885
+ this.tlat = getBitU(msg, 14, 4);
1886
+ for (let i = 0; i < this.sats.length; i++) {
1887
+ const ai = getBitU(msg, 22 + i * 4, 4);
1888
+ if (this.sats[i].fcorr) this.sats[i].fcorr.ai = ai;
1889
+ else this.sats[i].fcorr = { prc: 0, udre: 0, ai, iodf: -1, t0s: 0 };
1890
+ }
1891
+ return true;
1892
+ }
1893
+ decodeMixed(msg, week, tow) {
1894
+ if (this.iodp !== getBitU(msg, 110, 2)) return false;
1895
+ const blk = getBitU(msg, 112, 2);
1896
+ const iodf = getBitU(msg, 114, 2);
1897
+ const t0s = gpsSeconds(week, tow);
1898
+ for (let i = 0; i < 6; i++) {
1899
+ const j = 13 * blk + i;
1900
+ if (j >= this.sats.length) break;
1901
+ const udre = getBitU(msg, 86 + 4 * i, 4);
1902
+ const prc = getBitS(msg, 14 + i * 12, 12) * 0.125;
1903
+ const prev = this.sats[j].fcorr;
1904
+ this.sats[j].fcorr = {
1905
+ prc,
1906
+ udre: udre + 1,
1907
+ ai: prev?.ai ?? 0,
1908
+ iodf,
1909
+ t0s
1910
+ };
1911
+ }
1912
+ return this.decodeLongCorrHalf(msg, 120, week, tow);
1913
+ }
1914
+ decodeLongCorrHalf(msg, p, week, tow) {
1915
+ if (getBitU(msg, p, 1) === 0) {
1916
+ if (this.iodp === getBitU(msg, p + 103, 2)) {
1917
+ const a = this.decodeLong0(msg, p + 1, week, tow);
1918
+ const b = this.decodeLong0(msg, p + 52, week, tow);
1919
+ return a || b;
1920
+ }
1921
+ } else if (this.iodp === getBitU(msg, p + 104, 2)) {
1922
+ return this.decodeLong1(msg, p + 1, week, tow);
1923
+ }
1924
+ return false;
1925
+ }
1926
+ decodeLong0(msg, p, week, tow) {
1927
+ const n = getBitU(msg, p, 6);
1928
+ if (n === 0 || n > this.sats.length) return false;
1929
+ this.sats[n - 1].lcorr = {
1930
+ iode: getBitU(msg, p + 6, 8),
1931
+ dpos: [
1932
+ getBitS(msg, p + 14, 9) * 0.125,
1933
+ getBitS(msg, p + 23, 9) * 0.125,
1934
+ getBitS(msg, p + 32, 9) * 0.125
1935
+ ],
1936
+ dvel: [0, 0, 0],
1937
+ daf0: getBitS(msg, p + 41, 10) * P2_31,
1938
+ daf1: 0,
1939
+ t0s: gpsSeconds(week, tow)
1940
+ };
1941
+ return true;
1942
+ }
1943
+ decodeLong1(msg, p, week, tow) {
1944
+ const n = getBitU(msg, p, 6);
1945
+ if (n === 0 || n > this.sats.length) return false;
1946
+ let t = getBitU(msg, p + 90, 13) * 16 - tow % 86400;
1947
+ if (t <= -43200) t += 86400;
1948
+ else if (t > 43200) t -= 86400;
1949
+ this.sats[n - 1].lcorr = {
1950
+ iode: getBitU(msg, p + 6, 8),
1951
+ dpos: [
1952
+ getBitS(msg, p + 14, 11) * 0.125,
1953
+ getBitS(msg, p + 25, 11) * 0.125,
1954
+ getBitS(msg, p + 36, 11) * 0.125
1955
+ ],
1956
+ dvel: [
1957
+ getBitS(msg, p + 58, 8) * P2_11,
1958
+ getBitS(msg, p + 66, 8) * P2_11,
1959
+ getBitS(msg, p + 74, 8) * P2_11
1960
+ ],
1961
+ daf0: getBitS(msg, p + 47, 11) * P2_31,
1962
+ daf1: getBitS(msg, p + 82, 8) * P2_39,
1963
+ t0s: gpsSeconds(week, tow + t)
1964
+ };
1965
+ return true;
1966
+ }
1967
+ decodeIgpMask(msg) {
1968
+ const band = getBitU(msg, 18, 4);
1969
+ let p;
1970
+ let m;
1971
+ if (band <= 8) {
1972
+ p = igpband1[band];
1973
+ m = 8;
1974
+ } else if (band <= 10) {
1975
+ p = igpband2[band - 9];
1976
+ m = 5;
1977
+ } else return false;
1978
+ const igp = [];
1979
+ for (let i = 1; i <= 201; i++) {
1980
+ if (!getBitU(msg, 23 + i, 1)) continue;
1981
+ for (let j = 0; j < m; j++) {
1982
+ const b = p[j];
1983
+ if (i < b.bits || b.bite < i) continue;
1984
+ const varying = b.arr[i - b.bits];
1985
+ igp.push({
1986
+ lat: band <= 8 ? varying : b.coord,
1987
+ lon: band <= 8 ? b.coord : varying,
1988
+ delay: 0,
1989
+ give: 0,
1990
+ t0s: 0
1991
+ });
1992
+ break;
1993
+ }
1994
+ }
1995
+ this.ion[band] = { iodi: getBitU(msg, 22, 2), igp };
1996
+ return true;
1997
+ }
1998
+ decodeIgpDelay(msg, week, tow) {
1999
+ const band = getBitU(msg, 14, 4);
2000
+ const b = this.ion[band];
2001
+ if (band > MAXBAND || !b || b.iodi !== getBitU(msg, 217, 2)) return false;
2002
+ const block = getBitU(msg, 18, 4);
2003
+ const t0s = gpsSeconds(week, tow);
2004
+ for (let i = 0; i < 15; i++) {
2005
+ const j = block * 15 + i;
2006
+ if (j >= b.igp.length) continue;
2007
+ const give = getBitU(msg, 22 + i * 13 + 9, 4);
2008
+ const delay = getBitU(msg, 22 + i * 13, 9);
2009
+ b.igp[j].delay = delay === 511 ? 0 : delay * 0.125;
2010
+ b.igp[j].give = give + 1 >= 16 ? 0 : give + 1;
2011
+ b.igp[j].t0s = t0s;
2012
+ }
2013
+ return true;
2014
+ }
2015
+ /** PRNs currently in the correction mask (in mask order). */
2016
+ activeSats() {
2017
+ return this.sats.map((s) => s.prn).filter((p) => !!p);
2018
+ }
2019
+ /** Number of ionospheric grid points with a valid delay across all bands. */
2020
+ ionoGridPoints() {
2021
+ let n = 0;
2022
+ for (const b of this.ion) if (b) {
2023
+ for (const g of b.igp) if (g.give > 0) n++;
2024
+ }
2025
+ return n;
2026
+ }
2027
+ /**
2028
+ * Satellite range/clock correction for a PRN at a GPS week/tow, or null if
2029
+ * no valid (unexpired) long-term + fast correction is available. Add `dPos`
2030
+ * to the satellite ECEF position and `dClkS` to its clock bias.
2031
+ */
2032
+ satCorrection(prn, week, tow) {
2033
+ const idx = this.satIdx.get(prn);
2034
+ if (idx === void 0) return null;
2035
+ const s = this.sats[idx];
2036
+ const now = gpsSeconds(week, tow);
2037
+ const drs = [0, 0, 0];
2038
+ let dclk = 0;
2039
+ let iode = -1;
2040
+ if (s.lcorr && s.lcorr.t0s !== 0) {
2041
+ const t = now - s.lcorr.t0s;
2042
+ if (Math.abs(t) > MAXSBSAGEL) return null;
2043
+ for (let i = 0; i < 3; i++)
2044
+ drs[i] = s.lcorr.dpos[i] + s.lcorr.dvel[i] * t;
2045
+ dclk = s.lcorr.daf0 + s.lcorr.daf1 * t;
2046
+ iode = s.lcorr.iode;
2047
+ } else if (prn[0] !== "S") {
2048
+ return null;
2049
+ }
2050
+ if (!s.fcorr || s.fcorr.t0s === 0) return null;
2051
+ const tf = now - s.fcorr.t0s + this.tlat;
2052
+ if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
2053
+ const prc = s.fcorr.prc;
2054
+ const varM2 = varfcorr(s.fcorr.udre) + degfcorr(s.fcorr.ai) * tf * tf / 2;
2055
+ return { dPos: drs, dClkS: dclk + prc / C_LIGHT, prcM: prc, varM2, iode };
2056
+ }
2057
+ /**
2058
+ * Slant L1 ionospheric delay (m) at the pierce point for a receiver geodetic
2059
+ * position (lat/lon rad, height m) and a satellite az/el (rad), interpolated
2060
+ * from the SBAS grid — or null if the pierce point isn't covered. RTKLIB
2061
+ * `sbsioncorr`.
2062
+ */
2063
+ ionoDelay(week, tow, latRad, lonRad, heightM, azRad, elRad) {
2064
+ if (heightM < -100 || elRad <= 0) return { delayM: 0, varM2: 0 };
2065
+ const { fp, latP, lonP } = ionppp(
2066
+ latRad,
2067
+ lonRad,
2068
+ azRad,
2069
+ elRad,
2070
+ 6378.1363,
2071
+ 350
2072
+ );
2073
+ const igp = this.searchIgp(latP, lonP);
2074
+ if (!igp) return null;
2075
+ const { pts, x, y } = igp;
2076
+ const w = [0, 0, 0, 0];
2077
+ if (pts[0] && pts[1] && pts[2] && pts[3]) {
2078
+ w[0] = (1 - x) * (1 - y);
2079
+ w[1] = (1 - x) * y;
2080
+ w[2] = x * (1 - y);
2081
+ w[3] = x * y;
2082
+ } else if (pts[0] && pts[1] && pts[2]) {
2083
+ w[1] = y;
2084
+ w[2] = x;
2085
+ w[0] = 1 - w[1] - w[2];
2086
+ if (w[0] < 0) return null;
2087
+ } else if (pts[0] && pts[2] && pts[3]) {
2088
+ w[0] = 1 - x;
2089
+ w[3] = y;
2090
+ w[2] = 1 - w[0] - w[3];
2091
+ if (w[2] < 0) return null;
2092
+ } else if (pts[0] && pts[1] && pts[3]) {
2093
+ w[0] = 1 - y;
2094
+ w[3] = x;
2095
+ w[1] = 1 - w[0] - w[3];
2096
+ if (w[1] < 0) return null;
2097
+ } else if (pts[1] && pts[2] && pts[3]) {
2098
+ w[1] = 1 - x;
2099
+ w[2] = 1 - y;
2100
+ w[3] = 1 - w[1] - w[2];
2101
+ if (w[3] < 0) return null;
2102
+ } else return null;
2103
+ const now = gpsSeconds(week, tow);
2104
+ let delay = 0;
2105
+ let varM2 = 0;
2106
+ for (let i = 0; i < 4; i++) {
2107
+ const g = pts[i];
2108
+ if (!g) continue;
2109
+ delay += w[i] * g.delay;
2110
+ varM2 += w[i] * varicorr(g.give) * 9e-8 * Math.abs(now - g.t0s);
2111
+ }
2112
+ return { delayM: delay * fp, varM2: varM2 * fp * fp };
2113
+ }
2114
+ /** Find the four IGPs bracketing a pierce point (RTKLIB `searchigp`). */
2115
+ searchIgp(latP, lonP) {
2116
+ const lat = latP * R2D;
2117
+ let lon = lonP * R2D;
2118
+ if (lon >= 180) lon -= 360;
2119
+ const latp = [0, 0];
2120
+ const lonp = [0, 0, 0, 0];
2121
+ let x;
2122
+ let y;
2123
+ if (lat >= -55 && lat < 55) {
2124
+ latp[0] = Math.floor(lat / 5) * 5;
2125
+ latp[1] = latp[0] + 5;
2126
+ lonp[0] = lonp[1] = Math.floor(lon / 5) * 5;
2127
+ lonp[2] = lonp[3] = lonp[0] + 5;
2128
+ x = (lon - lonp[0]) / 5;
2129
+ y = (lat - latp[0]) / 5;
2130
+ } else {
2131
+ latp[0] = Math.floor((lat - 5) / 10) * 10 + 5;
2132
+ latp[1] = latp[0] + 10;
2133
+ lonp[0] = lonp[1] = Math.floor(lon / 10) * 10;
2134
+ lonp[2] = lonp[3] = lonp[0] + 10;
2135
+ x = (lon - lonp[0]) / 10;
2136
+ y = (lat - latp[0]) / 10;
2137
+ if (lat >= 75 && lat < 85) {
2138
+ lonp[1] = Math.floor(lon / 90) * 90;
2139
+ lonp[3] = lonp[1] + 90;
2140
+ } else if (lat >= -85 && lat < -75) {
2141
+ lonp[0] = Math.floor((lon - 50) / 90) * 90 + 40;
2142
+ lonp[2] = lonp[0] + 90;
2143
+ } else if (lat >= 85) {
2144
+ for (let i = 0; i < 4; i++) lonp[i] = Math.floor(lon / 90) * 90;
2145
+ } else if (lat < -85) {
2146
+ for (let i = 0; i < 4; i++)
2147
+ lonp[i] = Math.floor((lon - 50) / 90) * 90 + 40;
2148
+ }
2149
+ }
2150
+ for (let i = 0; i < 4; i++) if (lonp[i] === 180) lonp[i] = -180;
2151
+ const pts = [null, null, null, null];
2152
+ for (const band of this.ion) {
2153
+ if (!band) continue;
2154
+ for (const g of band.igp) {
2155
+ if (g.t0s === 0 || g.give <= 0) continue;
2156
+ if (g.lat === latp[0] && g.lon === lonp[0]) pts[0] = g;
2157
+ else if (g.lat === latp[1] && g.lon === lonp[1]) pts[1] = g;
2158
+ else if (g.lat === latp[0] && g.lon === lonp[2]) pts[2] = g;
2159
+ else if (g.lat === latp[1] && g.lon === lonp[3]) pts[3] = g;
2160
+ }
2161
+ }
2162
+ if (!pts[0] && !pts[1] && !pts[2] && !pts[3]) return null;
2163
+ return { pts, x, y };
2164
+ }
2165
+ };
2166
+
1657
2167
  // src/rinex/sp3.ts
1658
2168
  function sp3Position(sp3, prn, tMs, order = 9) {
1659
2169
  const samples = sp3.satellites[prn];
@@ -1705,7 +2215,7 @@ function sp3Position(sp3, prn, tMs, order = 9) {
1705
2215
  }
1706
2216
 
1707
2217
  // src/rinex/clk.ts
1708
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
2218
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
1709
2219
  function clkBias(clk, prn, tMs) {
1710
2220
  const e = clk.sats[prn];
1711
2221
  if (!e || e.t.length === 0) return null;
@@ -3119,7 +3629,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3119
3629
  const systems = [...new Set(prns.map((p) => p[0]))].sort();
3120
3630
  const dim = 3 + systems.length;
3121
3631
  const sysIndex = new Map(systems.map((s, i) => [s, 3 + i]));
3122
- let x2 = 0;
3632
+ let x9 = 0;
3123
3633
  let y2 = 0;
3124
3634
  let z2 = 0;
3125
3635
  const clock2 = new Map(systems.map((s) => [s, 0]));
@@ -3134,9 +3644,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3134
3644
  );
3135
3645
  const Htv = new Array(dim).fill(0);
3136
3646
  let rows = 0;
3647
+ const sysRows = new Map(systems.map((s) => [s, 0]));
3137
3648
  for (const k of Object.keys(residuals2)) delete residuals2[k];
3138
- const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
3139
- const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
3649
+ const positionSaneIter = x9 * x9 + y2 * y2 + z2 * z2 > 1e12;
3650
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x9, y2, z2) : [0, 0, 0];
3140
3651
  for (const prn of prns) {
3141
3652
  const psr = pseudoranges.get(prn);
3142
3653
  const eph = ephemerides.get(prn);
@@ -3148,10 +3659,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3148
3659
  if (!Number.isFinite(sat.x)) continue;
3149
3660
  const isKepler = eph.system !== "R" && eph.system !== "S";
3150
3661
  const dts = dtsClock - (tgd && isKepler ? eph.tgd : 0);
3151
- const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
3662
+ const travel = Math.hypot(sat.x - x9, sat.y - y2, sat.z - z2) / C_LIGHT;
3152
3663
  const [sx, sy, sz] = sagnac2(sat, travel);
3153
- const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
3154
- const ux = (x2 - sx) / rho;
3664
+ const rho = Math.hypot(sx - x9, sy - y2, sz - z2);
3665
+ const ux = (x9 - sx) / rho;
3155
3666
  const uy = (y2 - sy) / rho;
3156
3667
  const uz = (z2 - sz) / rho;
3157
3668
  let elev = Math.PI / 2;
@@ -3159,7 +3670,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3159
3670
  let weight = 1;
3160
3671
  const positionSane = positionSaneIter;
3161
3672
  if (positionSane) {
3162
- const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
3673
+ const azel = ecefToAzEl(x9, y2, z2, sx, sy, sz);
3163
3674
  elev = azel.el;
3164
3675
  azim = azel.az;
3165
3676
  if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
@@ -3193,11 +3704,22 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3193
3704
  }
3194
3705
  residuals2[prn] = v;
3195
3706
  rows++;
3707
+ sysRows.set(sys, sysRows.get(sys) + 1);
3708
+ }
3709
+ let activeSystems = 0;
3710
+ for (const s of systems) {
3711
+ if (sysRows.get(s) === 0) {
3712
+ const idx = sysIndex.get(s);
3713
+ HtH[idx][idx] = 1;
3714
+ Htv[idx] = 0;
3715
+ } else {
3716
+ activeSystems++;
3717
+ }
3196
3718
  }
3197
- if (rows < dim) return null;
3719
+ if (rows < 3 + activeSystems) return null;
3198
3720
  const dx = solveLinear(HtH, Htv);
3199
3721
  if (!dx) return null;
3200
- x2 += dx[0];
3722
+ x9 += dx[0];
3201
3723
  y2 += dx[1];
3202
3724
  z2 += dx[2];
3203
3725
  for (const s of systems)
@@ -3207,7 +3729,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3207
3729
  break;
3208
3730
  }
3209
3731
  }
3210
- return { x: x2, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
3732
+ return { x: x9, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
3211
3733
  };
3212
3734
  const REJECT_THRESHOLD_M = 50;
3213
3735
  let candidates = all;
@@ -3261,6 +3783,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3261
3783
  0 && (module.exports = {
3262
3784
  IONO_L1_M_PER_TECU,
3263
3785
  RtkFloatEngine,
3786
+ SbasProcessor,
3264
3787
  buildPppAntenna,
3265
3788
  createPppCorrections,
3266
3789
  estimateNarrowlaneFcb,