gnss-js 1.29.0 → 1.31.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-6GTS5MVI.js → chunk-5RNDQQ6J.js} +81 -0
- package/dist/{chunk-5WHKMKIB.js → chunk-EVFD4UNZ.js} +98 -4
- package/dist/{chunk-4KMWWCUR.js → chunk-GPEP67TG.js} +63 -1
- package/dist/{sp3-BAq8l39b.d.ts → clk-DGVBGRzA.d.cts} +27 -1
- package/dist/{sp3-BAq8l39b.d.cts → clk-DGVBGRzA.d.ts} +27 -1
- package/dist/index.cjs +284 -43
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +20 -4
- package/dist/positioning.cjs +123 -5
- package/dist/positioning.d.cts +86 -2
- package/dist/positioning.d.ts +86 -2
- package/dist/positioning.js +10 -4
- package/dist/rinex.cjs +146 -0
- package/dist/rinex.d.cts +50 -2
- package/dist/rinex.d.ts +50 -2
- package/dist/rinex.js +12 -2
- package/package.json +1 -1
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
GPS_PI
|
|
7
7
|
} from "./chunk-IQ5OIMQD.js";
|
|
8
|
+
import {
|
|
9
|
+
C_LIGHT
|
|
10
|
+
} from "./chunk-FIEWO4J4.js";
|
|
8
11
|
|
|
9
12
|
// src/rinex/crx-writer.ts
|
|
10
13
|
var ARC_ORDER = 3;
|
|
@@ -1417,6 +1420,81 @@ function parseIonex(text) {
|
|
|
1417
1420
|
return { epochs, lats, lons, maps };
|
|
1418
1421
|
}
|
|
1419
1422
|
|
|
1423
|
+
// src/rinex/bias-sinex.ts
|
|
1424
|
+
function parseEpoch(s, openTo) {
|
|
1425
|
+
const m = /^(\d{4}):(\d{3}):(\d{5})$/.exec(s.trim());
|
|
1426
|
+
if (!m) return openTo;
|
|
1427
|
+
const y = +m[1];
|
|
1428
|
+
if (y === 0) return openTo;
|
|
1429
|
+
const doy = +m[2];
|
|
1430
|
+
const sod = +m[3];
|
|
1431
|
+
return Date.UTC(y, 0, 1) + (doy - 1) * 864e5 + sod * 1e3;
|
|
1432
|
+
}
|
|
1433
|
+
function parseBiasSinex(text) {
|
|
1434
|
+
const records = [];
|
|
1435
|
+
let inBlock = false;
|
|
1436
|
+
for (const line of text.split("\n")) {
|
|
1437
|
+
if (line.startsWith("+BIAS/SOLUTION")) {
|
|
1438
|
+
inBlock = true;
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
if (line.startsWith("-BIAS/SOLUTION")) {
|
|
1442
|
+
inBlock = false;
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (!inBlock || line.length < 65 || line.startsWith("*")) continue;
|
|
1446
|
+
const type = line.slice(1, 4).trim();
|
|
1447
|
+
if (type !== "DSB" && type !== "OSB" && type !== "ISB") continue;
|
|
1448
|
+
const prn = line.slice(11, 14).trim();
|
|
1449
|
+
const station = line.slice(15, 24).trim();
|
|
1450
|
+
const obs1 = line.slice(25, 29).trim();
|
|
1451
|
+
const obs2 = line.slice(30, 34).trim();
|
|
1452
|
+
const startMs = parseEpoch(line.slice(35, 49), -Infinity);
|
|
1453
|
+
const endMs = parseEpoch(line.slice(50, 64), Infinity);
|
|
1454
|
+
const tail = line.slice(65).trim().split(/\s+/);
|
|
1455
|
+
if (tail.length < 2) continue;
|
|
1456
|
+
const unit = tail[0];
|
|
1457
|
+
const value = Number(tail[1]);
|
|
1458
|
+
if (!Number.isFinite(value)) continue;
|
|
1459
|
+
records.push({
|
|
1460
|
+
type,
|
|
1461
|
+
prn,
|
|
1462
|
+
station,
|
|
1463
|
+
obs1,
|
|
1464
|
+
obs2,
|
|
1465
|
+
startMs,
|
|
1466
|
+
endMs,
|
|
1467
|
+
value,
|
|
1468
|
+
unit
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
const bySat = /* @__PURE__ */ new Map();
|
|
1472
|
+
for (const r of records) {
|
|
1473
|
+
if (!r.prn || r.station) continue;
|
|
1474
|
+
let a = bySat.get(r.prn);
|
|
1475
|
+
if (!a) {
|
|
1476
|
+
a = [];
|
|
1477
|
+
bySat.set(r.prn, a);
|
|
1478
|
+
}
|
|
1479
|
+
a.push(r);
|
|
1480
|
+
}
|
|
1481
|
+
return { records, bySat };
|
|
1482
|
+
}
|
|
1483
|
+
function biasMetres(r) {
|
|
1484
|
+
return r.unit === "ns" ? r.value * 1e-9 * C_LIGHT : r.value;
|
|
1485
|
+
}
|
|
1486
|
+
function findSatBias(bias, prn, obs1, obs2, tMs) {
|
|
1487
|
+
const recs = bias.bySat.get(prn);
|
|
1488
|
+
if (!recs) return null;
|
|
1489
|
+
for (const r of recs) {
|
|
1490
|
+
if (r.obs1 !== obs1) continue;
|
|
1491
|
+
if (obs2 === void 0 ? r.obs2 !== "" : r.obs2 !== obs2) continue;
|
|
1492
|
+
if (tMs != null && (tMs < r.startMs || tMs > r.endMs)) continue;
|
|
1493
|
+
return r;
|
|
1494
|
+
}
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1420
1498
|
// src/rinex/obs-writer-common.ts
|
|
1421
1499
|
function createGzipLineSink() {
|
|
1422
1500
|
const encoder = new TextEncoder();
|
|
@@ -1804,6 +1882,9 @@ export {
|
|
|
1804
1882
|
writeRinexNav,
|
|
1805
1883
|
writeRinexNav4,
|
|
1806
1884
|
parseIonex,
|
|
1885
|
+
parseBiasSinex,
|
|
1886
|
+
biasMetres,
|
|
1887
|
+
findSatBias,
|
|
1807
1888
|
createGzipLineSink,
|
|
1808
1889
|
stationHeaderLines,
|
|
1809
1890
|
writeRinexObsBlob,
|
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
getEnuDifference
|
|
11
11
|
} from "./chunk-37QNKGTC.js";
|
|
12
12
|
import {
|
|
13
|
+
clkBias,
|
|
13
14
|
sp3Position
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GPEP67TG.js";
|
|
15
16
|
import {
|
|
16
17
|
C_LIGHT,
|
|
17
18
|
OMEGA_E
|
|
@@ -1582,7 +1583,7 @@ function sagnacRotate(x, y, z, travelS) {
|
|
|
1582
1583
|
const s = Math.sin(theta);
|
|
1583
1584
|
return [x * c + y * s, -x * s + y * c, z];
|
|
1584
1585
|
}
|
|
1585
|
-
function satStateAtEmission(sp3, prn, recvTimeMs, rcv) {
|
|
1586
|
+
function satStateAtEmission(sp3, prn, recvTimeMs, rcv, clk) {
|
|
1586
1587
|
let travelS = 0.075;
|
|
1587
1588
|
let sx = 0;
|
|
1588
1589
|
let sy = 0;
|
|
@@ -1601,7 +1602,7 @@ function satStateAtEmission(sp3, prn, recvTimeMs, rcv) {
|
|
|
1601
1602
|
const vz = (pPlus.z - pMinus.z) / 1;
|
|
1602
1603
|
velDotPos = p.x * vx + p.y * vy + p.z * vz;
|
|
1603
1604
|
}
|
|
1604
|
-
clkS = p.clk;
|
|
1605
|
+
clkS = (clk && clkBias(clk, prn, tEmit)) ?? p.clk;
|
|
1605
1606
|
const [rx, ry, rz] = sagnacRotate(p.x, p.y, p.z, travelS);
|
|
1606
1607
|
sx = rx;
|
|
1607
1608
|
sy = ry;
|
|
@@ -1715,7 +1716,7 @@ function solvePpp(epochs, sp3, opts) {
|
|
|
1715
1716
|
for (const o of epoch.obs) {
|
|
1716
1717
|
if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
|
|
1717
1718
|
const slip = detectSlip(o, ei);
|
|
1718
|
-
const sat = satStateAtEmission(sp3, o.prn, epoch.timeMs, rcv);
|
|
1719
|
+
const sat = satStateAtEmission(sp3, o.prn, epoch.timeMs, rcv, opts.clk);
|
|
1719
1720
|
if (!sat) continue;
|
|
1720
1721
|
const [elRad, azRad] = getAer(
|
|
1721
1722
|
sat.state.x,
|
|
@@ -1910,6 +1911,96 @@ function solvePpp(epochs, sp3, opts) {
|
|
|
1910
1911
|
};
|
|
1911
1912
|
}
|
|
1912
1913
|
|
|
1914
|
+
// src/positioning/ppp-ar.ts
|
|
1915
|
+
function wlWavelength(f1, f2) {
|
|
1916
|
+
return C_LIGHT / (f1 - f2);
|
|
1917
|
+
}
|
|
1918
|
+
function nlWavelength(f1, f2) {
|
|
1919
|
+
return C_LIGHT / (f1 + f2);
|
|
1920
|
+
}
|
|
1921
|
+
function resolvePppAmbiguities(sats, Qaif, opts = {}) {
|
|
1922
|
+
const wlThreshold = opts.wlThreshold ?? 0.25;
|
|
1923
|
+
const ratioThreshold = opts.ratioThreshold ?? 3;
|
|
1924
|
+
const kept = [];
|
|
1925
|
+
const nWl = [];
|
|
1926
|
+
const rejectedWl = [];
|
|
1927
|
+
for (let i = 0; i < sats.length; i++) {
|
|
1928
|
+
const s = sats[i];
|
|
1929
|
+
const wlFloat = s.mwCyc - (s.wlBiasCyc ?? 0);
|
|
1930
|
+
const r = Math.round(wlFloat);
|
|
1931
|
+
if (Math.abs(wlFloat - r) <= wlThreshold) {
|
|
1932
|
+
kept.push(i);
|
|
1933
|
+
nWl.push(r);
|
|
1934
|
+
} else {
|
|
1935
|
+
rejectedWl.push(s.prn);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
if (kept.length < 2) {
|
|
1939
|
+
return {
|
|
1940
|
+
fixed: false,
|
|
1941
|
+
ratio: 0,
|
|
1942
|
+
refPrn: null,
|
|
1943
|
+
sats: [],
|
|
1944
|
+
rejectedWl
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
const n1Float = [];
|
|
1948
|
+
const lamNl = [];
|
|
1949
|
+
for (let k = 0; k < kept.length; k++) {
|
|
1950
|
+
const s = sats[kept[k]];
|
|
1951
|
+
const lNl = nlWavelength(s.f1, s.f2);
|
|
1952
|
+
lamNl.push(lNl);
|
|
1953
|
+
const n1 = s.aIF / lNl - s.f2 / (s.f1 - s.f2) * nWl[k] - (s.nlBiasCyc ?? 0);
|
|
1954
|
+
n1Float.push(n1);
|
|
1955
|
+
}
|
|
1956
|
+
let ref = 0;
|
|
1957
|
+
for (let k = 1; k < kept.length; k++) {
|
|
1958
|
+
const e = sats[kept[k]].elevDeg ?? -1;
|
|
1959
|
+
if (e > (sats[kept[ref]].elevDeg ?? -1)) ref = k;
|
|
1960
|
+
}
|
|
1961
|
+
const m = kept.length - 1;
|
|
1962
|
+
const sd = new Float64Array(m);
|
|
1963
|
+
const Qsd = new Float64Array(m * m);
|
|
1964
|
+
const others = [];
|
|
1965
|
+
for (let k = 0; k < kept.length; k++) if (k !== ref) others.push(k);
|
|
1966
|
+
const n = sats.length;
|
|
1967
|
+
const qN1 = (ka, kb) => Qaif[kept[ka] * n + kept[kb]] / (lamNl[ka] * lamNl[kb]);
|
|
1968
|
+
for (let a = 0; a < m; a++) {
|
|
1969
|
+
const ka = others[a];
|
|
1970
|
+
sd[a] = n1Float[ka] - n1Float[ref];
|
|
1971
|
+
for (let b = 0; b < m; b++) {
|
|
1972
|
+
const kb = others[b];
|
|
1973
|
+
Qsd[a * m + b] = qN1(ka, kb) - qN1(ka, ref) - qN1(ref, kb) + qN1(ref, ref);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
const res = lambdaSearch(sd, Qsd, m, 2);
|
|
1977
|
+
const ratio = res ? Math.min(res.ratio, 999.9) : 0;
|
|
1978
|
+
const fixed = !!res && ratio >= ratioThreshold;
|
|
1979
|
+
const refN1 = Math.round(n1Float[ref]);
|
|
1980
|
+
const sdFix = res?.candidates[0];
|
|
1981
|
+
const outSats = [];
|
|
1982
|
+
for (let k = 0; k < kept.length; k++) {
|
|
1983
|
+
const s = sats[kept[k]];
|
|
1984
|
+
let n1;
|
|
1985
|
+
if (k === ref) {
|
|
1986
|
+
n1 = refN1;
|
|
1987
|
+
} else {
|
|
1988
|
+
const a = others.indexOf(k);
|
|
1989
|
+
n1 = fixed && sdFix ? refN1 + Math.round(sdFix[a]) : Math.round(n1Float[k]);
|
|
1990
|
+
}
|
|
1991
|
+
const lNl = lamNl[k];
|
|
1992
|
+
const aIF = lNl * n1 + lNl * (s.f2 / (s.f1 - s.f2)) * nWl[k];
|
|
1993
|
+
outSats.push({ prn: s.prn, nWl: nWl[k], n1, aIF });
|
|
1994
|
+
}
|
|
1995
|
+
return {
|
|
1996
|
+
fixed,
|
|
1997
|
+
ratio,
|
|
1998
|
+
refPrn: sats[kept[ref]].prn,
|
|
1999
|
+
sats: outSats,
|
|
2000
|
+
rejectedWl
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1913
2004
|
// src/positioning/index.ts
|
|
1914
2005
|
var GM_GPS = 3986005e8;
|
|
1915
2006
|
var F_REL = -4442807633e-19;
|
|
@@ -2156,6 +2247,9 @@ export {
|
|
|
2156
2247
|
buildPppAntenna,
|
|
2157
2248
|
createPppCorrections,
|
|
2158
2249
|
solvePpp,
|
|
2250
|
+
wlWavelength,
|
|
2251
|
+
nlWavelength,
|
|
2252
|
+
resolvePppAmbiguities,
|
|
2159
2253
|
satClockCorrection,
|
|
2160
2254
|
ionoFree,
|
|
2161
2255
|
solveSpp
|
|
@@ -114,7 +114,69 @@ function sp3Position(sp3, prn, tMs, order = 9) {
|
|
|
114
114
|
return { x, y, z, clk };
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// src/rinex/clk.ts
|
|
118
|
+
var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
|
|
119
|
+
function parseClk(text) {
|
|
120
|
+
const sats = {};
|
|
121
|
+
let inData = false;
|
|
122
|
+
for (const line of text.split("\n")) {
|
|
123
|
+
if (!inData) {
|
|
124
|
+
if (line.includes("END OF HEADER")) inData = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (line.length < 3 || line[0] !== "A" || line[1] !== "S") continue;
|
|
128
|
+
const p = line.slice(2).trim().split(/\s+/);
|
|
129
|
+
if (p.length < 9) continue;
|
|
130
|
+
const prn = p[0];
|
|
131
|
+
const y = +p[1];
|
|
132
|
+
const mo = +p[2];
|
|
133
|
+
const d = +p[3];
|
|
134
|
+
const h = +p[4];
|
|
135
|
+
const mi = +p[5];
|
|
136
|
+
const s = +p[6];
|
|
137
|
+
const bias = +p[8];
|
|
138
|
+
if (!Number.isFinite(bias)) continue;
|
|
139
|
+
const t = Date.UTC(y, mo - 1, d, h, mi, Math.floor(s)) + s % 1 * 1e3;
|
|
140
|
+
let e = sats[prn];
|
|
141
|
+
if (!e) {
|
|
142
|
+
e = { t: [], c: [] };
|
|
143
|
+
sats[prn] = e;
|
|
144
|
+
}
|
|
145
|
+
e.t.push(t);
|
|
146
|
+
e.c.push(bias);
|
|
147
|
+
}
|
|
148
|
+
let intervalSec = 30;
|
|
149
|
+
for (const e of Object.values(sats)) {
|
|
150
|
+
if (e.t.length >= 2) {
|
|
151
|
+
intervalSec = Math.round((e.t[1] - e.t[0]) / 1e3);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { sats, intervalSec };
|
|
156
|
+
}
|
|
157
|
+
function clkBias(clk, prn, tMs) {
|
|
158
|
+
const e = clk.sats[prn];
|
|
159
|
+
if (!e || e.t.length === 0) return null;
|
|
160
|
+
const { t, c } = e;
|
|
161
|
+
let lo = 0;
|
|
162
|
+
let hi = t.length - 1;
|
|
163
|
+
if (tMs < t[0] || tMs > t[hi]) return null;
|
|
164
|
+
while (lo < hi) {
|
|
165
|
+
const mid = lo + hi + 1 >> 1;
|
|
166
|
+
if (t[mid] <= tMs) lo = mid;
|
|
167
|
+
else hi = mid - 1;
|
|
168
|
+
}
|
|
169
|
+
if (t[lo] === tMs || lo === t.length - 1) return c[lo];
|
|
170
|
+
const t0 = t[lo];
|
|
171
|
+
const t1 = t[lo + 1];
|
|
172
|
+
if (t1 - t0 > clk.intervalSec * 1e3 * 4) return null;
|
|
173
|
+
const f = (tMs - t0) / (t1 - t0);
|
|
174
|
+
return c[lo] + f * (c[lo + 1] - c[lo]);
|
|
175
|
+
}
|
|
176
|
+
|
|
117
177
|
export {
|
|
118
178
|
parseSp3,
|
|
119
|
-
sp3Position
|
|
179
|
+
sp3Position,
|
|
180
|
+
parseClk,
|
|
181
|
+
clkBias
|
|
120
182
|
};
|
|
@@ -67,4 +67,30 @@ declare function sp3Position(sp3: Sp3File, prn: string, tMs: number, order?: num
|
|
|
67
67
|
clk: number | null;
|
|
68
68
|
} | null;
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* RINEX CLOCK (.CLK) parser — high-rate (typically 30 s) precise satellite
|
|
72
|
+
* clock corrections, the accuracy-limiting term for PPP that SP3's 5-minute
|
|
73
|
+
* (linearly interpolated) clocks cannot match.
|
|
74
|
+
*
|
|
75
|
+
* Only `AS` (satellite) records are kept; `AR` (station) records — the bulk
|
|
76
|
+
* of a combined file — are skipped. Clock bias is in seconds.
|
|
77
|
+
*/
|
|
78
|
+
interface ClkFile {
|
|
79
|
+
/** prn → time-sorted samples { t: GPS-scale ms, c: clock bias (s) }. */
|
|
80
|
+
sats: Record<string, {
|
|
81
|
+
t: number[];
|
|
82
|
+
c: number[];
|
|
83
|
+
}>;
|
|
84
|
+
/** Nominal sampling interval (s), inferred from the first satellite. */
|
|
85
|
+
intervalSec: number;
|
|
86
|
+
}
|
|
87
|
+
/** Parse a RINEX CLOCK file. */
|
|
88
|
+
declare function parseClk(text: string): ClkFile;
|
|
89
|
+
/**
|
|
90
|
+
* Satellite clock bias (seconds) at an arbitrary time by linear
|
|
91
|
+
* interpolation between the bracketing samples. Returns null outside the
|
|
92
|
+
* span or across a gap larger than 4 sampling intervals.
|
|
93
|
+
*/
|
|
94
|
+
declare function clkBias(clk: ClkFile, prn: string, tMs: number): number | null;
|
|
95
|
+
|
|
96
|
+
export { type ClkFile as C, type IonexGrid as I, type Sp3File as S, type Sp3Sample as a, parseIonex as b, clkBias as c, parseSp3 as d, parseClk as p, sp3Position as s };
|
|
@@ -67,4 +67,30 @@ declare function sp3Position(sp3: Sp3File, prn: string, tMs: number, order?: num
|
|
|
67
67
|
clk: number | null;
|
|
68
68
|
} | null;
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* RINEX CLOCK (.CLK) parser — high-rate (typically 30 s) precise satellite
|
|
72
|
+
* clock corrections, the accuracy-limiting term for PPP that SP3's 5-minute
|
|
73
|
+
* (linearly interpolated) clocks cannot match.
|
|
74
|
+
*
|
|
75
|
+
* Only `AS` (satellite) records are kept; `AR` (station) records — the bulk
|
|
76
|
+
* of a combined file — are skipped. Clock bias is in seconds.
|
|
77
|
+
*/
|
|
78
|
+
interface ClkFile {
|
|
79
|
+
/** prn → time-sorted samples { t: GPS-scale ms, c: clock bias (s) }. */
|
|
80
|
+
sats: Record<string, {
|
|
81
|
+
t: number[];
|
|
82
|
+
c: number[];
|
|
83
|
+
}>;
|
|
84
|
+
/** Nominal sampling interval (s), inferred from the first satellite. */
|
|
85
|
+
intervalSec: number;
|
|
86
|
+
}
|
|
87
|
+
/** Parse a RINEX CLOCK file. */
|
|
88
|
+
declare function parseClk(text: string): ClkFile;
|
|
89
|
+
/**
|
|
90
|
+
* Satellite clock bias (seconds) at an arbitrary time by linear
|
|
91
|
+
* interpolation between the bracketing samples. Returns null outside the
|
|
92
|
+
* span or across a gap larger than 4 sampling intervals.
|
|
93
|
+
*/
|
|
94
|
+
declare function clkBias(clk: ClkFile, prn: string, tMs: number): number | null;
|
|
95
|
+
|
|
96
|
+
export { type ClkFile as C, type IonexGrid as I, type Sp3File as S, type Sp3Sample as a, parseIonex as b, clkBias as c, parseSp3 as d, parseClk as p, sp3Position as s };
|