gnss-js 0.1.2 → 1.0.1
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/LICENSE +23 -0
- package/README.md +171 -5
- package/dist/analysis.cjs +1488 -0
- package/dist/analysis.d.cts +211 -0
- package/dist/analysis.d.ts +211 -0
- package/dist/analysis.js +14 -0
- package/dist/antex.cjs +239 -0
- package/dist/antex.d.cts +77 -0
- package/dist/antex.d.ts +77 -0
- package/dist/antex.js +8 -0
- package/dist/chunk-37QNKGTC.js +65 -0
- package/dist/chunk-4YN353Q7.js +440 -0
- package/dist/chunk-5CMSYIYM.js +214 -0
- package/dist/chunk-5S5MONFK.js +0 -0
- package/dist/chunk-6FAL6P4G.js +12 -0
- package/dist/chunk-7NOFXKET.js +433 -0
- package/dist/chunk-BJHTBYNG.js +154 -0
- package/dist/chunk-HBLU2EJ4.js +802 -0
- package/dist/chunk-HKN3PUGN.js +800 -0
- package/dist/chunk-LEEU5OIO.js +75 -0
- package/dist/chunk-LWNTWBHB.js +268 -0
- package/dist/chunk-MIIM4LDY.js +239 -0
- package/dist/chunk-SO3POWWR.js +502 -0
- package/dist/chunk-W4YMQKWH.js +622 -0
- package/dist/chunk-WP2JFDLA.js +1475 -0
- package/dist/chunk-YDMYO3YK.js +220 -0
- package/dist/constants.cjs +423 -0
- package/dist/constants.d.cts +14 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +103 -0
- package/dist/coordinates.cjs +337 -0
- package/dist/coordinates.d.cts +81 -0
- package/dist/coordinates.d.ts +81 -0
- package/dist/coordinates.js +39 -0
- package/dist/ecef-CF0uAysr.d.cts +40 -0
- package/dist/ecef-CF0uAysr.d.ts +40 -0
- package/dist/ephemeris-C10stHhM.d.cts +114 -0
- package/dist/ephemeris-C10stHhM.d.ts +114 -0
- package/dist/gnss-BT6ulR17.d.cts +56 -0
- package/dist/gnss-C-tgoYNa.d.ts +56 -0
- package/dist/index.cjs +5940 -78
- package/dist/index.d.cts +17 -96
- package/dist/index.d.ts +17 -96
- package/dist/index.js +352 -415
- package/dist/nav-BAI1a9vK.d.cts +108 -0
- package/dist/nav-BAI1a9vK.d.ts +108 -0
- package/dist/nmea.cjs +247 -0
- package/dist/nmea.d.cts +37 -0
- package/dist/nmea.d.ts +37 -0
- package/dist/nmea.js +12 -0
- package/dist/ntrip.cjs +180 -0
- package/dist/ntrip.d.cts +98 -0
- package/dist/ntrip.d.ts +98 -0
- package/dist/ntrip.js +10 -0
- package/dist/orbit.cjs +511 -0
- package/dist/orbit.d.cts +117 -0
- package/dist/orbit.d.ts +117 -0
- package/dist/orbit.js +29 -0
- package/dist/parser-JPjjFgeP.d.cts +73 -0
- package/dist/parser-JPjjFgeP.d.ts +73 -0
- package/dist/rinex.cjs +1469 -0
- package/dist/rinex.d.cts +116 -0
- package/dist/rinex.d.ts +116 -0
- package/dist/rinex.js +37 -0
- package/dist/rtcm3.cjs +1512 -0
- package/dist/rtcm3.d.cts +143 -0
- package/dist/rtcm3.d.ts +143 -0
- package/dist/rtcm3.js +31 -0
- package/dist/signals.cjs +858 -0
- package/dist/signals.d.cts +93 -0
- package/dist/signals.d.ts +93 -0
- package/dist/signals.js +70 -0
- package/dist/time-DnI1VpE8.d.cts +33 -0
- package/dist/time-DnI1VpE8.d.ts +33 -0
- package/dist/time.cjs +585 -0
- package/dist/time.d.cts +307 -0
- package/dist/time.d.ts +307 -0
- package/dist/time.js +154 -0
- package/package.json +112 -14
- package/.prettierrc +0 -5
- package/jest.config.ts +0 -9
- package/test/functions.test.ts +0 -63
- package/test/gnss-scales.test.ts +0 -95
- package/test/julian.test.ts +0 -145
- package/test/rinex.test.ts +0 -11
- package/test/test.ts +0 -10
- package/test/utc.test.ts +0 -25
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseRinexStream
|
|
3
|
+
} from "./chunk-HBLU2EJ4.js";
|
|
4
|
+
import {
|
|
5
|
+
ARC_GAP_FACTOR,
|
|
6
|
+
BAND_LABELS,
|
|
7
|
+
C_LIGHT,
|
|
8
|
+
DUAL_FREQ_PAIRS,
|
|
9
|
+
FREQ,
|
|
10
|
+
SYSTEM_NAMES,
|
|
11
|
+
buildGloChannelMap,
|
|
12
|
+
buildObsIndices,
|
|
13
|
+
getFreq
|
|
14
|
+
} from "./chunk-LWNTWBHB.js";
|
|
15
|
+
|
|
16
|
+
// src/analysis/multipath.ts
|
|
17
|
+
var MIN_ARC_LENGTH = 10;
|
|
18
|
+
var MultipathAccumulator = class {
|
|
19
|
+
state = /* @__PURE__ */ new Map();
|
|
20
|
+
results = [];
|
|
21
|
+
interval;
|
|
22
|
+
obsIndices;
|
|
23
|
+
gloChannels;
|
|
24
|
+
constructor(header) {
|
|
25
|
+
this.interval = header.interval ?? 30;
|
|
26
|
+
this.obsIndices = buildObsIndices(header);
|
|
27
|
+
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
28
|
+
}
|
|
29
|
+
/** Observation callback — wire this into parseRinexStream. */
|
|
30
|
+
onObservation = (time, prn, _codes, values) => {
|
|
31
|
+
const sys = prn[0];
|
|
32
|
+
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
33
|
+
if (!bandMap) return;
|
|
34
|
+
const bandData = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const [band, { C, L }] of bandMap) {
|
|
36
|
+
if (C === null) continue;
|
|
37
|
+
const cVal = values[C];
|
|
38
|
+
const lVal = values[L];
|
|
39
|
+
const freq = getFreq(this.gloChannels, prn, band);
|
|
40
|
+
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
41
|
+
bandData.set(band, { C: cVal, L: lVal, f: freq });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (bandData.size < 2) return;
|
|
45
|
+
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
46
|
+
for (const [bi, bj] of pairs) {
|
|
47
|
+
const di = bandData.get(bi);
|
|
48
|
+
const dj = bandData.get(bj);
|
|
49
|
+
if (!di || !dj) continue;
|
|
50
|
+
const \u03BBi = C_LIGHT / di.f;
|
|
51
|
+
const \u03BBj = C_LIGHT / dj.f;
|
|
52
|
+
const \u03B1 = di.f * di.f / (dj.f * dj.f);
|
|
53
|
+
const coeff = 2 / (\u03B1 - 1);
|
|
54
|
+
const mp = di.C - (1 + coeff) * di.L * \u03BBi + coeff * dj.L * \u03BBj;
|
|
55
|
+
this.pushMp(prn, bi, bj, time, mp);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
pushMp(prn, band, refBand, time, mp) {
|
|
59
|
+
if (!isFinite(mp)) return;
|
|
60
|
+
const pairKey = `${band}-${refBand}`;
|
|
61
|
+
let satStates = this.state.get(prn);
|
|
62
|
+
if (!satStates) {
|
|
63
|
+
satStates = /* @__PURE__ */ new Map();
|
|
64
|
+
this.state.set(prn, satStates);
|
|
65
|
+
}
|
|
66
|
+
let bandState = satStates.get(pairKey);
|
|
67
|
+
if (!bandState) {
|
|
68
|
+
bandState = { arc: { times: [], rawMp: [] }, lastTime: 0 };
|
|
69
|
+
satStates.set(pairKey, bandState);
|
|
70
|
+
}
|
|
71
|
+
const gap = bandState.lastTime > 0 ? (time - bandState.lastTime) / 1e3 : 0;
|
|
72
|
+
if (bandState.lastTime > 0 && gap > this.interval * ARC_GAP_FACTOR) {
|
|
73
|
+
this.closeArc(prn, band, refBand, bandState);
|
|
74
|
+
}
|
|
75
|
+
bandState.arc.times.push(time);
|
|
76
|
+
bandState.arc.rawMp.push(mp);
|
|
77
|
+
bandState.lastTime = time;
|
|
78
|
+
}
|
|
79
|
+
/** External cycle-slip notification — close affected arcs for this PRN. */
|
|
80
|
+
notifySlip(_time, prn, bands) {
|
|
81
|
+
const satStates = this.state.get(prn);
|
|
82
|
+
if (!satStates) return;
|
|
83
|
+
for (const [pairKey, bandState] of satStates) {
|
|
84
|
+
const [band, refBand] = pairKey.split("-");
|
|
85
|
+
if (bands.has(band) || bands.has(refBand)) {
|
|
86
|
+
this.closeArc(prn, band, refBand, bandState);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
closeArc(prn, band, refBand, state) {
|
|
91
|
+
const arc = state.arc;
|
|
92
|
+
if (arc.times.length >= MIN_ARC_LENGTH) {
|
|
93
|
+
const mean = arc.rawMp.reduce((a, b) => a + b, 0) / arc.rawMp.length;
|
|
94
|
+
const sys = prn[0];
|
|
95
|
+
const bLabel = BAND_LABELS[sys]?.[band] ?? band;
|
|
96
|
+
const rLabel = BAND_LABELS[sys]?.[refBand] ?? refBand;
|
|
97
|
+
const label = `${prn} MP ${bLabel}-${rLabel}`;
|
|
98
|
+
const points = arc.times.map((t, i) => ({
|
|
99
|
+
time: t,
|
|
100
|
+
mp: arc.rawMp[i] - mean
|
|
101
|
+
}));
|
|
102
|
+
const rms = Math.sqrt(
|
|
103
|
+
points.reduce((s, p) => s + p.mp * p.mp, 0) / points.length
|
|
104
|
+
);
|
|
105
|
+
this.results.push({
|
|
106
|
+
prn,
|
|
107
|
+
system: sys,
|
|
108
|
+
band,
|
|
109
|
+
refBand,
|
|
110
|
+
label,
|
|
111
|
+
points,
|
|
112
|
+
rms
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
state.arc = { times: [], rawMp: [] };
|
|
116
|
+
}
|
|
117
|
+
/** Finalize: close remaining arcs and compute statistics. */
|
|
118
|
+
finalize() {
|
|
119
|
+
for (const [prn, satStates] of this.state) {
|
|
120
|
+
for (const [pairKey, bandState] of satStates) {
|
|
121
|
+
const [band, refBand] = pairKey.split("-");
|
|
122
|
+
this.closeArc(prn, band, refBand, bandState);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const signalGroups = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const series of this.results) {
|
|
127
|
+
const key = `${series.system}-${series.band}-${series.refBand}`;
|
|
128
|
+
if (!signalGroups.has(key)) signalGroups.set(key, []);
|
|
129
|
+
signalGroups.get(key).push(series);
|
|
130
|
+
}
|
|
131
|
+
const signalStats = [];
|
|
132
|
+
for (const [, seriesList] of signalGroups) {
|
|
133
|
+
const { system, band, refBand } = seriesList[0];
|
|
134
|
+
let sumSq = 0;
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (const s of seriesList) {
|
|
137
|
+
for (const p of s.points) {
|
|
138
|
+
sumSq += p.mp * p.mp;
|
|
139
|
+
count++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const rms = count > 0 ? Math.sqrt(sumSq / count) : 0;
|
|
143
|
+
const bLabel = BAND_LABELS[system]?.[band] ?? band;
|
|
144
|
+
const rLabel = BAND_LABELS[system]?.[refBand] ?? refBand;
|
|
145
|
+
const sysName = SYSTEM_NAMES[system] ?? system;
|
|
146
|
+
signalStats.push({
|
|
147
|
+
label: `MP ${bLabel}-${rLabel} (${sysName})`,
|
|
148
|
+
system,
|
|
149
|
+
band,
|
|
150
|
+
refBand,
|
|
151
|
+
rms,
|
|
152
|
+
count,
|
|
153
|
+
satellites: seriesList.length
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const sysOrder = "GRECIJS";
|
|
157
|
+
signalStats.sort((a, b) => {
|
|
158
|
+
const da = sysOrder.indexOf(a.system);
|
|
159
|
+
const db = sysOrder.indexOf(b.system);
|
|
160
|
+
if (da !== db) return (da === -1 ? 99 : da) - (db === -1 ? 99 : db);
|
|
161
|
+
return a.band.localeCompare(b.band);
|
|
162
|
+
});
|
|
163
|
+
return { series: this.results, signalStats };
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/analysis/cycle-slip.ts
|
|
168
|
+
var MW_MIN_THRESHOLD = 0.5;
|
|
169
|
+
var MW_SIGMA_FACTOR = 4;
|
|
170
|
+
var MW_MIN_EPOCHS = 3;
|
|
171
|
+
var GF_THRESHOLD_M = 0.15;
|
|
172
|
+
var SF_THRESHOLD_M = 3;
|
|
173
|
+
function mwSmoothInit(value) {
|
|
174
|
+
return { mean: value, m2: 0, n: 1 };
|
|
175
|
+
}
|
|
176
|
+
function mwSmoothUpdate(s, value) {
|
|
177
|
+
const n = s.n + 1;
|
|
178
|
+
const delta = value - s.mean;
|
|
179
|
+
const mean = s.mean + delta / n;
|
|
180
|
+
const delta2 = value - mean;
|
|
181
|
+
return { mean, m2: s.m2 + delta * delta2, n };
|
|
182
|
+
}
|
|
183
|
+
function mwSmoothStddev(s) {
|
|
184
|
+
return s.n > 1 ? Math.sqrt(s.m2 / (s.n - 1)) : Infinity;
|
|
185
|
+
}
|
|
186
|
+
var CycleSlipAccumulator = class {
|
|
187
|
+
obsIndices;
|
|
188
|
+
prev = /* @__PURE__ */ new Map();
|
|
189
|
+
events = [];
|
|
190
|
+
interval;
|
|
191
|
+
/** GLONASS PRN → channel k for FDMA frequency computation. */
|
|
192
|
+
gloChannels;
|
|
193
|
+
onSlip;
|
|
194
|
+
// Per-signal epoch/slip counting
|
|
195
|
+
signalEpochs = /* @__PURE__ */ new Map();
|
|
196
|
+
signalSlips = /* @__PURE__ */ new Map();
|
|
197
|
+
signalSats = /* @__PURE__ */ new Map();
|
|
198
|
+
constructor(header, onSlip) {
|
|
199
|
+
this.interval = header.interval ?? 30;
|
|
200
|
+
this.obsIndices = buildObsIndices(header);
|
|
201
|
+
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
202
|
+
this.onSlip = onSlip;
|
|
203
|
+
}
|
|
204
|
+
recordSlip(sigKey, time, prn, signal, magnitude, bands) {
|
|
205
|
+
this.events.push({ time, prn, signal, magnitude });
|
|
206
|
+
this.signalSlips.set(sigKey, (this.signalSlips.get(sigKey) ?? 0) + 1);
|
|
207
|
+
if (!this.signalSats.has(sigKey)) this.signalSats.set(sigKey, /* @__PURE__ */ new Set());
|
|
208
|
+
this.signalSats.get(sigKey).add(prn);
|
|
209
|
+
this.onSlip?.(time, prn, bands);
|
|
210
|
+
}
|
|
211
|
+
countEpoch(sigKey) {
|
|
212
|
+
this.signalEpochs.set(sigKey, (this.signalEpochs.get(sigKey) ?? 0) + 1);
|
|
213
|
+
}
|
|
214
|
+
/** Resolve frequency for a given PRN + band. Handles GLONASS FDMA. */
|
|
215
|
+
getFreqForPrn(prn, band) {
|
|
216
|
+
return getFreq(this.gloChannels, prn, band);
|
|
217
|
+
}
|
|
218
|
+
onObservation = (time, prn, _codes, values) => {
|
|
219
|
+
const sys = prn[0];
|
|
220
|
+
const idxMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
221
|
+
if (!idxMap) return;
|
|
222
|
+
if (sys !== "R" && !FREQ[sys]) return;
|
|
223
|
+
const phaseM = /* @__PURE__ */ new Map();
|
|
224
|
+
const codeM = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const [band, { L, C }] of idxMap) {
|
|
226
|
+
const freq = this.getFreqForPrn(prn, band);
|
|
227
|
+
if (!freq) continue;
|
|
228
|
+
const lVal = values[L];
|
|
229
|
+
if (lVal != null && lVal !== 0) phaseM.set(band, lVal * (C_LIGHT / freq));
|
|
230
|
+
if (C !== null) {
|
|
231
|
+
const cVal = values[C];
|
|
232
|
+
if (cVal != null && cVal !== 0) codeM.set(band, cVal);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (phaseM.size === 0) return;
|
|
236
|
+
const prev = this.prev.get(prn);
|
|
237
|
+
const gap = prev ? (time - prev.time) / 1e3 : 0;
|
|
238
|
+
const isArcBreak = !prev || gap > this.interval * ARC_GAP_FACTOR;
|
|
239
|
+
const mwSmooth = /* @__PURE__ */ new Map();
|
|
240
|
+
const gfMap = /* @__PURE__ */ new Map();
|
|
241
|
+
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
242
|
+
for (const [b1, b2] of pairs) {
|
|
243
|
+
const l1 = phaseM.get(b1);
|
|
244
|
+
const l2 = phaseM.get(b2);
|
|
245
|
+
const f1 = this.getFreqForPrn(prn, b1);
|
|
246
|
+
const f2 = this.getFreqForPrn(prn, b2);
|
|
247
|
+
if (!f1 || !f2) continue;
|
|
248
|
+
const pairKey = `${b1}-${b2}`;
|
|
249
|
+
if (l1 !== void 0 && l2 !== void 0) {
|
|
250
|
+
gfMap.set(pairKey, l1 - l2);
|
|
251
|
+
}
|
|
252
|
+
const c1 = codeM.get(b1);
|
|
253
|
+
const c2 = codeM.get(b2);
|
|
254
|
+
if (l1 !== void 0 && l2 !== void 0 && c1 !== void 0 && c2 !== void 0) {
|
|
255
|
+
const lambda_wl = C_LIGHT / (f1 - f2);
|
|
256
|
+
const phi_wl = (f1 * l1 - f2 * l2) / C_LIGHT;
|
|
257
|
+
const P_nl = (f1 * c1 + f2 * c2) / ((f1 + f2) * lambda_wl);
|
|
258
|
+
const mw = phi_wl - P_nl;
|
|
259
|
+
const prevSmooth = isArcBreak ? void 0 : prev?.mwSmooth.get(pairKey);
|
|
260
|
+
if (prevSmooth && prevSmooth.n >= MW_MIN_EPOCHS) {
|
|
261
|
+
const lbl1 = BAND_LABELS[sys]?.[b1] ?? b1;
|
|
262
|
+
const lbl2 = BAND_LABELS[sys]?.[b2] ?? b2;
|
|
263
|
+
const signal = `MW ${lbl1}-${lbl2}`;
|
|
264
|
+
const sigKey = `${sys}:${signal}`;
|
|
265
|
+
this.countEpoch(sigKey);
|
|
266
|
+
const stddev = mwSmoothStddev(prevSmooth);
|
|
267
|
+
const threshold = Math.max(
|
|
268
|
+
MW_MIN_THRESHOLD,
|
|
269
|
+
MW_SIGMA_FACTOR * stddev
|
|
270
|
+
);
|
|
271
|
+
const deviation = Math.abs(mw - prevSmooth.mean);
|
|
272
|
+
if (deviation > threshold) {
|
|
273
|
+
this.recordSlip(
|
|
274
|
+
sigKey,
|
|
275
|
+
time,
|
|
276
|
+
prn,
|
|
277
|
+
signal,
|
|
278
|
+
deviation,
|
|
279
|
+
/* @__PURE__ */ new Set([b1, b2])
|
|
280
|
+
);
|
|
281
|
+
mwSmooth.set(pairKey, mwSmoothInit(mw));
|
|
282
|
+
} else {
|
|
283
|
+
mwSmooth.set(pairKey, mwSmoothUpdate(prevSmooth, mw));
|
|
284
|
+
}
|
|
285
|
+
} else if (prevSmooth) {
|
|
286
|
+
mwSmooth.set(pairKey, mwSmoothUpdate(prevSmooth, mw));
|
|
287
|
+
} else {
|
|
288
|
+
mwSmooth.set(pairKey, mwSmoothInit(mw));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (!isArcBreak && prev) {
|
|
293
|
+
const dualChecked = /* @__PURE__ */ new Set();
|
|
294
|
+
for (const [b1, b2] of pairs) {
|
|
295
|
+
const pairKey = `${b1}-${b2}`;
|
|
296
|
+
const lbl1 = BAND_LABELS[sys]?.[b1] ?? b1;
|
|
297
|
+
const lbl2 = BAND_LABELS[sys]?.[b2] ?? b2;
|
|
298
|
+
if (mwSmooth.has(pairKey)) {
|
|
299
|
+
dualChecked.add(b1);
|
|
300
|
+
dualChecked.add(b2);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const curGf = gfMap.get(pairKey);
|
|
304
|
+
const prevGf = prev.gf.get(pairKey);
|
|
305
|
+
if (curGf !== void 0 && prevGf !== void 0) {
|
|
306
|
+
const signal = `GF ${lbl1}-${lbl2}`;
|
|
307
|
+
const sigKey = `${sys}:${signal}`;
|
|
308
|
+
this.countEpoch(sigKey);
|
|
309
|
+
const jump = Math.abs(curGf - prevGf);
|
|
310
|
+
if (jump > GF_THRESHOLD_M) {
|
|
311
|
+
this.recordSlip(sigKey, time, prn, signal, jump, /* @__PURE__ */ new Set([b1, b2]));
|
|
312
|
+
}
|
|
313
|
+
dualChecked.add(b1);
|
|
314
|
+
dualChecked.add(b2);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (const [band, lm] of phaseM) {
|
|
318
|
+
if (dualChecked.has(band)) continue;
|
|
319
|
+
const prevLm = prev.phase.get(band);
|
|
320
|
+
if (prevLm === void 0) continue;
|
|
321
|
+
const cm = codeM.get(band);
|
|
322
|
+
const prevCm = prev.code.get(band);
|
|
323
|
+
if (cm === void 0 || prevCm === void 0) continue;
|
|
324
|
+
const dPhase = lm - prevLm;
|
|
325
|
+
const dCode = cm - prevCm;
|
|
326
|
+
const jumpM = Math.abs(dPhase - dCode);
|
|
327
|
+
const lbl = BAND_LABELS[sys]?.[band] ?? band;
|
|
328
|
+
const sigKey = `${sys}:${lbl}`;
|
|
329
|
+
this.countEpoch(sigKey);
|
|
330
|
+
if (jumpM > SF_THRESHOLD_M) {
|
|
331
|
+
this.recordSlip(sigKey, time, prn, lbl, jumpM, /* @__PURE__ */ new Set([band]));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
this.prev.set(prn, {
|
|
336
|
+
time,
|
|
337
|
+
mwSmooth,
|
|
338
|
+
gf: gfMap,
|
|
339
|
+
phase: phaseM,
|
|
340
|
+
code: codeM
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
finalize() {
|
|
344
|
+
const satSlipCounts = {};
|
|
345
|
+
for (const ev of this.events) {
|
|
346
|
+
satSlipCounts[ev.prn] = (satSlipCounts[ev.prn] ?? 0) + 1;
|
|
347
|
+
}
|
|
348
|
+
const signalStats = [];
|
|
349
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
350
|
+
...this.signalEpochs.keys(),
|
|
351
|
+
...this.signalSlips.keys()
|
|
352
|
+
]);
|
|
353
|
+
for (const sigKey of allKeys) {
|
|
354
|
+
const [sys, ...rest] = sigKey.split(":");
|
|
355
|
+
const signal = rest.join(":");
|
|
356
|
+
const totalEpochs = this.signalEpochs.get(sigKey) ?? 0;
|
|
357
|
+
const totalSlips = this.signalSlips.get(sigKey) ?? 0;
|
|
358
|
+
const sysName = SYSTEM_NAMES[sys] ?? sys;
|
|
359
|
+
signalStats.push({
|
|
360
|
+
label: `${signal} (${sysName})`,
|
|
361
|
+
system: sys,
|
|
362
|
+
totalSlips,
|
|
363
|
+
satellites: this.signalSats.get(sigKey)?.size ?? 0,
|
|
364
|
+
totalEpochs,
|
|
365
|
+
slipRate: totalEpochs > 0 ? totalSlips / totalEpochs * 1e3 : 0
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const sysOrder = "GRECIJS";
|
|
369
|
+
signalStats.sort((a, b) => {
|
|
370
|
+
const da = sysOrder.indexOf(a.system);
|
|
371
|
+
const db = sysOrder.indexOf(b.system);
|
|
372
|
+
if (da !== db) return (da === -1 ? 99 : da) - (db === -1 ? 99 : db);
|
|
373
|
+
return a.label.localeCompare(b.label);
|
|
374
|
+
});
|
|
375
|
+
return { events: this.events, signalStats, satSlipCounts };
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/analysis/completeness.ts
|
|
380
|
+
var CompletenessAccumulator = class {
|
|
381
|
+
// PRN → code → { seen, expected, present }
|
|
382
|
+
// "seen" gates counting: expected only increments after a satellite has transmitted
|
|
383
|
+
// the signal at least once (avoids inflating expected for signals a sat doesn't have,
|
|
384
|
+
// e.g. L5 on GPS Block IIR).
|
|
385
|
+
data = /* @__PURE__ */ new Map();
|
|
386
|
+
header;
|
|
387
|
+
constructor(header) {
|
|
388
|
+
this.header = header;
|
|
389
|
+
}
|
|
390
|
+
onObservation = (_time, prn, codes, values) => {
|
|
391
|
+
let satMap = this.data.get(prn);
|
|
392
|
+
if (!satMap) {
|
|
393
|
+
satMap = /* @__PURE__ */ new Map();
|
|
394
|
+
this.data.set(prn, satMap);
|
|
395
|
+
}
|
|
396
|
+
for (let i = 0; i < codes.length; i++) {
|
|
397
|
+
const code = codes[i];
|
|
398
|
+
const hasValue = values[i] != null && values[i] !== 0;
|
|
399
|
+
let cell = satMap.get(code);
|
|
400
|
+
if (!cell) {
|
|
401
|
+
cell = { seen: false, expected: 0, present: 0 };
|
|
402
|
+
satMap.set(code, cell);
|
|
403
|
+
}
|
|
404
|
+
if (hasValue) cell.seen = true;
|
|
405
|
+
if (cell.seen) cell.expected++;
|
|
406
|
+
if (hasValue) cell.present++;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
finalize() {
|
|
410
|
+
const cells = [];
|
|
411
|
+
const systemSet = /* @__PURE__ */ new Set();
|
|
412
|
+
for (const [prn, satMap] of this.data) {
|
|
413
|
+
const sys = prn[0];
|
|
414
|
+
systemSet.add(sys);
|
|
415
|
+
for (const [code, { seen, expected, present }] of satMap) {
|
|
416
|
+
if (!seen) continue;
|
|
417
|
+
const band = code[1] ?? "?";
|
|
418
|
+
cells.push({
|
|
419
|
+
prn,
|
|
420
|
+
system: sys,
|
|
421
|
+
code,
|
|
422
|
+
band,
|
|
423
|
+
expected,
|
|
424
|
+
present,
|
|
425
|
+
percent: expected > 0 ? present / expected * 100 : 0
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const sigAgg = /* @__PURE__ */ new Map();
|
|
430
|
+
for (const c of cells) {
|
|
431
|
+
const key = `${c.system}:${c.code}`;
|
|
432
|
+
let agg = sigAgg.get(key);
|
|
433
|
+
if (!agg) {
|
|
434
|
+
agg = { expected: 0, present: 0, sats: /* @__PURE__ */ new Set() };
|
|
435
|
+
sigAgg.set(key, agg);
|
|
436
|
+
}
|
|
437
|
+
agg.expected += c.expected;
|
|
438
|
+
agg.present += c.present;
|
|
439
|
+
agg.sats.add(c.prn);
|
|
440
|
+
}
|
|
441
|
+
const signalStats = [];
|
|
442
|
+
for (const [key, agg] of sigAgg) {
|
|
443
|
+
const [sys, code] = key.split(":");
|
|
444
|
+
const band = code[1] ?? "?";
|
|
445
|
+
const bandLabel = BAND_LABELS[sys]?.[band] ?? `Band ${band}`;
|
|
446
|
+
const sysName = SYSTEM_NAMES[sys] ?? sys;
|
|
447
|
+
signalStats.push({
|
|
448
|
+
code,
|
|
449
|
+
system: sys,
|
|
450
|
+
label: `${code} (${sysName} ${bandLabel})`,
|
|
451
|
+
expected: agg.expected,
|
|
452
|
+
present: agg.present,
|
|
453
|
+
percent: agg.expected > 0 ? agg.present / agg.expected * 100 : 0,
|
|
454
|
+
satellites: agg.sats.size
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const sysOrder = "GRECIJS";
|
|
458
|
+
signalStats.sort((a, b) => {
|
|
459
|
+
const da = sysOrder.indexOf(a.system);
|
|
460
|
+
const db = sysOrder.indexOf(b.system);
|
|
461
|
+
if (da !== db) return (da === -1 ? 99 : da) - (db === -1 ? 99 : db);
|
|
462
|
+
return a.code.localeCompare(b.code);
|
|
463
|
+
});
|
|
464
|
+
const systems = [...systemSet].sort((a, b) => {
|
|
465
|
+
return (sysOrder.indexOf(a) === -1 ? 99 : sysOrder.indexOf(a)) - (sysOrder.indexOf(b) === -1 ? 99 : sysOrder.indexOf(b));
|
|
466
|
+
});
|
|
467
|
+
return { cells, signalStats, systems };
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// src/analysis/quality-analysis.ts
|
|
472
|
+
async function analyzeQuality(file, header, onProgress, signal) {
|
|
473
|
+
const mpAccum = new MultipathAccumulator(header);
|
|
474
|
+
const csAccum = new CycleSlipAccumulator(header, (time, prn, bands) => {
|
|
475
|
+
mpAccum.notifySlip(time, prn, bands);
|
|
476
|
+
});
|
|
477
|
+
const compAccum = new CompletenessAccumulator(header);
|
|
478
|
+
await parseRinexStream(
|
|
479
|
+
file,
|
|
480
|
+
onProgress,
|
|
481
|
+
signal,
|
|
482
|
+
(time, prn, codes, values) => {
|
|
483
|
+
csAccum.onObservation(time, prn, codes, values);
|
|
484
|
+
mpAccum.onObservation(time, prn, codes, values);
|
|
485
|
+
compAccum.onObservation(time, prn, codes, values);
|
|
486
|
+
},
|
|
487
|
+
true
|
|
488
|
+
/* workerMode: skip EpochSummary construction */
|
|
489
|
+
);
|
|
490
|
+
return {
|
|
491
|
+
cycleSlips: csAccum.finalize(),
|
|
492
|
+
completeness: compAccum.finalize(),
|
|
493
|
+
multipath: mpAccum.finalize()
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export {
|
|
498
|
+
MultipathAccumulator,
|
|
499
|
+
CycleSlipAccumulator,
|
|
500
|
+
CompletenessAccumulator,
|
|
501
|
+
analyzeQuality
|
|
502
|
+
};
|