gnss-js 1.11.0 → 1.13.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.
- package/dist/{chunk-LKFSXVIZ.js → chunk-E5CP37P2.js} +1 -1
- package/dist/{chunk-77VUSDNI.js → chunk-HXF7XVW7.js} +499 -1
- package/dist/{chunk-DMNFB7Q6.js → chunk-VAHMHYID.js} +9 -2
- package/dist/index.cjs +517 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +23 -5
- package/dist/orbit.cjs +9 -1
- package/dist/orbit.d.cts +8 -1
- package/dist/orbit.d.ts +8 -1
- package/dist/orbit.js +3 -1
- package/dist/positioning.js +2 -2
- package/dist/rinex.cjs +508 -2
- package/dist/rinex.d.cts +129 -1
- package/dist/rinex.d.ts +129 -1
- package/dist/rinex.js +19 -3
- package/package.json +1 -1
package/dist/rinex.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(rinex_exports, {
|
|
|
23
23
|
EMPTY_WARNINGS: () => EMPTY_WARNINGS,
|
|
24
24
|
SYSTEM_ORDER: () => SYSTEM_ORDER,
|
|
25
25
|
WarningAccumulator: () => WarningAccumulator,
|
|
26
|
+
createGzipLineSink: () => createGzipLineSink,
|
|
26
27
|
crxDecompress: () => crxDecompress,
|
|
27
28
|
crxRepair: () => crxRepair,
|
|
28
29
|
fmtF: () => fmtF,
|
|
@@ -33,9 +34,16 @@ __export(rinex_exports, {
|
|
|
33
34
|
parseIonex: () => parseIonex,
|
|
34
35
|
parseNavFile: () => parseNavFile,
|
|
35
36
|
parseRinexStream: () => parseRinexStream,
|
|
37
|
+
parseSp3: () => parseSp3,
|
|
38
|
+
sp3Position: () => sp3Position,
|
|
39
|
+
stationHeaderLines: () => stationHeaderLines,
|
|
36
40
|
systemCmp: () => systemCmp,
|
|
37
41
|
systemName: () => systemName,
|
|
38
|
-
|
|
42
|
+
writeModernObsBlob: () => writeModernObsBlob,
|
|
43
|
+
writeRinex2ObsBlob: () => writeRinex2ObsBlob,
|
|
44
|
+
writeRinex4ObsBlob: () => writeRinex4ObsBlob,
|
|
45
|
+
writeRinexNav: () => writeRinexNav,
|
|
46
|
+
writeRinexObsBlob: () => writeRinexObsBlob
|
|
39
47
|
});
|
|
40
48
|
module.exports = __toCommonJS(rinex_exports);
|
|
41
49
|
|
|
@@ -1535,11 +1543,502 @@ function parseIonex(text) {
|
|
|
1535
1543
|
}
|
|
1536
1544
|
return { epochs, lats, lons, maps };
|
|
1537
1545
|
}
|
|
1546
|
+
|
|
1547
|
+
// src/rinex/sp3.ts
|
|
1548
|
+
var BAD_CLOCK = 999999.999999;
|
|
1549
|
+
function parseSp3(text) {
|
|
1550
|
+
const lines = text.split("\n");
|
|
1551
|
+
const epochs = [];
|
|
1552
|
+
const satellites = {};
|
|
1553
|
+
let version = "";
|
|
1554
|
+
let intervalSec = 0;
|
|
1555
|
+
let timeSystem = "GPS";
|
|
1556
|
+
let timeSystemSet = false;
|
|
1557
|
+
let epochIdx = -1;
|
|
1558
|
+
for (const line of lines) {
|
|
1559
|
+
if (!version && line.startsWith("#") && line.charAt(1) !== "#") {
|
|
1560
|
+
version = line.charAt(1);
|
|
1561
|
+
}
|
|
1562
|
+
if (line.startsWith("##")) {
|
|
1563
|
+
const f = line.slice(2).trim().split(/\s+/);
|
|
1564
|
+
intervalSec = Number(f[2]) || 0;
|
|
1565
|
+
} else if (line.startsWith("%c") && !timeSystemSet) {
|
|
1566
|
+
const sys = line.slice(9, 12).trim();
|
|
1567
|
+
if (sys && !/^c+$/.test(sys)) {
|
|
1568
|
+
timeSystem = sys;
|
|
1569
|
+
timeSystemSet = true;
|
|
1570
|
+
}
|
|
1571
|
+
} else if (line.startsWith("*")) {
|
|
1572
|
+
const f = line.slice(1).trim().split(/\s+/).map(Number);
|
|
1573
|
+
if (f.length >= 6 && f.every(Number.isFinite)) {
|
|
1574
|
+
const sec = f[5];
|
|
1575
|
+
epochs.push(
|
|
1576
|
+
Date.UTC(
|
|
1577
|
+
f[0],
|
|
1578
|
+
f[1] - 1,
|
|
1579
|
+
f[2],
|
|
1580
|
+
f[3],
|
|
1581
|
+
f[4],
|
|
1582
|
+
Math.floor(sec),
|
|
1583
|
+
Math.round(sec % 1 * 1e3)
|
|
1584
|
+
)
|
|
1585
|
+
);
|
|
1586
|
+
epochIdx++;
|
|
1587
|
+
}
|
|
1588
|
+
} else if (line.startsWith("P") && epochIdx >= 0) {
|
|
1589
|
+
const prn = line.slice(1, 4).replace(/\s/g, "0");
|
|
1590
|
+
const f = line.slice(4).trim().split(/\s+/).map(Number);
|
|
1591
|
+
if (f.length < 3) continue;
|
|
1592
|
+
const [xKm, yKm, zKm] = f;
|
|
1593
|
+
let arr = satellites[prn];
|
|
1594
|
+
if (!arr) satellites[prn] = arr = [];
|
|
1595
|
+
while (arr.length < epochIdx) arr.push(null);
|
|
1596
|
+
if (xKm === 0 && yKm === 0 && zKm === 0) {
|
|
1597
|
+
arr.push(null);
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const clkUs = f[3];
|
|
1601
|
+
arr.push({
|
|
1602
|
+
x: xKm * 1e3,
|
|
1603
|
+
y: yKm * 1e3,
|
|
1604
|
+
z: zKm * 1e3,
|
|
1605
|
+
clk: clkUs !== void 0 && Math.abs(clkUs) < BAD_CLOCK ? clkUs * 1e-6 : null
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
for (const arr of Object.values(satellites)) {
|
|
1610
|
+
while (arr.length < epochs.length) arr.push(null);
|
|
1611
|
+
}
|
|
1612
|
+
return { version, epochs, intervalSec, timeSystem, satellites };
|
|
1613
|
+
}
|
|
1614
|
+
function sp3Position(sp3, prn, tMs, order = 9) {
|
|
1615
|
+
const samples = sp3.satellites[prn];
|
|
1616
|
+
const { epochs } = sp3;
|
|
1617
|
+
const n = epochs.length;
|
|
1618
|
+
if (!samples || n < order) return null;
|
|
1619
|
+
if (tMs < epochs[0] || tMs > epochs[n - 1]) return null;
|
|
1620
|
+
let lo = 0;
|
|
1621
|
+
let hi = n - 1;
|
|
1622
|
+
while (lo < hi) {
|
|
1623
|
+
const mid = lo + hi + 1 >> 1;
|
|
1624
|
+
if (epochs[mid] <= tMs) lo = mid;
|
|
1625
|
+
else hi = mid - 1;
|
|
1626
|
+
}
|
|
1627
|
+
let start = lo - (order >> 1) + (order % 2 === 0 ? 1 : 0);
|
|
1628
|
+
start = Math.max(0, Math.min(n - order, start));
|
|
1629
|
+
for (let i = start; i < start + order; i++) {
|
|
1630
|
+
if (!samples[i]) return null;
|
|
1631
|
+
}
|
|
1632
|
+
const t0 = epochs[start];
|
|
1633
|
+
const t = (tMs - t0) / 1e3;
|
|
1634
|
+
let x = 0;
|
|
1635
|
+
let y = 0;
|
|
1636
|
+
let z = 0;
|
|
1637
|
+
for (let i = 0; i < order; i++) {
|
|
1638
|
+
const ti = (epochs[start + i] - t0) / 1e3;
|
|
1639
|
+
let w = 1;
|
|
1640
|
+
for (let j = 0; j < order; j++) {
|
|
1641
|
+
if (j === i) continue;
|
|
1642
|
+
const tj = (epochs[start + j] - t0) / 1e3;
|
|
1643
|
+
w *= (t - tj) / (ti - tj);
|
|
1644
|
+
}
|
|
1645
|
+
const s = samples[start + i];
|
|
1646
|
+
x += w * s.x;
|
|
1647
|
+
y += w * s.y;
|
|
1648
|
+
z += w * s.z;
|
|
1649
|
+
}
|
|
1650
|
+
let clk = null;
|
|
1651
|
+
const a = samples[lo];
|
|
1652
|
+
const b = samples[Math.min(lo + 1, n - 1)];
|
|
1653
|
+
if (a?.clk != null && b?.clk != null) {
|
|
1654
|
+
const span = epochs[Math.min(lo + 1, n - 1)] - epochs[lo];
|
|
1655
|
+
const f = span > 0 ? (tMs - epochs[lo]) / span : 0;
|
|
1656
|
+
clk = a.clk * (1 - f) + b.clk * f;
|
|
1657
|
+
} else if (a?.clk != null) {
|
|
1658
|
+
clk = a.clk;
|
|
1659
|
+
}
|
|
1660
|
+
return { x, y, z, clk };
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// src/rinex/obs-writer-common.ts
|
|
1664
|
+
function createGzipLineSink() {
|
|
1665
|
+
const encoder = new TextEncoder();
|
|
1666
|
+
const compressor = new CompressionStream("gzip");
|
|
1667
|
+
const writer = compressor.writable.getWriter();
|
|
1668
|
+
const compressedChunks = [];
|
|
1669
|
+
const readerDone = (async () => {
|
|
1670
|
+
const reader = compressor.readable.getReader();
|
|
1671
|
+
for (; ; ) {
|
|
1672
|
+
const { done, value } = await reader.read();
|
|
1673
|
+
if (done) break;
|
|
1674
|
+
compressedChunks.push(value);
|
|
1675
|
+
}
|
|
1676
|
+
})();
|
|
1677
|
+
let batch = [];
|
|
1678
|
+
async function flush() {
|
|
1679
|
+
if (batch.length === 0) return;
|
|
1680
|
+
await writer.write(encoder.encode(batch.join("\n") + "\n"));
|
|
1681
|
+
batch = [];
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
push(line) {
|
|
1685
|
+
batch.push(line);
|
|
1686
|
+
},
|
|
1687
|
+
flush,
|
|
1688
|
+
async finish() {
|
|
1689
|
+
await flush();
|
|
1690
|
+
await writer.close();
|
|
1691
|
+
await readerDone;
|
|
1692
|
+
return new Blob(compressedChunks, {
|
|
1693
|
+
type: "application/gzip"
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
function stationHeaderLines(header) {
|
|
1699
|
+
const pos = header.approxPosition ?? [0, 0, 0];
|
|
1700
|
+
const delta = header.antDelta ?? [0, 0, 0];
|
|
1701
|
+
return [
|
|
1702
|
+
hdrLine(header.markerName || "UNKNOWN", "MARKER NAME"),
|
|
1703
|
+
hdrLine("", "MARKER NUMBER"),
|
|
1704
|
+
hdrLine(
|
|
1705
|
+
`${padL(header.observer || "", 20)}${padL(header.agency || "", 40)}`,
|
|
1706
|
+
"OBSERVER / AGENCY"
|
|
1707
|
+
),
|
|
1708
|
+
hdrLine(
|
|
1709
|
+
`${padL(header.receiverNumber || "", 20)}${padL(header.receiverType || "", 20)}${padL(header.receiverVersion || "", 20)}`,
|
|
1710
|
+
"REC # / TYPE / VERS"
|
|
1711
|
+
),
|
|
1712
|
+
hdrLine(
|
|
1713
|
+
`${padL(header.antNumber || "", 20)}${padL(header.antType || "", 20)}`,
|
|
1714
|
+
"ANT # / TYPE"
|
|
1715
|
+
),
|
|
1716
|
+
hdrLine(
|
|
1717
|
+
`${fmtF(pos[0], 14, 4)}${fmtF(pos[1], 14, 4)}${fmtF(pos[2], 14, 4)}`,
|
|
1718
|
+
"APPROX POSITION XYZ"
|
|
1719
|
+
),
|
|
1720
|
+
hdrLine(
|
|
1721
|
+
`${fmtF(delta[0], 14, 4)}${fmtF(delta[1], 14, 4)}${fmtF(delta[2], 14, 4)}`,
|
|
1722
|
+
"ANTENNA: DELTA H/E/N"
|
|
1723
|
+
)
|
|
1724
|
+
];
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/rinex/obs-writer.ts
|
|
1728
|
+
function writeRinexObsBlob(header, epochs, obsTypes) {
|
|
1729
|
+
return writeModernObsBlob(header, epochs, obsTypes, {
|
|
1730
|
+
version: "3.04",
|
|
1731
|
+
comment: "Merged from multiple RINEX files"
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
async function writeModernObsBlob(header, epochs, obsTypes, opts) {
|
|
1735
|
+
if (epochs.length === 0) return new Blob([], { type: "application/gzip" });
|
|
1736
|
+
const sink = createGzipLineSink();
|
|
1737
|
+
const BATCH = 200;
|
|
1738
|
+
const systems = [...obsTypes.keys()];
|
|
1739
|
+
const sysChar = systems.length === 1 ? systems[0] : "M";
|
|
1740
|
+
sink.push(
|
|
1741
|
+
hdrLine(
|
|
1742
|
+
` ${opts.version} OBSERVATION DATA ${sysChar}`,
|
|
1743
|
+
"RINEX VERSION / TYPE"
|
|
1744
|
+
)
|
|
1745
|
+
);
|
|
1746
|
+
const now = /* @__PURE__ */ new Date();
|
|
1747
|
+
const dateStr = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")} ${String(now.getUTCHours()).padStart(2, "0")}${String(now.getUTCMinutes()).padStart(2, "0")}${String(now.getUTCSeconds()).padStart(2, "0")} UTC`;
|
|
1748
|
+
sink.push(
|
|
1749
|
+
hdrLine(
|
|
1750
|
+
`${padL("GNSSCalc", 20)}${padL("", 20)}${dateStr}`,
|
|
1751
|
+
"PGM / RUN BY / DATE"
|
|
1752
|
+
)
|
|
1753
|
+
);
|
|
1754
|
+
sink.push(hdrLine(opts.comment, "COMMENT"));
|
|
1755
|
+
for (const line of stationHeaderLines(header)) sink.push(line);
|
|
1756
|
+
for (const [sys, types] of obsTypes) {
|
|
1757
|
+
for (let i = 0; i < types.length; i += 13) {
|
|
1758
|
+
const chunk = types.slice(i, i + 13);
|
|
1759
|
+
let content;
|
|
1760
|
+
if (i === 0) {
|
|
1761
|
+
content = `${sys} ${padR(String(types.length), 3)}`;
|
|
1762
|
+
} else {
|
|
1763
|
+
content = " ";
|
|
1764
|
+
}
|
|
1765
|
+
content += chunk.map((t) => ` ${padL(t, 3)}`).join("");
|
|
1766
|
+
sink.push(hdrLine(content, "SYS / # / OBS TYPES"));
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const timeSys = systems.includes("R") && systems.length === 1 ? "GLO" : "GPS";
|
|
1770
|
+
const obsTimeLine = (d) => ` ${d.getUTCFullYear()} ${String(d.getUTCMonth() + 1).padStart(2)} ${String(d.getUTCDate()).padStart(2)} ${String(d.getUTCHours()).padStart(2)} ${String(d.getUTCMinutes()).padStart(2)} ${(d.getUTCSeconds() + d.getUTCMilliseconds() / 1e3).toFixed(7).padStart(10)} ${timeSys}`;
|
|
1771
|
+
sink.push(
|
|
1772
|
+
hdrLine(obsTimeLine(new Date(epochs[0].time)), "TIME OF FIRST OBS")
|
|
1773
|
+
);
|
|
1774
|
+
sink.push(
|
|
1775
|
+
hdrLine(
|
|
1776
|
+
obsTimeLine(new Date(epochs[epochs.length - 1].time)),
|
|
1777
|
+
"TIME OF LAST OBS"
|
|
1778
|
+
)
|
|
1779
|
+
);
|
|
1780
|
+
if (epochs.length >= 2) {
|
|
1781
|
+
const interval = (epochs[1].time - epochs[0].time) / 1e3;
|
|
1782
|
+
if (interval > 0 && interval < 3600) {
|
|
1783
|
+
sink.push(hdrLine(fmtF(interval, 10, 3), "INTERVAL"));
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (header.glonassSlots && Object.keys(header.glonassSlots).length > 0) {
|
|
1787
|
+
const entries = Object.entries(header.glonassSlots).sort(
|
|
1788
|
+
([a], [b]) => Number(a) - Number(b)
|
|
1789
|
+
);
|
|
1790
|
+
for (let i = 0; i < entries.length; i += 8) {
|
|
1791
|
+
const chunk = entries.slice(i, i + 8);
|
|
1792
|
+
let content = i === 0 ? padR(String(entries.length), 3) + " " : " ";
|
|
1793
|
+
for (const [slot, freq] of chunk) {
|
|
1794
|
+
content += `R${padR(slot, 2)} ${padR(String(freq), 2)} `;
|
|
1795
|
+
}
|
|
1796
|
+
sink.push(hdrLine(content, "GLONASS SLOT / FRQ #"));
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
sink.push(hdrLine("", "END OF HEADER"));
|
|
1800
|
+
await sink.flush();
|
|
1801
|
+
let epochCount = 0;
|
|
1802
|
+
for (const epoch of epochs) {
|
|
1803
|
+
const t = new Date(epoch.time);
|
|
1804
|
+
const sec = t.getUTCSeconds() + t.getUTCMilliseconds() / 1e3;
|
|
1805
|
+
const prns = [...epoch.sats.keys()].sort();
|
|
1806
|
+
sink.push(
|
|
1807
|
+
`> ${t.getUTCFullYear()} ${String(t.getUTCMonth() + 1).padStart(2, "0")} ${String(t.getUTCDate()).padStart(2, "0")} ${String(t.getUTCHours()).padStart(2, "0")} ${String(t.getUTCMinutes()).padStart(2, "0")}${fmtF(sec, 11, 7)} 0${padR(String(prns.length), 3)}`
|
|
1808
|
+
);
|
|
1809
|
+
for (const prn of prns) {
|
|
1810
|
+
const sys = prn[0];
|
|
1811
|
+
const sysTypes = obsTypes.get(sys);
|
|
1812
|
+
if (!sysTypes) continue;
|
|
1813
|
+
const valArr = epoch.sats.get(prn);
|
|
1814
|
+
let line = prn;
|
|
1815
|
+
for (let i = 0; i < sysTypes.length; i++) {
|
|
1816
|
+
const val = i < valArr.length ? valArr[i] : NaN;
|
|
1817
|
+
if (!isNaN(val)) {
|
|
1818
|
+
line += fmtF(val, 14, 3) + " ";
|
|
1819
|
+
} else {
|
|
1820
|
+
line += " ".repeat(16);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
sink.push(line);
|
|
1824
|
+
}
|
|
1825
|
+
epochCount++;
|
|
1826
|
+
if (epochCount % BATCH === 0) await sink.flush();
|
|
1827
|
+
}
|
|
1828
|
+
return sink.finish();
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// src/rinex/obs-writer-v2.ts
|
|
1832
|
+
var V3_TO_V2 = {
|
|
1833
|
+
// GPS
|
|
1834
|
+
C1C: "C1",
|
|
1835
|
+
C1S: "C1",
|
|
1836
|
+
C1L: "C1",
|
|
1837
|
+
C1X: "C1",
|
|
1838
|
+
C1W: "P1",
|
|
1839
|
+
C1P: "P1",
|
|
1840
|
+
C2C: "C2",
|
|
1841
|
+
C2S: "P2",
|
|
1842
|
+
C2L: "P2",
|
|
1843
|
+
C2X: "P2",
|
|
1844
|
+
C2W: "P2",
|
|
1845
|
+
C2P: "P2",
|
|
1846
|
+
C5I: "C5",
|
|
1847
|
+
C5Q: "C5",
|
|
1848
|
+
C5X: "C5",
|
|
1849
|
+
L1C: "L1",
|
|
1850
|
+
L1S: "L1",
|
|
1851
|
+
L1L: "L1",
|
|
1852
|
+
L1X: "L1",
|
|
1853
|
+
L1W: "L1",
|
|
1854
|
+
L1P: "L1",
|
|
1855
|
+
L2C: "L2",
|
|
1856
|
+
L2S: "L2",
|
|
1857
|
+
L2L: "L2",
|
|
1858
|
+
L2X: "L2",
|
|
1859
|
+
L2W: "L2",
|
|
1860
|
+
L2P: "L2",
|
|
1861
|
+
L5I: "L5",
|
|
1862
|
+
L5Q: "L5",
|
|
1863
|
+
L5X: "L5",
|
|
1864
|
+
D1C: "D1",
|
|
1865
|
+
D1S: "D1",
|
|
1866
|
+
D1L: "D1",
|
|
1867
|
+
D1X: "D1",
|
|
1868
|
+
D1W: "D1",
|
|
1869
|
+
D1P: "D1",
|
|
1870
|
+
D2C: "D2",
|
|
1871
|
+
D2S: "D2",
|
|
1872
|
+
D2L: "D2",
|
|
1873
|
+
D2X: "D2",
|
|
1874
|
+
D2W: "D2",
|
|
1875
|
+
D2P: "D2",
|
|
1876
|
+
D5I: "D5",
|
|
1877
|
+
D5Q: "D5",
|
|
1878
|
+
D5X: "D5",
|
|
1879
|
+
S1C: "S1",
|
|
1880
|
+
S1S: "S1",
|
|
1881
|
+
S1L: "S1",
|
|
1882
|
+
S1X: "S1",
|
|
1883
|
+
S1W: "S1",
|
|
1884
|
+
S1P: "S1",
|
|
1885
|
+
S2C: "S2",
|
|
1886
|
+
S2S: "S2",
|
|
1887
|
+
S2L: "S2",
|
|
1888
|
+
S2X: "S2",
|
|
1889
|
+
S2W: "S2",
|
|
1890
|
+
S2P: "S2",
|
|
1891
|
+
S5I: "S5",
|
|
1892
|
+
S5Q: "S5",
|
|
1893
|
+
S5X: "S5"
|
|
1894
|
+
};
|
|
1895
|
+
function v3ToV2Code(code3) {
|
|
1896
|
+
return V3_TO_V2[code3] ?? null;
|
|
1897
|
+
}
|
|
1898
|
+
async function writeRinex2ObsBlob(header, epochs, obsTypes) {
|
|
1899
|
+
if (epochs.length === 0) return new Blob([], { type: "application/gzip" });
|
|
1900
|
+
const gpsCodes = obsTypes.get("G") ?? obsTypes.get("R") ?? [];
|
|
1901
|
+
const v2Codes = [];
|
|
1902
|
+
const v2CodeSet = /* @__PURE__ */ new Set();
|
|
1903
|
+
const v3ToV2Map = /* @__PURE__ */ new Map();
|
|
1904
|
+
for (const code3 of gpsCodes) {
|
|
1905
|
+
const code2 = v3ToV2Code(code3);
|
|
1906
|
+
if (!code2) continue;
|
|
1907
|
+
if (!v2CodeSet.has(code2)) {
|
|
1908
|
+
v2CodeSet.add(code2);
|
|
1909
|
+
v3ToV2Map.set(code3, v2Codes.length);
|
|
1910
|
+
v2Codes.push(code2);
|
|
1911
|
+
} else {
|
|
1912
|
+
v3ToV2Map.set(code3, v2Codes.indexOf(code2));
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
const gloCodes = obsTypes.get("R") ?? [];
|
|
1916
|
+
for (const code3 of gloCodes) {
|
|
1917
|
+
const code2 = v3ToV2Code(code3);
|
|
1918
|
+
if (!code2) continue;
|
|
1919
|
+
if (!v2CodeSet.has(code2)) {
|
|
1920
|
+
v2CodeSet.add(code2);
|
|
1921
|
+
v3ToV2Map.set(code3, v2Codes.length);
|
|
1922
|
+
v2Codes.push(code2);
|
|
1923
|
+
} else if (!v3ToV2Map.has(code3)) {
|
|
1924
|
+
v3ToV2Map.set(code3, v2Codes.indexOf(code2));
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
const sink = createGzipLineSink();
|
|
1928
|
+
const BATCH = 200;
|
|
1929
|
+
const hasSystems = /* @__PURE__ */ new Set();
|
|
1930
|
+
for (const epoch of epochs) {
|
|
1931
|
+
for (const prn of epoch.sats.keys()) {
|
|
1932
|
+
const sys = prn[0];
|
|
1933
|
+
if (sys === "G" || sys === "R") hasSystems.add(sys);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
const sysChar = hasSystems.has("G") && hasSystems.has("R") ? "M" : hasSystems.has("R") ? "R" : "G";
|
|
1937
|
+
sink.push(
|
|
1938
|
+
hdrLine(
|
|
1939
|
+
` 2.11 OBSERVATION DATA ${sysChar}`,
|
|
1940
|
+
"RINEX VERSION / TYPE"
|
|
1941
|
+
)
|
|
1942
|
+
);
|
|
1943
|
+
const now = /* @__PURE__ */ new Date();
|
|
1944
|
+
const dateStr = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")} ${String(now.getUTCHours()).padStart(2, "0")}:${String(now.getUTCMinutes()).padStart(2, "0")}:${String(now.getUTCSeconds()).padStart(2, "0")}`;
|
|
1945
|
+
sink.push(
|
|
1946
|
+
hdrLine(
|
|
1947
|
+
`${padL("GNSSCalc", 20)}${padL("", 20)}${dateStr}`,
|
|
1948
|
+
"PGM / RUN BY / DATE"
|
|
1949
|
+
)
|
|
1950
|
+
);
|
|
1951
|
+
sink.push(hdrLine("Converted from RINEX 3 by GNSSCalc", "COMMENT"));
|
|
1952
|
+
for (const line of stationHeaderLines(header)) sink.push(line);
|
|
1953
|
+
sink.push(hdrLine(" 1 1", "WAVELENGTH FACT L1/2"));
|
|
1954
|
+
for (let i = 0; i < v2Codes.length; i += 9) {
|
|
1955
|
+
const chunk = v2Codes.slice(i, i + 9);
|
|
1956
|
+
let content;
|
|
1957
|
+
if (i === 0) {
|
|
1958
|
+
content = padR(String(v2Codes.length), 6);
|
|
1959
|
+
} else {
|
|
1960
|
+
content = " ";
|
|
1961
|
+
}
|
|
1962
|
+
content += chunk.map((c) => padR(c, 6)).join("");
|
|
1963
|
+
sink.push(hdrLine(content, "# / TYPES OF OBSERV"));
|
|
1964
|
+
}
|
|
1965
|
+
const first = new Date(epochs[0].time);
|
|
1966
|
+
const timeSys = sysChar === "R" ? "GLO" : "GPS";
|
|
1967
|
+
sink.push(
|
|
1968
|
+
hdrLine(
|
|
1969
|
+
` ${first.getUTCFullYear()} ${String(first.getUTCMonth() + 1).padStart(2)} ${String(first.getUTCDate()).padStart(2)} ${String(first.getUTCHours()).padStart(2)} ${String(first.getUTCMinutes()).padStart(2)} ${(first.getUTCSeconds() + first.getUTCMilliseconds() / 1e3).toFixed(7).padStart(10)} ${timeSys}`,
|
|
1970
|
+
"TIME OF FIRST OBS"
|
|
1971
|
+
)
|
|
1972
|
+
);
|
|
1973
|
+
sink.push(hdrLine("", "END OF HEADER"));
|
|
1974
|
+
await sink.flush();
|
|
1975
|
+
let epochCount = 0;
|
|
1976
|
+
for (const epoch of epochs) {
|
|
1977
|
+
const t = new Date(epoch.time);
|
|
1978
|
+
const sec = t.getUTCSeconds() + t.getUTCMilliseconds() / 1e3;
|
|
1979
|
+
const prns = [...epoch.sats.keys()].filter((p) => p[0] === "G" || p[0] === "R").sort();
|
|
1980
|
+
if (prns.length === 0) continue;
|
|
1981
|
+
const yy = t.getUTCFullYear() % 100;
|
|
1982
|
+
const yyS = padR(String(yy), 2);
|
|
1983
|
+
const moS = padR(String(t.getUTCMonth() + 1), 2);
|
|
1984
|
+
const ddS = padR(String(t.getUTCDate()), 2);
|
|
1985
|
+
const hhS = padR(String(t.getUTCHours()), 2);
|
|
1986
|
+
const mmS = padR(String(t.getUTCMinutes()), 2);
|
|
1987
|
+
const secS = fmtF(sec, 11, 7);
|
|
1988
|
+
const nSats = padR(String(prns.length), 3);
|
|
1989
|
+
let epochLine = ` ${yyS} ${moS} ${ddS} ${hhS} ${mmS}${secS} 0${nSats}`;
|
|
1990
|
+
for (let i = 0; i < prns.length; i++) {
|
|
1991
|
+
if (i > 0 && i % 12 === 0) {
|
|
1992
|
+
sink.push(epochLine);
|
|
1993
|
+
epochLine = " ".repeat(32);
|
|
1994
|
+
}
|
|
1995
|
+
epochLine += prns[i];
|
|
1996
|
+
}
|
|
1997
|
+
sink.push(epochLine);
|
|
1998
|
+
for (const prn of prns) {
|
|
1999
|
+
const sys = prn[0];
|
|
2000
|
+
const sysCodes3 = obsTypes.get(sys) ?? [];
|
|
2001
|
+
const valArr = epoch.sats.get(prn);
|
|
2002
|
+
const v2Vals = new Array(v2Codes.length).fill(NaN);
|
|
2003
|
+
for (let j = 0; j < sysCodes3.length; j++) {
|
|
2004
|
+
const v2Idx = v3ToV2Map.get(sysCodes3[j]);
|
|
2005
|
+
if (v2Idx != null && j < valArr.length && !isNaN(valArr[j])) {
|
|
2006
|
+
v2Vals[v2Idx] = valArr[j];
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
let line = "";
|
|
2010
|
+
for (let j = 0; j < v2Codes.length; j++) {
|
|
2011
|
+
const val = v2Vals[j];
|
|
2012
|
+
if (!isNaN(val)) {
|
|
2013
|
+
line += fmtF(val, 14, 3) + " ";
|
|
2014
|
+
} else {
|
|
2015
|
+
line += " ".repeat(16);
|
|
2016
|
+
}
|
|
2017
|
+
if ((j + 1) % 5 === 0 || j === v2Codes.length - 1) {
|
|
2018
|
+
sink.push(line);
|
|
2019
|
+
line = "";
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
epochCount++;
|
|
2024
|
+
if (epochCount % BATCH === 0) await sink.flush();
|
|
2025
|
+
}
|
|
2026
|
+
return sink.finish();
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// src/rinex/obs-writer-v4.ts
|
|
2030
|
+
function writeRinex4ObsBlob(header, epochs, obsTypes) {
|
|
2031
|
+
return writeModernObsBlob(header, epochs, obsTypes, {
|
|
2032
|
+
version: "4.01",
|
|
2033
|
+
comment: "Converted to RINEX 4 by GNSSCalc"
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
1538
2036
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1539
2037
|
0 && (module.exports = {
|
|
1540
2038
|
EMPTY_WARNINGS,
|
|
1541
2039
|
SYSTEM_ORDER,
|
|
1542
2040
|
WarningAccumulator,
|
|
2041
|
+
createGzipLineSink,
|
|
1543
2042
|
crxDecompress,
|
|
1544
2043
|
crxRepair,
|
|
1545
2044
|
fmtF,
|
|
@@ -1550,7 +2049,14 @@ function parseIonex(text) {
|
|
|
1550
2049
|
parseIonex,
|
|
1551
2050
|
parseNavFile,
|
|
1552
2051
|
parseRinexStream,
|
|
2052
|
+
parseSp3,
|
|
2053
|
+
sp3Position,
|
|
2054
|
+
stationHeaderLines,
|
|
1553
2055
|
systemCmp,
|
|
1554
2056
|
systemName,
|
|
1555
|
-
|
|
2057
|
+
writeModernObsBlob,
|
|
2058
|
+
writeRinex2ObsBlob,
|
|
2059
|
+
writeRinex4ObsBlob,
|
|
2060
|
+
writeRinexNav,
|
|
2061
|
+
writeRinexObsBlob
|
|
1556
2062
|
});
|
package/dist/rinex.d.cts
CHANGED
|
@@ -135,4 +135,132 @@ interface IonexGrid {
|
|
|
135
135
|
}
|
|
136
136
|
declare function parseIonex(text: string): IonexGrid;
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
/**
|
|
139
|
+
* SP3-c/-d precise orbit and clock parser, with Lagrange interpolation
|
|
140
|
+
* for evaluating positions between the tabulated epochs.
|
|
141
|
+
*
|
|
142
|
+
* Positions are converted to meters (SP3 tabulates km), clocks to
|
|
143
|
+
* seconds (SP3 tabulates microseconds). Missing positions (all-zero
|
|
144
|
+
* coordinates) and the 999999.999999 bad-clock sentinel become null.
|
|
145
|
+
* Epoch tags are read in the file's own time scale (GPS for IGS/MGEX
|
|
146
|
+
* products) as epoch milliseconds, matching the convention of the
|
|
147
|
+
* RINEX parsers in this library.
|
|
148
|
+
*/
|
|
149
|
+
interface Sp3Sample {
|
|
150
|
+
/** ECEF meters. */
|
|
151
|
+
x: number;
|
|
152
|
+
y: number;
|
|
153
|
+
z: number;
|
|
154
|
+
/** Satellite clock offset in seconds, or null when flagged bad. */
|
|
155
|
+
clk: number | null;
|
|
156
|
+
}
|
|
157
|
+
interface Sp3File {
|
|
158
|
+
version: string;
|
|
159
|
+
/** Epoch timestamps (ms). */
|
|
160
|
+
epochs: number[];
|
|
161
|
+
/** Tabulated interval in seconds (from the ## header line). */
|
|
162
|
+
intervalSec: number;
|
|
163
|
+
/** Time system from the %c line, e.g. 'GPS'. */
|
|
164
|
+
timeSystem: string;
|
|
165
|
+
/** prn → per-epoch samples (null where the satellite is missing). */
|
|
166
|
+
satellites: Record<string, (Sp3Sample | null)[]>;
|
|
167
|
+
}
|
|
168
|
+
declare function parseSp3(text: string): Sp3File;
|
|
169
|
+
/**
|
|
170
|
+
* Satellite position at an arbitrary time by Lagrange interpolation
|
|
171
|
+
* over the `order` nearest tabulated samples (default 9, the standard
|
|
172
|
+
* choice for 5–15 min SP3 tables; centimetre-level between nodes).
|
|
173
|
+
* Clock is interpolated linearly between the bracketing samples.
|
|
174
|
+
*
|
|
175
|
+
* Returns null outside the covered span, near data gaps, or when the
|
|
176
|
+
* satellite is absent.
|
|
177
|
+
*/
|
|
178
|
+
declare function sp3Position(sp3: Sp3File, prn: string, tMs: number, order?: number): {
|
|
179
|
+
x: number;
|
|
180
|
+
y: number;
|
|
181
|
+
z: number;
|
|
182
|
+
clk: number | null;
|
|
183
|
+
} | null;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* RINEX 3.04 / 4.01 observation file writer.
|
|
187
|
+
*
|
|
188
|
+
* The two formats share the same header layout and epoch record
|
|
189
|
+
* encoding — only the version line and the provenance comment differ
|
|
190
|
+
* (see obs-writer.test.ts, which pins this). RINEX 2.11 lives in
|
|
191
|
+
* obs-writer-v2.ts (different header records and epoch layout).
|
|
192
|
+
*
|
|
193
|
+
* Streams text through gzip compression so we never hold the full
|
|
194
|
+
* uncompressed output in memory. Returns a gzip-compressed Blob.
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Compact epoch — stores observation values in Float64Arrays
|
|
199
|
+
* indexed by a per-system obs code registry (NaN = missing).
|
|
200
|
+
* ~10-15x less memory than Map-of-Maps.
|
|
201
|
+
*/
|
|
202
|
+
interface CompactEpoch {
|
|
203
|
+
time: number;
|
|
204
|
+
sats: Map<string, Float64Array>;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Write RINEX 3.04 obs file from compact observations.
|
|
208
|
+
*
|
|
209
|
+
* The `obsTypes` map defines both the RINEX header declaration order and
|
|
210
|
+
* the Float64Array index order for each system.
|
|
211
|
+
*/
|
|
212
|
+
declare function writeRinexObsBlob(header: RinexHeader, epochs: CompactEpoch[], obsTypes: Map<string, string[]>): Promise<Blob>;
|
|
213
|
+
/** Shared RINEX 3/4 implementation (also used by obs-writer-v4.ts). */
|
|
214
|
+
declare function writeModernObsBlob(header: RinexHeader, epochs: CompactEpoch[], obsTypes: Map<string, string[]>, opts: {
|
|
215
|
+
version: string;
|
|
216
|
+
comment: string;
|
|
217
|
+
}): Promise<Blob>;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* RINEX 2.11 observation file writer.
|
|
221
|
+
*
|
|
222
|
+
* Converts RINEX 3 obs codes to RINEX 2 format and writes
|
|
223
|
+
* a valid RINEX 2.11 observation file.
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Write RINEX 2.11 obs file from compact observations.
|
|
228
|
+
* Only GPS+GLONASS satellites are included (RINEX 2 limitation).
|
|
229
|
+
*/
|
|
230
|
+
declare function writeRinex2ObsBlob(header: RinexHeader, epochs: CompactEpoch[], obsTypes: Map<string, string[]>): Promise<Blob>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* RINEX 4.01 observation file writer.
|
|
234
|
+
*
|
|
235
|
+
* RINEX 4 is structurally identical to RINEX 3 for our output — same
|
|
236
|
+
* header layout, same epoch record format. Only the version line and
|
|
237
|
+
* the provenance comment differ, so this delegates to the shared
|
|
238
|
+
* implementation in obs-writer.ts.
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
/** Write RINEX 4.01 obs file from compact observations. */
|
|
242
|
+
declare function writeRinex4ObsBlob(header: RinexHeader, epochs: CompactEpoch[], obsTypes: Map<string, string[]>): Promise<Blob>;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Shared plumbing for the RINEX 2/3/4 observation writers.
|
|
246
|
+
*/
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Batched line sink that streams text through gzip compression so the
|
|
250
|
+
* full uncompressed output is never held in memory.
|
|
251
|
+
*
|
|
252
|
+
* Callers decide when to `flush()` (the writers flush every N epochs);
|
|
253
|
+
* `finish()` flushes, closes the stream, and returns the Blob.
|
|
254
|
+
*/
|
|
255
|
+
declare function createGzipLineSink(): {
|
|
256
|
+
push(line: string): void;
|
|
257
|
+
flush: () => Promise<void>;
|
|
258
|
+
finish(): Promise<Blob>;
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Station / receiver / antenna header block — byte-identical across
|
|
262
|
+
* RINEX 2.11, 3.04, and 4.01.
|
|
263
|
+
*/
|
|
264
|
+
declare function stationHeaderLines(header: RinexHeader): string[];
|
|
265
|
+
|
|
266
|
+
export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, type IonexGrid, NavResult, RinexHeader, type RinexWarning, type RinexWarnings, type Sp3File, type Sp3Sample, WarningAccumulator, type WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexObsBlob };
|