gnss-js 1.20.0 → 1.21.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.
@@ -1335,6 +1335,529 @@ function parseSbfGloNav(data) {
1335
1335
  return { ephemerides, badCrc, messages };
1336
1336
  }
1337
1337
 
1338
+ // src/navbits/rs255.ts
1339
+ var RS255_N = 255;
1340
+ var RS255_K = 32;
1341
+ var EXP = new Uint8Array(510);
1342
+ var LOG = new Int16Array(256).fill(-1);
1343
+ {
1344
+ let x = 1;
1345
+ for (let i = 0; i < 255; i++) {
1346
+ EXP[i] = x;
1347
+ LOG[x] = i;
1348
+ x <<= 1;
1349
+ if (x & 256) x ^= 285;
1350
+ }
1351
+ for (let i = 255; i < 510; i++) EXP[i] = EXP[i - 255];
1352
+ }
1353
+ var gfMul = (a, b) => a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]];
1354
+ var gfInv = (a) => EXP[255 - LOG[a]];
1355
+ var GEN_POLY = (() => {
1356
+ let g = new Uint8Array([1]);
1357
+ for (let i = 1; i <= 223; i++) {
1358
+ const root = EXP[i];
1359
+ const next = new Uint8Array(g.length + 1);
1360
+ for (let j = 0; j < g.length; j++) {
1361
+ next[j] ^= g[j];
1362
+ next[j + 1] ^= gfMul(g[j], root);
1363
+ }
1364
+ g = next;
1365
+ }
1366
+ return g;
1367
+ })();
1368
+ function rs255Encode(info) {
1369
+ if (info.length > RS255_K)
1370
+ throw new RangeError(`rs255Encode: info length ${info.length} > 32`);
1371
+ const cw = new Uint8Array(RS255_N);
1372
+ cw.set(info, 0);
1373
+ const work = new Uint8Array(RS255_N);
1374
+ work.set(info, 0);
1375
+ for (let i = 0; i < RS255_K; i++) {
1376
+ const coef = work[i];
1377
+ if (coef === 0) continue;
1378
+ for (let j = 1; j < GEN_POLY.length; j++)
1379
+ work[i + j] ^= gfMul(GEN_POLY[j], coef);
1380
+ }
1381
+ cw.set(work.subarray(RS255_K), RS255_K);
1382
+ return cw;
1383
+ }
1384
+ var genMatrix = null;
1385
+ function generatorMatrix() {
1386
+ if (genMatrix) return genMatrix;
1387
+ const rows = [];
1388
+ for (let r = 0; r < RS255_N; r++) rows.push(new Uint8Array(RS255_K));
1389
+ for (let c = 0; c < RS255_K; c++) {
1390
+ const e = new Uint8Array(RS255_K);
1391
+ e[c] = 1;
1392
+ const cw = rs255Encode(e);
1393
+ for (let r = 0; r < RS255_N; r++) rows[r][c] = cw[r];
1394
+ }
1395
+ genMatrix = rows;
1396
+ return rows;
1397
+ }
1398
+ function rs255DecodeErasures(pages, k) {
1399
+ if (k < 1 || k > RS255_K) return null;
1400
+ const seen = /* @__PURE__ */ new Set();
1401
+ const use = [];
1402
+ for (const p of pages) {
1403
+ if (p.pageId < 1 || p.pageId > RS255_N || seen.has(p.pageId)) continue;
1404
+ seen.add(p.pageId);
1405
+ use.push(p);
1406
+ if (use.length === k) break;
1407
+ }
1408
+ if (use.length < k) return null;
1409
+ const width = use[0].octets.length;
1410
+ if (use.some((p) => p.octets.length !== width)) return null;
1411
+ const G = generatorMatrix();
1412
+ const a = use.map((p) => G[p.pageId - 1].slice(0, k));
1413
+ const inv = use.map((_, r) => {
1414
+ const row = new Uint8Array(k);
1415
+ row[r] = 1;
1416
+ return row;
1417
+ });
1418
+ for (let c = 0; c < k; c++) {
1419
+ let pivot = -1;
1420
+ for (let r = c; r < k; r++)
1421
+ if (a[r][c] !== 0) {
1422
+ pivot = r;
1423
+ break;
1424
+ }
1425
+ if (pivot < 0) return null;
1426
+ [a[c], a[pivot]] = [a[pivot], a[c]];
1427
+ [inv[c], inv[pivot]] = [inv[pivot], inv[c]];
1428
+ const s = gfInv(a[c][c]);
1429
+ for (let j = 0; j < k; j++) {
1430
+ a[c][j] = gfMul(a[c][j], s);
1431
+ inv[c][j] = gfMul(inv[c][j], s);
1432
+ }
1433
+ for (let r = 0; r < k; r++) {
1434
+ if (r === c || a[r][c] === 0) continue;
1435
+ const f = a[r][c];
1436
+ for (let j = 0; j < k; j++) {
1437
+ a[r][j] ^= gfMul(a[c][j], f);
1438
+ inv[r][j] ^= gfMul(inv[c][j], f);
1439
+ }
1440
+ }
1441
+ }
1442
+ const out = new Uint8Array(k * width);
1443
+ for (let r = 0; r < k; r++)
1444
+ for (let c = 0; c < width; c++) {
1445
+ let v = 0;
1446
+ for (let j = 0; j < k; j++) v ^= gfMul(inv[r][j], use[j].octets[c]);
1447
+ out[r * width + c] = v;
1448
+ }
1449
+ return out;
1450
+ }
1451
+
1452
+ // src/navbits/has.ts
1453
+ var HAS_DUMMY_HEADER = 11484099;
1454
+ var HAS_VALIDITY_SECONDS = [
1455
+ 5,
1456
+ 10,
1457
+ 15,
1458
+ 20,
1459
+ 30,
1460
+ 60,
1461
+ 90,
1462
+ 120,
1463
+ 180,
1464
+ 240,
1465
+ 300,
1466
+ 600,
1467
+ 900,
1468
+ 1800,
1469
+ 3600,
1470
+ null
1471
+ ];
1472
+ var HAS_GPS_SIGNALS = [
1473
+ "L1 C/A",
1474
+ "Reserved",
1475
+ "Reserved",
1476
+ "L1C(D)",
1477
+ "L1C(P)",
1478
+ "L1C(D+P)",
1479
+ "L2 CM",
1480
+ "L2 CL",
1481
+ "L2 CM+CL",
1482
+ "L2 P",
1483
+ "Reserved",
1484
+ "L5 I",
1485
+ "L5 Q",
1486
+ "L5 I+Q",
1487
+ "Reserved",
1488
+ "Reserved"
1489
+ ];
1490
+ var HAS_GALILEO_SIGNALS = [
1491
+ "E1-B",
1492
+ "E1-C",
1493
+ "E1-B+E1-C",
1494
+ "E5a-I",
1495
+ "E5a-Q",
1496
+ "E5a-I+E5a-Q",
1497
+ "E5b-I",
1498
+ "E5b-Q",
1499
+ "E5b-I+E5b-Q",
1500
+ "E5-I",
1501
+ "E5-Q",
1502
+ "E5-I+E5-Q",
1503
+ "E6-B",
1504
+ "E6-C",
1505
+ "E6-B+E6-C",
1506
+ "Reserved"
1507
+ ];
1508
+ var SYSTEM_OF_GNSS_ID = { 0: "G", 2: "E" };
1509
+ var IOD_BITS = { G: 8, E: 10 };
1510
+ function hasPageCrcOk(page) {
1511
+ if (page.length < 61) return false;
1512
+ return crc24q(page, 462) === getBitU(page, 462, 24);
1513
+ }
1514
+ function parseHasPage(page) {
1515
+ if (getBitU(page, 14, 24) === HAS_DUMMY_HEADER) return null;
1516
+ const body = new Uint8Array(53);
1517
+ for (let i = 0; i < 53; i++) body[i] = getBitU(page, 38 + 8 * i, 8);
1518
+ return {
1519
+ status: getBitU(page, 14, 2),
1520
+ messageType: getBitU(page, 18, 2),
1521
+ messageId: getBitU(page, 20, 5),
1522
+ messageSize: getBitU(page, 25, 5) + 1,
1523
+ pageId: getBitU(page, 30, 8),
1524
+ body
1525
+ };
1526
+ }
1527
+ var Cursor = class {
1528
+ constructor(buf, bits) {
1529
+ this.buf = buf;
1530
+ this.bits = bits;
1531
+ }
1532
+ pos = 0;
1533
+ u(len) {
1534
+ if (this.pos + len > this.bits) throw new RangeError("truncated");
1535
+ const v = getBitU(this.buf, this.pos, len);
1536
+ this.pos += len;
1537
+ return v;
1538
+ }
1539
+ s(len) {
1540
+ if (this.pos + len > this.bits) throw new RangeError("truncated");
1541
+ const v = getBitS(this.buf, this.pos, len);
1542
+ this.pos += len;
1543
+ return v;
1544
+ }
1545
+ };
1546
+ var prnOf = (system, idx) => `${system}${String(idx + 1).padStart(2, "0")}`;
1547
+ function parseMaskBlock(c, maskId) {
1548
+ const nSys = c.u(4);
1549
+ const systems = [];
1550
+ for (let s = 0; s < nSys; s++) {
1551
+ const gnssId = c.u(4);
1552
+ const system = SYSTEM_OF_GNSS_ID[gnssId];
1553
+ if (system === void 0) throw new RangeError(`unknown GNSS ID ${gnssId}`);
1554
+ const prns = [];
1555
+ for (let b = 0; b < 40; b++) if (c.u(1)) prns.push(prnOf(system, b));
1556
+ const signalIndices = [];
1557
+ for (let b = 0; b < 16; b++) if (c.u(1)) signalIndices.push(b);
1558
+ const signalNames = system === "G" ? HAS_GPS_SIGNALS : HAS_GALILEO_SIGNALS;
1559
+ let cellMask = null;
1560
+ if (c.u(1)) {
1561
+ cellMask = prns.map(() => signalIndices.map(() => c.u(1) === 1));
1562
+ }
1563
+ systems.push({
1564
+ gnssId,
1565
+ system,
1566
+ prns,
1567
+ signalIndices,
1568
+ signals: signalIndices.map((i) => signalNames[i]),
1569
+ cellMask,
1570
+ navMessage: c.u(3)
1571
+ });
1572
+ }
1573
+ c.u(6);
1574
+ return { maskId, systems };
1575
+ }
1576
+ function parseOrbitBlock(c, masks) {
1577
+ const validityIndex = c.u(4);
1578
+ const corrections = [];
1579
+ for (const sys of masks.systems) {
1580
+ for (const prn of sys.prns) {
1581
+ const gnssIod = c.u(IOD_BITS[sys.system]);
1582
+ const rad = c.s(13);
1583
+ const inT = c.s(12);
1584
+ const cross = c.s(12);
1585
+ corrections.push({
1586
+ system: sys.system,
1587
+ prn,
1588
+ gnssIod,
1589
+ deltaRadial: rad === -4096 ? null : rad * 25e-4,
1590
+ deltaInTrack: inT === -2048 ? null : inT * 8e-3,
1591
+ deltaCrossTrack: cross === -2048 ? null : cross * 8e-3
1592
+ });
1593
+ }
1594
+ }
1595
+ return {
1596
+ validityIndex,
1597
+ validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
1598
+ corrections
1599
+ };
1600
+ }
1601
+ function clockValue(c, system, prn, multiplier) {
1602
+ const raw = c.s(13);
1603
+ return {
1604
+ system,
1605
+ prn,
1606
+ deltaClock: raw === -4096 || raw === 4095 ? null : raw * 25e-4 * multiplier,
1607
+ notUsable: raw === 4095
1608
+ };
1609
+ }
1610
+ function parseClockFullBlock(c, masks) {
1611
+ const validityIndex = c.u(4);
1612
+ const multipliers = masks.systems.map((sys) => ({
1613
+ system: sys.system,
1614
+ multiplier: c.u(2) + 1
1615
+ }));
1616
+ const corrections = [];
1617
+ for (let s = 0; s < masks.systems.length; s++) {
1618
+ const sys = masks.systems[s];
1619
+ for (const prn of sys.prns)
1620
+ corrections.push(
1621
+ clockValue(c, sys.system, prn, multipliers[s].multiplier)
1622
+ );
1623
+ }
1624
+ return {
1625
+ validityIndex,
1626
+ validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
1627
+ multipliers,
1628
+ corrections
1629
+ };
1630
+ }
1631
+ function parseClockSubsetBlock(c, masks) {
1632
+ const validityIndex = c.u(4);
1633
+ const nSys = c.u(4);
1634
+ const multipliers = [];
1635
+ const corrections = [];
1636
+ for (let s = 0; s < nSys; s++) {
1637
+ const gnssId = c.u(4);
1638
+ const sys = masks.systems.find((m) => m.gnssId === gnssId);
1639
+ if (!sys) throw new RangeError(`subset GNSS ID ${gnssId} not in mask`);
1640
+ const multiplier = c.u(2) + 1;
1641
+ multipliers.push({ system: sys.system, multiplier });
1642
+ const selected = [];
1643
+ for (const prn of sys.prns) if (c.u(1)) selected.push(prn);
1644
+ for (const prn of selected)
1645
+ corrections.push(clockValue(c, sys.system, prn, multiplier));
1646
+ }
1647
+ return {
1648
+ validityIndex,
1649
+ validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
1650
+ multipliers,
1651
+ corrections
1652
+ };
1653
+ }
1654
+ function parseBiasBlock(c, masks, kind) {
1655
+ const validityIndex = c.u(4);
1656
+ const biases = [];
1657
+ for (const sys of masks.systems) {
1658
+ for (let s = 0; s < sys.prns.length; s++) {
1659
+ for (let g = 0; g < sys.signalIndices.length; g++) {
1660
+ if (sys.cellMask && !sys.cellMask[s][g]) continue;
1661
+ const raw = c.s(11);
1662
+ const entry = {
1663
+ system: sys.system,
1664
+ prn: sys.prns[s],
1665
+ signalIndex: sys.signalIndices[g],
1666
+ signal: sys.signals[g],
1667
+ bias: raw === -1024 ? null : raw * (kind === "code" ? 0.02 : 0.01)
1668
+ };
1669
+ if (kind === "phase") entry.discontinuity = c.u(2);
1670
+ biases.push(entry);
1671
+ }
1672
+ }
1673
+ }
1674
+ return {
1675
+ validityIndex,
1676
+ validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
1677
+ biases
1678
+ };
1679
+ }
1680
+ function parseHasMt1(payload, header, cachedMasks) {
1681
+ const c = new Cursor(payload, payload.length * 8);
1682
+ const toh = c.u(12);
1683
+ const flags = {
1684
+ mask: c.u(1) === 1,
1685
+ orbit: c.u(1) === 1,
1686
+ clockFullSet: c.u(1) === 1,
1687
+ clockSubset: c.u(1) === 1,
1688
+ codeBias: c.u(1) === 1,
1689
+ phaseBias: c.u(1) === 1
1690
+ };
1691
+ c.u(4);
1692
+ const maskId = c.u(5);
1693
+ const iodSetId = c.u(5);
1694
+ const msg = {
1695
+ ...header,
1696
+ toh,
1697
+ maskId,
1698
+ iodSetId,
1699
+ flags,
1700
+ maskFromCache: false,
1701
+ parseError: null
1702
+ };
1703
+ try {
1704
+ let masks;
1705
+ if (flags.mask) {
1706
+ masks = parseMaskBlock(c, maskId);
1707
+ } else if (cachedMasks) {
1708
+ masks = cachedMasks;
1709
+ msg.maskFromCache = true;
1710
+ } else {
1711
+ msg.parseError = "mask-unavailable";
1712
+ return msg;
1713
+ }
1714
+ msg.masks = masks;
1715
+ if (flags.orbit) msg.orbit = parseOrbitBlock(c, masks);
1716
+ if (flags.clockFullSet) msg.clockFullSet = parseClockFullBlock(c, masks);
1717
+ if (flags.clockSubset) msg.clockSubset = parseClockSubsetBlock(c, masks);
1718
+ if (flags.codeBias) msg.codeBias = parseBiasBlock(c, masks, "code");
1719
+ if (flags.phaseBias) msg.phaseBias = parseBiasBlock(c, masks, "phase");
1720
+ } catch (e) {
1721
+ msg.parseError = e instanceof Error ? e.message : String(e);
1722
+ }
1723
+ return msg;
1724
+ }
1725
+ var HasAssembler = class {
1726
+ slots = /* @__PURE__ */ new Map();
1727
+ lastMessageId = -1;
1728
+ maskCache = /* @__PURE__ */ new Map();
1729
+ /** Pages a message may span before its slot is restarted (s). */
1730
+ timeoutSec;
1731
+ stats = {
1732
+ pages: 0,
1733
+ dummyPages: 0,
1734
+ skippedPages: 0,
1735
+ duplicatePages: 0,
1736
+ resets: 0,
1737
+ messages: 0,
1738
+ parseErrors: 0
1739
+ };
1740
+ constructor(opts = {}) {
1741
+ this.timeoutSec = opts.timeoutSec ?? 30;
1742
+ }
1743
+ /**
1744
+ * Push one 492-bit C/NAV page (≥ 61 bytes; check the CRC first with
1745
+ * `hasPageCrcOk`). `tow` — a receiver time of week in seconds, any
1746
+ * origin as long as it is common to all pages — enables the slot
1747
+ * timeout and is stamped on completed messages.
1748
+ */
1749
+ push(page, tow) {
1750
+ const p = parseHasPage(page);
1751
+ if (!p) {
1752
+ this.stats.dummyPages++;
1753
+ return null;
1754
+ }
1755
+ this.stats.pages++;
1756
+ if (p.messageType !== 1 || p.status > 1 || p.pageId === 0) {
1757
+ this.stats.skippedPages++;
1758
+ return null;
1759
+ }
1760
+ if (p.messageId === this.lastMessageId) return null;
1761
+ let slot = this.slots.get(p.messageId);
1762
+ const timedOut = slot?.firstTow !== void 0 && tow !== void 0 && tow - slot.firstTow > this.timeoutSec;
1763
+ if (slot && (slot.messageSize !== p.messageSize || timedOut)) {
1764
+ this.stats.resets++;
1765
+ slot = void 0;
1766
+ }
1767
+ if (!slot) {
1768
+ slot = {
1769
+ messageSize: p.messageSize,
1770
+ status: p.status,
1771
+ pages: [],
1772
+ bodies: /* @__PURE__ */ new Map(),
1773
+ firstTow: tow
1774
+ };
1775
+ this.slots.set(p.messageId, slot);
1776
+ }
1777
+ const bodyKey = Array.from(
1778
+ p.body,
1779
+ (b) => b.toString(16).padStart(2, "0")
1780
+ ).join("");
1781
+ const existing = slot.bodies.get(p.pageId);
1782
+ if (existing !== void 0) {
1783
+ if (existing === bodyKey) {
1784
+ this.stats.duplicatePages++;
1785
+ return null;
1786
+ }
1787
+ this.stats.resets++;
1788
+ slot.pages = [];
1789
+ slot.bodies.clear();
1790
+ slot.firstTow = tow;
1791
+ }
1792
+ slot.bodies.set(p.pageId, bodyKey);
1793
+ slot.pages.push({ pageId: p.pageId, octets: p.body });
1794
+ if (slot.pages.length < slot.messageSize) return null;
1795
+ const payload = rs255DecodeErasures(slot.pages, slot.messageSize);
1796
+ if (!payload) return null;
1797
+ this.slots.delete(p.messageId);
1798
+ this.lastMessageId = p.messageId;
1799
+ const msg = parseHasMt1(
1800
+ payload,
1801
+ {
1802
+ status: slot.status,
1803
+ messageId: p.messageId,
1804
+ messageSize: slot.messageSize
1805
+ },
1806
+ void 0
1807
+ );
1808
+ const resolved = msg.parseError === "mask-unavailable" && this.maskCache.has(msg.maskId) ? parseHasMt1(
1809
+ payload,
1810
+ {
1811
+ status: slot.status,
1812
+ messageId: p.messageId,
1813
+ messageSize: slot.messageSize
1814
+ },
1815
+ this.maskCache.get(msg.maskId)
1816
+ ) : msg;
1817
+ if (resolved.flags.mask && resolved.masks)
1818
+ this.maskCache.set(resolved.maskId, resolved.masks);
1819
+ if (tow !== void 0) resolved.tow = tow;
1820
+ this.stats.messages++;
1821
+ if (resolved.parseError !== null) this.stats.parseErrors++;
1822
+ return resolved;
1823
+ }
1824
+ };
1825
+
1826
+ // src/sbf/rawnav-has.ts
1827
+ function parseSbfHas(data) {
1828
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1829
+ const assembler = new HasAssembler();
1830
+ const messages = [];
1831
+ let pagesSeen = 0;
1832
+ let pagesBadCrc = 0;
1833
+ scanSbfFrames(data, view, (id, b, len) => {
1834
+ if (id !== 4024 || len < 84) return;
1835
+ pagesSeen++;
1836
+ const page = new Uint8Array(64);
1837
+ for (let k = 0; k < 16; k++) {
1838
+ const w = view.getUint32(b + 20 + 4 * k, true);
1839
+ page[4 * k] = w >>> 24;
1840
+ page[4 * k + 1] = w >>> 16 & 255;
1841
+ page[4 * k + 2] = w >>> 8 & 255;
1842
+ page[4 * k + 3] = w & 255;
1843
+ }
1844
+ if (!hasPageCrcOk(page)) {
1845
+ pagesBadCrc++;
1846
+ return;
1847
+ }
1848
+ const tow = view.getUint32(b + 8, true) / 1e3;
1849
+ const msg = assembler.push(page, tow);
1850
+ if (msg) messages.push({ ...msg, tow, wnc: view.getUint16(b + 12, true) });
1851
+ });
1852
+ return {
1853
+ messages,
1854
+ pagesSeen,
1855
+ pagesBadCrc,
1856
+ pagesDummy: assembler.stats.dummyPages,
1857
+ pagesHas: assembler.stats.pages
1858
+ };
1859
+ }
1860
+
1338
1861
  // src/sbf/index.ts
1339
1862
  var CLIGHT = 299792458;
1340
1863
  var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
@@ -2013,5 +2536,6 @@ export {
2013
2536
  parseSbfGalNav,
2014
2537
  parseSbfBdsNav,
2015
2538
  parseSbfGloNav,
2539
+ parseSbfHas,
2016
2540
  parseSbfMeas
2017
2541
  };