gnss-js 1.22.0 → 1.24.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.
@@ -1,262 +0,0 @@
1
- import {
2
- computeDop,
3
- computeSatPosition,
4
- ecefToAzEl
5
- } from "./chunk-JNFV5BYB.js";
6
- import {
7
- ecefToGeodetic
8
- } from "./chunk-37QNKGTC.js";
9
- import {
10
- C_LIGHT,
11
- OMEGA_E
12
- } from "./chunk-FIEWO4J4.js";
13
-
14
- // src/positioning/klobuchar.ts
15
- function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
16
- const { alpha, beta } = coeffs;
17
- if (alpha.length < 4 || beta.length < 4) return 0;
18
- const E = elRad / Math.PI;
19
- const phiU = latRad / Math.PI;
20
- const lambdaU = lonRad / Math.PI;
21
- const psi = 0.0137 / (E + 0.11) - 0.022;
22
- let phiI = phiU + psi * Math.cos(azRad);
23
- if (phiI > 0.416) phiI = 0.416;
24
- else if (phiI < -0.416) phiI = -0.416;
25
- const lambdaI = lambdaU + psi * Math.sin(azRad) / Math.cos(phiI * Math.PI);
26
- const phiM = phiI + 0.064 * Math.cos((lambdaI - 1.617) * Math.PI);
27
- let t = 43200 * lambdaI + gpsTowSec;
28
- t %= 86400;
29
- if (t < 0) t += 86400;
30
- let amp = 0;
31
- let per = 0;
32
- for (let n = 3; n >= 0; n--) {
33
- amp = amp * phiM + alpha[n];
34
- per = per * phiM + beta[n];
35
- }
36
- if (amp < 0) amp = 0;
37
- if (per < 72e3) per = 72e3;
38
- const F = 1 + 16 * Math.pow(0.53 - E, 3);
39
- const x = 2 * Math.PI * (t - 50400) / per;
40
- if (Math.abs(x) >= 1.57) return F * 5e-9;
41
- const x2 = x * x;
42
- return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
43
- }
44
-
45
- // src/positioning/index.ts
46
- var GM_GPS = 3986005e8;
47
- var F_REL = -4442807633e-19;
48
- function satClockCorrection(eph, tMs) {
49
- if (eph.system === "R" || eph.system === "S") {
50
- const dt2 = (tMs - eph.tocDate.getTime()) / 1e3;
51
- return eph.tauN + eph.gammaN * dt2;
52
- }
53
- const k = eph;
54
- const dt = (tMs - k.tocDate.getTime()) / 1e3;
55
- const poly = k.af0 + k.af1 * dt + k.af2 * dt * dt;
56
- const a = k.sqrtA * k.sqrtA;
57
- const n = Math.sqrt(GM_GPS / (a * a * a)) + k.deltaN;
58
- const tk = dt;
59
- const Mk = k.m0 + n * tk;
60
- let Ek = Mk;
61
- for (let i = 0; i < 8; i++) {
62
- Ek = Mk + k.e * Math.sin(Ek);
63
- }
64
- const rel = F_REL * k.e * k.sqrtA * Math.sin(Ek);
65
- return poly + rel;
66
- }
67
- function ionoFree(p1, p2, f1, f2) {
68
- const g = f1 * f1 / (f1 * f1 - f2 * f2);
69
- return g * p1 - (g - 1) * p2;
70
- }
71
- var GPS_EPOCH_MS_SPP = Date.UTC(1980, 0, 6);
72
- var PRIMARY_FREQ_HZ = {
73
- G: 157542e4,
74
- E: 157542e4,
75
- J: 157542e4,
76
- S: 157542e4,
77
- C: 1561098e3,
78
- R: 1602e6
79
- };
80
- var F_L1 = 157542e4;
81
- function tropoDelay(elevationRad) {
82
- const sinEl = Math.sin(elevationRad);
83
- return 2.47 / (sinEl + 0.0121);
84
- }
85
- function sagnac(pos, travelTimeS) {
86
- const theta = OMEGA_E * travelTimeS;
87
- const c = Math.cos(theta);
88
- const s = Math.sin(theta);
89
- return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
90
- }
91
- function solveLinear(A, b) {
92
- const n = b.length;
93
- const M = A.map((row, i) => [...row, b[i]]);
94
- for (let col = 0; col < n; col++) {
95
- let pivot = col;
96
- for (let r = col + 1; r < n; r++) {
97
- if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
98
- }
99
- if (Math.abs(M[pivot][col]) < 1e-12) return null;
100
- [M[col], M[pivot]] = [M[pivot], M[col]];
101
- for (let r = 0; r < n; r++) {
102
- if (r === col) continue;
103
- const f = M[r][col] / M[col][col];
104
- for (let c = col; c <= n; c++) M[r][c] -= f * M[col][c];
105
- }
106
- }
107
- return M.map((row, i) => row[n] / M[i][i]);
108
- }
109
- function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
110
- const {
111
- elevationMaskDeg = 10,
112
- troposphere = true,
113
- maxIterations = 15,
114
- convergenceM = 1e-4,
115
- tgd = true,
116
- iono
117
- } = opts;
118
- const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
119
- const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
120
- const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
121
- if (all.length < minSats(all)) return null;
122
- const gaussNewton = (prns) => {
123
- const systems = [...new Set(prns.map((p) => p[0]))].sort();
124
- const dim = 3 + systems.length;
125
- const sysIndex = new Map(systems.map((s, i) => [s, 3 + i]));
126
- let x2 = 0;
127
- let y2 = 0;
128
- let z2 = 0;
129
- const clock2 = new Map(systems.map((s) => [s, 0]));
130
- const residuals2 = {};
131
- let iterations2 = 0;
132
- let converged2 = false;
133
- for (let iter = 0; iter < maxIterations; iter++) {
134
- iterations2 = iter + 1;
135
- const HtH = Array.from(
136
- { length: dim },
137
- () => new Array(dim).fill(0)
138
- );
139
- const Htv = new Array(dim).fill(0);
140
- let rows = 0;
141
- for (const k of Object.keys(residuals2)) delete residuals2[k];
142
- const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
143
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
144
- for (const prn of prns) {
145
- const psr = pseudoranges.get(prn);
146
- const eph = ephemerides.get(prn);
147
- const sys = prn[0];
148
- const clk = clock2.get(sys);
149
- const tTx = timeMs - psr / C_LIGHT * 1e3;
150
- const dtsClock = satClockCorrection(eph, tTx);
151
- const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
152
- if (!Number.isFinite(sat.x)) continue;
153
- const isKepler = eph.system !== "R" && eph.system !== "S";
154
- const dts = dtsClock - (tgd && isKepler ? eph.tgd : 0);
155
- const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
156
- const [sx, sy, sz] = sagnac(sat, travel);
157
- const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
158
- const ux = (x2 - sx) / rho;
159
- const uy = (y2 - sy) / rho;
160
- const uz = (z2 - sz) / rho;
161
- let elev = Math.PI / 2;
162
- let azim = 0;
163
- let weight = 1;
164
- const positionSane = positionSaneIter;
165
- if (positionSane) {
166
- const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
167
- elev = azel.el;
168
- azim = azel.az;
169
- if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
170
- const sinEl = Math.max(Math.sin(elev), 0.1);
171
- weight = sinEl * sinEl;
172
- }
173
- const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
174
- let ionoM = 0;
175
- if (iono && positionSane) {
176
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
177
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
178
- }
179
- const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
180
- const v = psr - predicted;
181
- const h = new Array(dim).fill(0);
182
- h[0] = ux;
183
- h[1] = uy;
184
- h[2] = uz;
185
- h[sysIndex.get(sys)] = 1;
186
- for (let i = 0; i < dim; i++) {
187
- for (let j = 0; j < dim; j++) HtH[i][j] += weight * h[i] * h[j];
188
- Htv[i] += weight * h[i] * v;
189
- }
190
- residuals2[prn] = v;
191
- rows++;
192
- }
193
- if (rows < dim) return null;
194
- const dx = solveLinear(HtH, Htv);
195
- if (!dx) return null;
196
- x2 += dx[0];
197
- y2 += dx[1];
198
- z2 += dx[2];
199
- for (const s of systems)
200
- clock2.set(s, clock2.get(s) + dx[sysIndex.get(s)]);
201
- if (Math.hypot(dx[0], dx[1], dx[2]) < convergenceM) {
202
- converged2 = true;
203
- break;
204
- }
205
- }
206
- return { x: x2, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
207
- };
208
- const REJECT_THRESHOLD_M = 50;
209
- let candidates = all;
210
- const rejected = [];
211
- let inner = null;
212
- for (let round = 0; round <= all.length; round++) {
213
- inner = gaussNewton(candidates);
214
- if (!inner) return null;
215
- const vals = Object.entries(inner.residuals);
216
- let worst = null;
217
- let worstAbs = REJECT_THRESHOLD_M;
218
- for (const [prn, v] of vals) {
219
- if (Math.abs(v) > worstAbs) {
220
- worstAbs = Math.abs(v);
221
- worst = prn;
222
- }
223
- }
224
- if (!worst) break;
225
- const remaining = candidates.filter((p) => p !== worst);
226
- if (remaining.length < minSats(remaining)) break;
227
- rejected.push(worst);
228
- candidates = remaining;
229
- }
230
- if (!inner) return null;
231
- const { x, y, z, clock, residuals, iterations, converged } = inner;
232
- const used = [];
233
- const azels = [];
234
- for (const prn of candidates) {
235
- if (!(prn in residuals)) continue;
236
- const eph = ephemerides.get(prn);
237
- const sat = computeSatPosition(eph, timeMs);
238
- if (!Number.isFinite(sat.x)) continue;
239
- const azel = ecefToAzEl(x, y, z, sat.x, sat.y, sat.z);
240
- if (azel.el >= elevationMaskDeg * Math.PI / 180) {
241
- used.push(prn);
242
- azels.push(azel);
243
- }
244
- }
245
- return {
246
- position: [x, y, z],
247
- clockBias: Object.fromEntries(clock),
248
- usedSatellites: used,
249
- rejectedSatellites: rejected,
250
- residuals,
251
- dop: computeDop(azels),
252
- iterations,
253
- converged
254
- };
255
- }
256
-
257
- export {
258
- klobucharDelay,
259
- satClockCorrection,
260
- ionoFree,
261
- solveSpp
262
- };