gnss-js 0.1.2 → 1.0.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.
Files changed (87) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +171 -5
  3. package/dist/analysis.cjs +1381 -0
  4. package/dist/analysis.d.cts +211 -0
  5. package/dist/analysis.d.ts +211 -0
  6. package/dist/analysis.js +14 -0
  7. package/dist/antex.cjs +239 -0
  8. package/dist/antex.d.cts +77 -0
  9. package/dist/antex.d.ts +77 -0
  10. package/dist/antex.js +8 -0
  11. package/dist/chunk-354IRDOG.js +239 -0
  12. package/dist/chunk-37QNKGTC.js +65 -0
  13. package/dist/chunk-5CMSYIYM.js +214 -0
  14. package/dist/chunk-5S5MONFK.js +0 -0
  15. package/dist/chunk-6FAL6P4G.js +12 -0
  16. package/dist/chunk-7NOFXKET.js +433 -0
  17. package/dist/chunk-CB6EOOLA.js +753 -0
  18. package/dist/chunk-HKN3PUGN.js +800 -0
  19. package/dist/chunk-IS4UUDBV.js +577 -0
  20. package/dist/chunk-K7WZQFBV.js +236 -0
  21. package/dist/chunk-LEEU5OIO.js +75 -0
  22. package/dist/chunk-PRSZIWKM.js +1482 -0
  23. package/dist/chunk-SDRRAJT5.js +433 -0
  24. package/dist/chunk-WR7LCB52.js +143 -0
  25. package/dist/chunk-Y3R57B5P.js +473 -0
  26. package/dist/chunk-YDMYO3YK.js +220 -0
  27. package/dist/constants.cjs +394 -0
  28. package/dist/constants.d.cts +14 -0
  29. package/dist/constants.d.ts +14 -0
  30. package/dist/constants.js +103 -0
  31. package/dist/coordinates.cjs +334 -0
  32. package/dist/coordinates.d.cts +81 -0
  33. package/dist/coordinates.d.ts +81 -0
  34. package/dist/coordinates.js +39 -0
  35. package/dist/ecef-CF0uAysr.d.cts +40 -0
  36. package/dist/ecef-CF0uAysr.d.ts +40 -0
  37. package/dist/ephemeris-BUWzfmBy.d.cts +114 -0
  38. package/dist/ephemeris-BUWzfmBy.d.ts +114 -0
  39. package/dist/gnss-BT6ulR17.d.cts +56 -0
  40. package/dist/gnss-C-tgoYNa.d.ts +56 -0
  41. package/dist/index.cjs +5774 -78
  42. package/dist/index.d.cts +17 -96
  43. package/dist/index.d.ts +17 -96
  44. package/dist/index.js +352 -415
  45. package/dist/nav-BAI1a9vK.d.cts +108 -0
  46. package/dist/nav-BAI1a9vK.d.ts +108 -0
  47. package/dist/nmea.cjs +247 -0
  48. package/dist/nmea.d.cts +37 -0
  49. package/dist/nmea.d.ts +37 -0
  50. package/dist/nmea.js +12 -0
  51. package/dist/ntrip.cjs +169 -0
  52. package/dist/ntrip.d.cts +98 -0
  53. package/dist/ntrip.d.ts +98 -0
  54. package/dist/ntrip.js +10 -0
  55. package/dist/orbit.cjs +504 -0
  56. package/dist/orbit.d.cts +117 -0
  57. package/dist/orbit.d.ts +117 -0
  58. package/dist/orbit.js +29 -0
  59. package/dist/parser-JPjjFgeP.d.cts +73 -0
  60. package/dist/parser-JPjjFgeP.d.ts +73 -0
  61. package/dist/rinex.cjs +1375 -0
  62. package/dist/rinex.d.cts +116 -0
  63. package/dist/rinex.d.ts +116 -0
  64. package/dist/rinex.js +37 -0
  65. package/dist/rtcm3.cjs +1519 -0
  66. package/dist/rtcm3.d.cts +143 -0
  67. package/dist/rtcm3.d.ts +143 -0
  68. package/dist/rtcm3.js +31 -0
  69. package/dist/signals.cjs +858 -0
  70. package/dist/signals.d.cts +93 -0
  71. package/dist/signals.d.ts +93 -0
  72. package/dist/signals.js +70 -0
  73. package/dist/time-DnI1VpE8.d.cts +33 -0
  74. package/dist/time-DnI1VpE8.d.ts +33 -0
  75. package/dist/time.cjs +585 -0
  76. package/dist/time.d.cts +307 -0
  77. package/dist/time.d.ts +307 -0
  78. package/dist/time.js +154 -0
  79. package/package.json +112 -14
  80. package/.prettierrc +0 -5
  81. package/jest.config.ts +0 -9
  82. package/test/functions.test.ts +0 -63
  83. package/test/gnss-scales.test.ts +0 -95
  84. package/test/julian.test.ts +0 -145
  85. package/test/rinex.test.ts +0 -11
  86. package/test/test.ts +0 -10
  87. package/test/utc.test.ts +0 -25
@@ -0,0 +1,1381 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/analysis/index.ts
21
+ var analysis_exports = {};
22
+ __export(analysis_exports, {
23
+ CompletenessAccumulator: () => CompletenessAccumulator,
24
+ CycleSlipAccumulator: () => CycleSlipAccumulator,
25
+ MultipathAccumulator: () => MultipathAccumulator,
26
+ analyzeQuality: () => analyzeQuality
27
+ });
28
+ module.exports = __toCommonJS(analysis_exports);
29
+
30
+ // src/constants/gnss.ts
31
+ var SYSTEM_NAMES = {
32
+ G: "GPS",
33
+ R: "GLONASS",
34
+ E: "Galileo",
35
+ C: "BeiDou",
36
+ J: "QZSS",
37
+ I: "NavIC",
38
+ S: "SBAS"
39
+ };
40
+ var C_LIGHT = 299792458;
41
+ var FREQ = {
42
+ G: { "1": 157542e4, "2": 12276e5, "5": 117645e4 },
43
+ R: { "3": 1202025e3 },
44
+ E: { "1": 157542e4, "5": 117645e4, "6": 127875e4, "7": 120714e4, "8": 1191795e3 },
45
+ C: { "1": 157542e4, "2": 1561098e3, "5": 117645e4, "6": 126852e4, "7": 120714e4 },
46
+ J: { "1": 157542e4, "2": 12276e5, "5": 117645e4, "6": 127875e4 },
47
+ I: { "5": 117645e4, "9": 2492028e3 },
48
+ S: { "1": 157542e4, "5": 117645e4 }
49
+ };
50
+ var BAND_LABELS = {
51
+ G: { "1": "L1", "2": "L2", "5": "L5" },
52
+ R: { "1": "G1", "2": "G2", "3": "G3" },
53
+ E: { "1": "E1", "5": "E5a", "6": "E6", "7": "E5b", "8": "E5" },
54
+ C: { "1": "B1C", "2": "B1I", "5": "B2a", "6": "B3I", "7": "B2I" },
55
+ J: { "1": "L1", "2": "L2", "5": "L5", "6": "L6" },
56
+ I: { "5": "L5", "9": "S" },
57
+ S: { "1": "L1", "5": "L5" }
58
+ };
59
+ var DUAL_FREQ_PAIRS = {
60
+ G: [["1", "2"], ["1", "5"]],
61
+ R: [["1", "2"], ["1", "3"]],
62
+ E: [["1", "5"], ["1", "7"], ["1", "6"]],
63
+ C: [["2", "7"], ["2", "6"], ["1", "5"]],
64
+ J: [["1", "2"], ["1", "5"]],
65
+ I: [["5", "9"]],
66
+ S: [["1", "5"]]
67
+ };
68
+ var GLO_F1_BASE = 1602e6;
69
+ var GLO_F1_STEP = 562500;
70
+ var GLO_F2_BASE = 1246e6;
71
+ var GLO_F2_STEP = 437500;
72
+ var GLO_F3 = 1202025e3;
73
+ var GLO_CHANNEL_FALLBACK = {
74
+ R01: 1,
75
+ R02: -4,
76
+ R03: 5,
77
+ R04: 6,
78
+ R05: 1,
79
+ R06: -4,
80
+ R07: 5,
81
+ R08: 6,
82
+ R09: -2,
83
+ R10: -7,
84
+ R11: 0,
85
+ R12: -1,
86
+ R13: -2,
87
+ R14: -7,
88
+ R15: 0,
89
+ R16: -1,
90
+ R17: 4,
91
+ R18: -3,
92
+ R19: 3,
93
+ R20: 2,
94
+ R21: 4,
95
+ R22: -3,
96
+ R23: 3,
97
+ R24: 2
98
+ };
99
+ function buildGloChannelMap(slots) {
100
+ if (Object.keys(slots).length === 0) return { ...GLO_CHANNEL_FALLBACK };
101
+ const map = {};
102
+ for (const [slot, k] of Object.entries(slots)) {
103
+ map[`R${String(slot).padStart(2, "0")}`] = k;
104
+ }
105
+ return map;
106
+ }
107
+ function gloFreq(gloChannels, prn, band) {
108
+ const k = gloChannels[prn];
109
+ if (k === void 0) return void 0;
110
+ if (band === "1") return GLO_F1_BASE + k * GLO_F1_STEP;
111
+ if (band === "2") return GLO_F2_BASE + k * GLO_F2_STEP;
112
+ if (band === "3") return GLO_F3;
113
+ return void 0;
114
+ }
115
+ function getFreq(gloChannels, prn, band) {
116
+ const sys = prn[0];
117
+ if (sys === "R") return gloFreq(gloChannels, prn, band);
118
+ return FREQ[sys]?.[band];
119
+ }
120
+ var ATTR_PRIORITY = {
121
+ X: 8,
122
+ C: 7,
123
+ S: 6,
124
+ L: 6,
125
+ Q: 6,
126
+ I: 5,
127
+ B: 5,
128
+ D: 4,
129
+ Z: 3,
130
+ P: 2,
131
+ W: 1
132
+ };
133
+ function attrRank(code) {
134
+ return ATTR_PRIORITY[code[2] ?? ""] ?? 3;
135
+ }
136
+ function buildObsIndices(header) {
137
+ const result = /* @__PURE__ */ new Map();
138
+ for (const [sys, codes] of Object.entries(header.obsTypes)) {
139
+ if (sys === "_v2") continue;
140
+ const lIdx = /* @__PURE__ */ new Map();
141
+ const lRank = /* @__PURE__ */ new Map();
142
+ const cIdx = /* @__PURE__ */ new Map();
143
+ const cRank = /* @__PURE__ */ new Map();
144
+ for (let i = 0; i < codes.length; i++) {
145
+ const code = codes[i];
146
+ const type = code[0];
147
+ const band = code[1];
148
+ if (!band) continue;
149
+ const rank = attrRank(code);
150
+ if (type === "L" && rank > (lRank.get(band) ?? -1)) {
151
+ lIdx.set(band, i);
152
+ lRank.set(band, rank);
153
+ }
154
+ if ((type === "C" || type === "P") && rank > (cRank.get(band) ?? -1)) {
155
+ cIdx.set(band, i);
156
+ cRank.set(band, rank);
157
+ }
158
+ }
159
+ const bandMap = /* @__PURE__ */ new Map();
160
+ for (const [band, li] of lIdx) {
161
+ bandMap.set(band, { L: li, C: cIdx.get(band) ?? null });
162
+ }
163
+ if (bandMap.size > 0) result.set(sys, bandMap);
164
+ }
165
+ const v2codes = header.obsTypes["_v2"];
166
+ if (v2codes) {
167
+ const lIdx = /* @__PURE__ */ new Map();
168
+ const cIdx = /* @__PURE__ */ new Map();
169
+ for (let i = 0; i < v2codes.length; i++) {
170
+ const code = v2codes[i];
171
+ const type = code[0];
172
+ const band = code[1];
173
+ if (!band) continue;
174
+ if (type === "L" && !lIdx.has(band)) lIdx.set(band, i);
175
+ if ((type === "C" || type === "P") && !cIdx.has(band)) cIdx.set(band, i);
176
+ }
177
+ const bandMap = /* @__PURE__ */ new Map();
178
+ for (const [band, li] of lIdx) {
179
+ bandMap.set(band, { L: li, C: cIdx.get(band) ?? null });
180
+ }
181
+ if (bandMap.size > 0) result.set("_v2", bandMap);
182
+ }
183
+ return result;
184
+ }
185
+ var ARC_GAP_FACTOR = 5;
186
+
187
+ // src/analysis/multipath.ts
188
+ var MIN_ARC_LENGTH = 10;
189
+ var MultipathAccumulator = class {
190
+ state = /* @__PURE__ */ new Map();
191
+ results = [];
192
+ interval;
193
+ obsIndices;
194
+ gloChannels;
195
+ constructor(header) {
196
+ this.interval = header.interval ?? 30;
197
+ this.obsIndices = buildObsIndices(header);
198
+ this.gloChannels = buildGloChannelMap(header.glonassSlots);
199
+ }
200
+ /** Observation callback — wire this into parseRinexStream. */
201
+ onObservation = (time, prn, _codes, values) => {
202
+ const sys = prn[0];
203
+ const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
204
+ if (!bandMap) return;
205
+ const bandData = /* @__PURE__ */ new Map();
206
+ for (const [band, { C, L }] of bandMap) {
207
+ if (C === null) continue;
208
+ const cVal = values[C];
209
+ const lVal = values[L];
210
+ const freq = getFreq(this.gloChannels, prn, band);
211
+ if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
212
+ bandData.set(band, { C: cVal, L: lVal, f: freq });
213
+ }
214
+ }
215
+ if (bandData.size < 2) return;
216
+ const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
217
+ for (const [bi, bj] of pairs) {
218
+ const di = bandData.get(bi);
219
+ const dj = bandData.get(bj);
220
+ if (!di || !dj) continue;
221
+ const \u03BBi = C_LIGHT / di.f;
222
+ const \u03BBj = C_LIGHT / dj.f;
223
+ const \u03B1 = di.f * di.f / (dj.f * dj.f);
224
+ const coeff = 2 / (\u03B1 - 1);
225
+ const mp = di.C - (1 + coeff) * di.L * \u03BBi + coeff * dj.L * \u03BBj;
226
+ this.pushMp(prn, bi, bj, time, mp);
227
+ }
228
+ };
229
+ pushMp(prn, band, refBand, time, mp) {
230
+ if (!isFinite(mp)) return;
231
+ const pairKey = `${band}-${refBand}`;
232
+ let satStates = this.state.get(prn);
233
+ if (!satStates) {
234
+ satStates = /* @__PURE__ */ new Map();
235
+ this.state.set(prn, satStates);
236
+ }
237
+ let bandState = satStates.get(pairKey);
238
+ if (!bandState) {
239
+ bandState = { arc: { times: [], rawMp: [] }, lastTime: 0 };
240
+ satStates.set(pairKey, bandState);
241
+ }
242
+ const gap = bandState.lastTime > 0 ? (time - bandState.lastTime) / 1e3 : 0;
243
+ if (bandState.lastTime > 0 && gap > this.interval * ARC_GAP_FACTOR) {
244
+ this.closeArc(prn, band, refBand, bandState);
245
+ }
246
+ bandState.arc.times.push(time);
247
+ bandState.arc.rawMp.push(mp);
248
+ bandState.lastTime = time;
249
+ }
250
+ /** External cycle-slip notification — close affected arcs for this PRN. */
251
+ notifySlip(_time, prn, bands) {
252
+ const satStates = this.state.get(prn);
253
+ if (!satStates) return;
254
+ for (const [pairKey, bandState] of satStates) {
255
+ const [band, refBand] = pairKey.split("-");
256
+ if (bands.has(band) || bands.has(refBand)) {
257
+ this.closeArc(prn, band, refBand, bandState);
258
+ }
259
+ }
260
+ }
261
+ closeArc(prn, band, refBand, state) {
262
+ const arc = state.arc;
263
+ if (arc.times.length >= MIN_ARC_LENGTH) {
264
+ const mean = arc.rawMp.reduce((a, b) => a + b, 0) / arc.rawMp.length;
265
+ const sys = prn[0];
266
+ const bLabel = BAND_LABELS[sys]?.[band] ?? band;
267
+ const rLabel = BAND_LABELS[sys]?.[refBand] ?? refBand;
268
+ const label = `${prn} MP ${bLabel}-${rLabel}`;
269
+ const points = arc.times.map((t, i) => ({
270
+ time: t,
271
+ mp: arc.rawMp[i] - mean
272
+ }));
273
+ const rms = Math.sqrt(points.reduce((s, p) => s + p.mp * p.mp, 0) / points.length);
274
+ this.results.push({ prn, system: sys, band, refBand, label, points, rms });
275
+ }
276
+ state.arc = { times: [], rawMp: [] };
277
+ }
278
+ /** Finalize: close remaining arcs and compute statistics. */
279
+ finalize() {
280
+ for (const [prn, satStates] of this.state) {
281
+ for (const [pairKey, bandState] of satStates) {
282
+ const [band, refBand] = pairKey.split("-");
283
+ this.closeArc(prn, band, refBand, bandState);
284
+ }
285
+ }
286
+ const signalGroups = /* @__PURE__ */ new Map();
287
+ for (const series of this.results) {
288
+ const key = `${series.system}-${series.band}-${series.refBand}`;
289
+ if (!signalGroups.has(key)) signalGroups.set(key, []);
290
+ signalGroups.get(key).push(series);
291
+ }
292
+ const signalStats = [];
293
+ for (const [, seriesList] of signalGroups) {
294
+ const { system, band, refBand } = seriesList[0];
295
+ let sumSq = 0;
296
+ let count = 0;
297
+ for (const s of seriesList) {
298
+ for (const p of s.points) {
299
+ sumSq += p.mp * p.mp;
300
+ count++;
301
+ }
302
+ }
303
+ const rms = count > 0 ? Math.sqrt(sumSq / count) : 0;
304
+ const bLabel = BAND_LABELS[system]?.[band] ?? band;
305
+ const rLabel = BAND_LABELS[system]?.[refBand] ?? refBand;
306
+ const sysName = SYSTEM_NAMES[system] ?? system;
307
+ signalStats.push({
308
+ label: `MP ${bLabel}-${rLabel} (${sysName})`,
309
+ system,
310
+ band,
311
+ refBand,
312
+ rms,
313
+ count,
314
+ satellites: seriesList.length
315
+ });
316
+ }
317
+ const sysOrder = "GRECIJS";
318
+ signalStats.sort((a, b) => {
319
+ const da = sysOrder.indexOf(a.system);
320
+ const db = sysOrder.indexOf(b.system);
321
+ if (da !== db) return (da === -1 ? 99 : da) - (db === -1 ? 99 : db);
322
+ return a.band.localeCompare(b.band);
323
+ });
324
+ return { series: this.results, signalStats };
325
+ }
326
+ };
327
+
328
+ // src/analysis/cycle-slip.ts
329
+ var MW_MIN_THRESHOLD = 0.5;
330
+ var MW_SIGMA_FACTOR = 4;
331
+ var MW_MIN_EPOCHS = 3;
332
+ var GF_THRESHOLD_M = 0.15;
333
+ var SF_THRESHOLD_M = 3;
334
+ function mwSmoothInit(value) {
335
+ return { mean: value, m2: 0, n: 1 };
336
+ }
337
+ function mwSmoothUpdate(s, value) {
338
+ const n = s.n + 1;
339
+ const delta = value - s.mean;
340
+ const mean = s.mean + delta / n;
341
+ const delta2 = value - mean;
342
+ return { mean, m2: s.m2 + delta * delta2, n };
343
+ }
344
+ function mwSmoothStddev(s) {
345
+ return s.n > 1 ? Math.sqrt(s.m2 / (s.n - 1)) : Infinity;
346
+ }
347
+ var CycleSlipAccumulator = class {
348
+ obsIndices;
349
+ prev = /* @__PURE__ */ new Map();
350
+ events = [];
351
+ interval;
352
+ /** GLONASS PRN → channel k for FDMA frequency computation. */
353
+ gloChannels;
354
+ onSlip;
355
+ // Per-signal epoch/slip counting
356
+ signalEpochs = /* @__PURE__ */ new Map();
357
+ signalSlips = /* @__PURE__ */ new Map();
358
+ signalSats = /* @__PURE__ */ new Map();
359
+ constructor(header, onSlip) {
360
+ this.interval = header.interval ?? 30;
361
+ this.obsIndices = buildObsIndices(header);
362
+ this.gloChannels = buildGloChannelMap(header.glonassSlots);
363
+ this.onSlip = onSlip;
364
+ }
365
+ recordSlip(sigKey, time, prn, signal, magnitude, bands) {
366
+ this.events.push({ time, prn, signal, magnitude });
367
+ this.signalSlips.set(sigKey, (this.signalSlips.get(sigKey) ?? 0) + 1);
368
+ if (!this.signalSats.has(sigKey)) this.signalSats.set(sigKey, /* @__PURE__ */ new Set());
369
+ this.signalSats.get(sigKey).add(prn);
370
+ this.onSlip?.(time, prn, bands);
371
+ }
372
+ countEpoch(sigKey) {
373
+ this.signalEpochs.set(sigKey, (this.signalEpochs.get(sigKey) ?? 0) + 1);
374
+ }
375
+ /** Resolve frequency for a given PRN + band. Handles GLONASS FDMA. */
376
+ getFreqForPrn(prn, band) {
377
+ return getFreq(this.gloChannels, prn, band);
378
+ }
379
+ onObservation = (time, prn, _codes, values) => {
380
+ const sys = prn[0];
381
+ const idxMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
382
+ if (!idxMap) return;
383
+ if (sys !== "R" && !FREQ[sys]) return;
384
+ const phaseM = /* @__PURE__ */ new Map();
385
+ const codeM = /* @__PURE__ */ new Map();
386
+ for (const [band, { L, C }] of idxMap) {
387
+ const freq = this.getFreqForPrn(prn, band);
388
+ if (!freq) continue;
389
+ const lVal = values[L];
390
+ if (lVal != null && lVal !== 0) phaseM.set(band, lVal * (C_LIGHT / freq));
391
+ if (C !== null) {
392
+ const cVal = values[C];
393
+ if (cVal != null && cVal !== 0) codeM.set(band, cVal);
394
+ }
395
+ }
396
+ if (phaseM.size === 0) return;
397
+ const prev = this.prev.get(prn);
398
+ const gap = prev ? (time - prev.time) / 1e3 : 0;
399
+ const isArcBreak = !prev || gap > this.interval * ARC_GAP_FACTOR;
400
+ const mwSmooth = /* @__PURE__ */ new Map();
401
+ const gfMap = /* @__PURE__ */ new Map();
402
+ const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
403
+ for (const [b1, b2] of pairs) {
404
+ const l1 = phaseM.get(b1);
405
+ const l2 = phaseM.get(b2);
406
+ const f1 = this.getFreqForPrn(prn, b1);
407
+ const f2 = this.getFreqForPrn(prn, b2);
408
+ if (!f1 || !f2) continue;
409
+ const pairKey = `${b1}-${b2}`;
410
+ if (l1 !== void 0 && l2 !== void 0) {
411
+ gfMap.set(pairKey, l1 - l2);
412
+ }
413
+ const c1 = codeM.get(b1);
414
+ const c2 = codeM.get(b2);
415
+ if (l1 !== void 0 && l2 !== void 0 && c1 !== void 0 && c2 !== void 0) {
416
+ const lambda_wl = C_LIGHT / (f1 - f2);
417
+ const phi_wl = (f1 * l1 - f2 * l2) / C_LIGHT;
418
+ const P_nl = (f1 * c1 + f2 * c2) / ((f1 + f2) * lambda_wl);
419
+ const mw = phi_wl - P_nl;
420
+ const prevSmooth = isArcBreak ? void 0 : prev?.mwSmooth.get(pairKey);
421
+ if (prevSmooth && prevSmooth.n >= MW_MIN_EPOCHS) {
422
+ const lbl1 = BAND_LABELS[sys]?.[b1] ?? b1;
423
+ const lbl2 = BAND_LABELS[sys]?.[b2] ?? b2;
424
+ const signal = `MW ${lbl1}-${lbl2}`;
425
+ const sigKey = `${sys}:${signal}`;
426
+ this.countEpoch(sigKey);
427
+ const stddev = mwSmoothStddev(prevSmooth);
428
+ const threshold = Math.max(MW_MIN_THRESHOLD, MW_SIGMA_FACTOR * stddev);
429
+ const deviation = Math.abs(mw - prevSmooth.mean);
430
+ if (deviation > threshold) {
431
+ this.recordSlip(sigKey, time, prn, signal, deviation, /* @__PURE__ */ new Set([b1, b2]));
432
+ mwSmooth.set(pairKey, mwSmoothInit(mw));
433
+ } else {
434
+ mwSmooth.set(pairKey, mwSmoothUpdate(prevSmooth, mw));
435
+ }
436
+ } else if (prevSmooth) {
437
+ mwSmooth.set(pairKey, mwSmoothUpdate(prevSmooth, mw));
438
+ } else {
439
+ mwSmooth.set(pairKey, mwSmoothInit(mw));
440
+ }
441
+ }
442
+ }
443
+ if (!isArcBreak && prev) {
444
+ const dualChecked = /* @__PURE__ */ new Set();
445
+ for (const [b1, b2] of pairs) {
446
+ const pairKey = `${b1}-${b2}`;
447
+ const lbl1 = BAND_LABELS[sys]?.[b1] ?? b1;
448
+ const lbl2 = BAND_LABELS[sys]?.[b2] ?? b2;
449
+ if (mwSmooth.has(pairKey)) {
450
+ dualChecked.add(b1);
451
+ dualChecked.add(b2);
452
+ continue;
453
+ }
454
+ const curGf = gfMap.get(pairKey);
455
+ const prevGf = prev.gf.get(pairKey);
456
+ if (curGf !== void 0 && prevGf !== void 0) {
457
+ const signal = `GF ${lbl1}-${lbl2}`;
458
+ const sigKey = `${sys}:${signal}`;
459
+ this.countEpoch(sigKey);
460
+ const jump = Math.abs(curGf - prevGf);
461
+ if (jump > GF_THRESHOLD_M) {
462
+ this.recordSlip(sigKey, time, prn, signal, jump, /* @__PURE__ */ new Set([b1, b2]));
463
+ }
464
+ dualChecked.add(b1);
465
+ dualChecked.add(b2);
466
+ }
467
+ }
468
+ for (const [band, lm] of phaseM) {
469
+ if (dualChecked.has(band)) continue;
470
+ const prevLm = prev.phase.get(band);
471
+ if (prevLm === void 0) continue;
472
+ const cm = codeM.get(band);
473
+ const prevCm = prev.code.get(band);
474
+ if (cm === void 0 || prevCm === void 0) continue;
475
+ const dPhase = lm - prevLm;
476
+ const dCode = cm - prevCm;
477
+ const jumpM = Math.abs(dPhase - dCode);
478
+ const lbl = BAND_LABELS[sys]?.[band] ?? band;
479
+ const sigKey = `${sys}:${lbl}`;
480
+ this.countEpoch(sigKey);
481
+ if (jumpM > SF_THRESHOLD_M) {
482
+ this.recordSlip(sigKey, time, prn, lbl, jumpM, /* @__PURE__ */ new Set([band]));
483
+ }
484
+ }
485
+ }
486
+ this.prev.set(prn, { time, mwSmooth, gf: gfMap, phase: phaseM, code: codeM });
487
+ };
488
+ finalize() {
489
+ const satSlipCounts = {};
490
+ for (const ev of this.events) {
491
+ satSlipCounts[ev.prn] = (satSlipCounts[ev.prn] ?? 0) + 1;
492
+ }
493
+ const signalStats = [];
494
+ const allKeys = /* @__PURE__ */ new Set([...this.signalEpochs.keys(), ...this.signalSlips.keys()]);
495
+ for (const sigKey of allKeys) {
496
+ const [sys, ...rest] = sigKey.split(":");
497
+ const signal = rest.join(":");
498
+ const totalEpochs = this.signalEpochs.get(sigKey) ?? 0;
499
+ const totalSlips = this.signalSlips.get(sigKey) ?? 0;
500
+ const sysName = SYSTEM_NAMES[sys] ?? sys;
501
+ signalStats.push({
502
+ label: `${signal} (${sysName})`,
503
+ system: sys,
504
+ totalSlips,
505
+ satellites: this.signalSats.get(sigKey)?.size ?? 0,
506
+ totalEpochs,
507
+ slipRate: totalEpochs > 0 ? totalSlips / totalEpochs * 1e3 : 0
508
+ });
509
+ }
510
+ const sysOrder = "GRECIJS";
511
+ signalStats.sort((a, b) => {
512
+ const da = sysOrder.indexOf(a.system);
513
+ const db = sysOrder.indexOf(b.system);
514
+ if (da !== db) return (da === -1 ? 99 : da) - (db === -1 ? 99 : db);
515
+ return a.label.localeCompare(b.label);
516
+ });
517
+ return { events: this.events, signalStats, satSlipCounts };
518
+ }
519
+ };
520
+
521
+ // src/analysis/completeness.ts
522
+ var CompletenessAccumulator = class {
523
+ // PRN → code → { seen, expected, present }
524
+ // "seen" gates counting: expected only increments after a satellite has transmitted
525
+ // the signal at least once (avoids inflating expected for signals a sat doesn't have,
526
+ // e.g. L5 on GPS Block IIR).
527
+ data = /* @__PURE__ */ new Map();
528
+ header;
529
+ constructor(header) {
530
+ this.header = header;
531
+ }
532
+ onObservation = (_time, prn, codes, values) => {
533
+ let satMap = this.data.get(prn);
534
+ if (!satMap) {
535
+ satMap = /* @__PURE__ */ new Map();
536
+ this.data.set(prn, satMap);
537
+ }
538
+ for (let i = 0; i < codes.length; i++) {
539
+ const code = codes[i];
540
+ const hasValue = values[i] != null && values[i] !== 0;
541
+ let cell = satMap.get(code);
542
+ if (!cell) {
543
+ cell = { seen: false, expected: 0, present: 0 };
544
+ satMap.set(code, cell);
545
+ }
546
+ if (hasValue) cell.seen = true;
547
+ if (cell.seen) cell.expected++;
548
+ if (hasValue) cell.present++;
549
+ }
550
+ };
551
+ finalize() {
552
+ const cells = [];
553
+ const systemSet = /* @__PURE__ */ new Set();
554
+ for (const [prn, satMap] of this.data) {
555
+ const sys = prn[0];
556
+ systemSet.add(sys);
557
+ for (const [code, { seen, expected, present }] of satMap) {
558
+ if (!seen) continue;
559
+ const band = code[1] ?? "?";
560
+ cells.push({
561
+ prn,
562
+ system: sys,
563
+ code,
564
+ band,
565
+ expected,
566
+ present,
567
+ percent: expected > 0 ? present / expected * 100 : 0
568
+ });
569
+ }
570
+ }
571
+ const sigAgg = /* @__PURE__ */ new Map();
572
+ for (const c of cells) {
573
+ const key = `${c.system}:${c.code}`;
574
+ let agg = sigAgg.get(key);
575
+ if (!agg) {
576
+ agg = { expected: 0, present: 0, sats: /* @__PURE__ */ new Set() };
577
+ sigAgg.set(key, agg);
578
+ }
579
+ agg.expected += c.expected;
580
+ agg.present += c.present;
581
+ agg.sats.add(c.prn);
582
+ }
583
+ const signalStats = [];
584
+ for (const [key, agg] of sigAgg) {
585
+ const [sys, code] = key.split(":");
586
+ const band = code[1] ?? "?";
587
+ const bandLabel = BAND_LABELS[sys]?.[band] ?? `Band ${band}`;
588
+ const sysName = SYSTEM_NAMES[sys] ?? sys;
589
+ signalStats.push({
590
+ code,
591
+ system: sys,
592
+ label: `${code} (${sysName} ${bandLabel})`,
593
+ expected: agg.expected,
594
+ present: agg.present,
595
+ percent: agg.expected > 0 ? agg.present / agg.expected * 100 : 0,
596
+ satellites: agg.sats.size
597
+ });
598
+ }
599
+ const sysOrder = "GRECIJS";
600
+ signalStats.sort((a, b) => {
601
+ const da = sysOrder.indexOf(a.system);
602
+ const db = sysOrder.indexOf(b.system);
603
+ if (da !== db) return (da === -1 ? 99 : da) - (db === -1 ? 99 : db);
604
+ return a.code.localeCompare(b.code);
605
+ });
606
+ const systems = [...systemSet].sort((a, b) => {
607
+ return (sysOrder.indexOf(a) === -1 ? 99 : sysOrder.indexOf(a)) - (sysOrder.indexOf(b) === -1 ? 99 : sysOrder.indexOf(b));
608
+ });
609
+ return { cells, signalStats, systems };
610
+ }
611
+ };
612
+
613
+ // src/rinex/crx.ts
614
+ function crxRepair(old, diff) {
615
+ const chars = old.split("");
616
+ for (let i = 0; i < diff.length; i++) {
617
+ const dc = diff[i];
618
+ if (dc === " ") {
619
+ } else if (dc === "&") {
620
+ chars[i] = " ";
621
+ } else {
622
+ chars[i] = dc;
623
+ }
624
+ }
625
+ if (diff.length > chars.length) {
626
+ for (let i = chars.length; i < diff.length; i++) {
627
+ chars[i] = diff[i] === "&" ? " " : diff[i];
628
+ }
629
+ }
630
+ return chars.join("");
631
+ }
632
+ function parseCrxDataLine(line, ntype) {
633
+ const fields = [];
634
+ let pos = 0;
635
+ for (let j = 0; j < ntype; j++) {
636
+ if (pos >= line.length || line[pos] === "\0") {
637
+ fields.push({ empty: true, init: false, arcOrder: -1, value: 0 });
638
+ pos++;
639
+ continue;
640
+ }
641
+ if (line[pos] === " ") {
642
+ fields.push({ empty: true, init: false, arcOrder: -1, value: 0 });
643
+ pos++;
644
+ continue;
645
+ }
646
+ const start = pos;
647
+ while (pos < line.length && line[pos] !== " ") pos++;
648
+ const token = line.substring(start, pos);
649
+ if (pos < line.length && line[pos] === " ") pos++;
650
+ if (token.length >= 3 && token[1] === "&") {
651
+ const arcOrder = parseInt(token[0]);
652
+ const value = parseInt(token.substring(2));
653
+ fields.push({ empty: false, init: true, arcOrder, value: isNaN(value) ? 0 : value });
654
+ } else {
655
+ const value = parseInt(token);
656
+ fields.push({ empty: false, init: false, arcOrder: -1, value: isNaN(value) ? 0 : value });
657
+ }
658
+ }
659
+ const flagStr = pos < line.length ? line.substring(pos) : "";
660
+ return { fields, flagStr };
661
+ }
662
+ function crxDecompress(prev, field) {
663
+ if (field.init) {
664
+ const a = new Array(Math.max(field.arcOrder + 1, 1)).fill(0);
665
+ a[0] = field.value;
666
+ return { state: { accum: a, order: 0, arcOrder: field.arcOrder }, result: field.value };
667
+ }
668
+ if (!prev) {
669
+ return { state: { accum: [0], order: 0, arcOrder: 0 }, result: 0 };
670
+ }
671
+ let order = prev.order;
672
+ if (order < prev.arcOrder) order++;
673
+ const accum = [...prev.accum];
674
+ while (accum.length <= order) accum.push(0);
675
+ accum[order] = field.value;
676
+ for (let k = order; k >= 1; k--) {
677
+ accum[k - 1] += accum[k];
678
+ }
679
+ return { state: { accum, order, arcOrder: prev.arcOrder }, result: accum[0] };
680
+ }
681
+
682
+ // src/rinex/parser.ts
683
+ var CHUNK_SIZE = 2 * 1024 * 1024;
684
+ var yieldToMain = () => new Promise((r) => setTimeout(r, 0));
685
+ var noYield = () => Promise.resolve();
686
+ var SYSTEM_ORDER = ["G", "R", "E", "C", "J", "I", "S"];
687
+ var SYSTEM_RANK = Object.fromEntries(SYSTEM_ORDER.map((s, i) => [s, i]));
688
+ function systemCmp(a, b) {
689
+ const ra = SYSTEM_RANK[a.charAt(0)] ?? 99;
690
+ const rb = SYSTEM_RANK[b.charAt(0)] ?? 99;
691
+ if (ra !== rb) return ra - rb;
692
+ return a.localeCompare(b);
693
+ }
694
+ function parseHeader(lines) {
695
+ const h = {
696
+ version: 0,
697
+ type: "",
698
+ satSystem: "",
699
+ markerName: "",
700
+ markerType: "",
701
+ observer: "",
702
+ agency: "",
703
+ receiverNumber: "",
704
+ receiverType: "",
705
+ receiverVersion: "",
706
+ antNumber: "",
707
+ antType: "",
708
+ approxPosition: null,
709
+ antDelta: null,
710
+ interval: null,
711
+ timeOfFirstObs: null,
712
+ timeOfLastObs: null,
713
+ obsTypes: {},
714
+ glonassSlots: {},
715
+ isCrx: false,
716
+ crxVersion: 0
717
+ };
718
+ let currentObsSys = "";
719
+ for (const raw of lines) {
720
+ const label = raw.substring(60).trim();
721
+ const data = raw.substring(0, 60);
722
+ switch (label) {
723
+ case "CRINEX VERS / TYPE": {
724
+ h.isCrx = true;
725
+ h.crxVersion = parseFloat(data.substring(0, 9)) || 0;
726
+ break;
727
+ }
728
+ case "RINEX VERSION / TYPE": {
729
+ h.version = parseFloat(data.substring(0, 9));
730
+ h.type = data.substring(20, 21).trim();
731
+ h.satSystem = data.substring(40, 41).trim();
732
+ break;
733
+ }
734
+ case "MARKER NAME":
735
+ h.markerName = data.trim();
736
+ break;
737
+ case "MARKER TYPE":
738
+ h.markerType = data.trim();
739
+ break;
740
+ case "OBSERVER / AGENCY":
741
+ h.observer = data.substring(0, 20).trim();
742
+ h.agency = data.substring(20).trim();
743
+ break;
744
+ case "REC # / TYPE / VERS":
745
+ h.receiverNumber = data.substring(0, 20).trim();
746
+ h.receiverType = data.substring(20, 40).trim();
747
+ h.receiverVersion = data.substring(40, 60).trim();
748
+ break;
749
+ case "ANT # / TYPE":
750
+ h.antNumber = data.substring(0, 20).trim();
751
+ h.antType = data.substring(20).trim();
752
+ break;
753
+ case "ANTENNA: DELTA H/E/N": {
754
+ const d = data.trim().split(/\s+/).map(Number);
755
+ if (d.length >= 3 && !d.slice(0, 3).some(isNaN)) {
756
+ h.antDelta = [d[0], d[1], d[2]];
757
+ }
758
+ break;
759
+ }
760
+ case "APPROX POSITION XYZ": {
761
+ const p = data.trim().split(/\s+/).map(Number);
762
+ if (p.length >= 3 && p[0] !== void 0 && p[1] !== void 0 && p[2] !== void 0 && !isNaN(p[0]) && !isNaN(p[1]) && !isNaN(p[2])) {
763
+ h.approxPosition = [p[0], p[1], p[2]];
764
+ }
765
+ break;
766
+ }
767
+ case "INTERVAL":
768
+ h.interval = parseFloat(data);
769
+ break;
770
+ case "TIME OF FIRST OBS":
771
+ h.timeOfFirstObs = parseHeaderTime(data);
772
+ break;
773
+ case "TIME OF LAST OBS":
774
+ h.timeOfLastObs = parseHeaderTime(data);
775
+ break;
776
+ case "SYS / # / OBS TYPES": {
777
+ const sys = data[0]?.trim();
778
+ if (sys) {
779
+ currentObsSys = sys;
780
+ const count = parseInt(data.substring(3, 6));
781
+ const codes = data.substring(7, 60).trim().split(/\s+/).filter(Boolean);
782
+ h.obsTypes[currentObsSys] = codes;
783
+ void count;
784
+ } else if (currentObsSys && h.obsTypes[currentObsSys]) {
785
+ const codes = data.substring(7, 60).trim().split(/\s+/).filter(Boolean);
786
+ h.obsTypes[currentObsSys].push(...codes);
787
+ }
788
+ break;
789
+ }
790
+ case "GLONASS SLOT / FRQ #": {
791
+ const nSats = parseInt(data.substring(0, 3));
792
+ void nSats;
793
+ for (let i = 0; i < 8; i++) {
794
+ const off = 4 + i * 7;
795
+ const sat = data.substring(off, off + 3).trim();
796
+ const ch = data.substring(off + 3, off + 7).trim();
797
+ if (sat && sat[0] === "R" && ch) {
798
+ const slot = parseInt(sat.substring(1));
799
+ const k = parseInt(ch);
800
+ if (!isNaN(slot) && !isNaN(k)) h.glonassSlots[slot] = k;
801
+ }
802
+ }
803
+ break;
804
+ }
805
+ case "# / TYPES OF OBSERV": {
806
+ const existing = h.obsTypes["_v2"];
807
+ const codes = data.substring(6, 60).trim().split(/\s+/).filter(Boolean);
808
+ if (!existing || existing.length === 0) {
809
+ h.obsTypes["_v2"] = codes;
810
+ } else {
811
+ existing.push(...codes);
812
+ }
813
+ break;
814
+ }
815
+ }
816
+ }
817
+ return h;
818
+ }
819
+ function parseHeaderTime(data) {
820
+ const p = data.trim().split(/\s+/).map(Number);
821
+ if (p.length < 6) return null;
822
+ const yr = p[0], mo = p[1], dy = p[2], hr = p[3], mn = p[4], sc = p[5];
823
+ if ([yr, mo, dy, hr, mn, sc].some(isNaN)) return null;
824
+ const wholeSec = Math.floor(sc);
825
+ const ms = Math.round((sc - wholeSec) * 1e3);
826
+ return new Date(Date.UTC(yr, mo - 1, dy, hr, mn, wholeSec, ms));
827
+ }
828
+ function snrIndicesWithBand(obsTypes, system) {
829
+ const codes = obsTypes[system];
830
+ if (!codes) return [];
831
+ const result = [];
832
+ for (let i = 0; i < codes.length; i++) {
833
+ const c = codes[i];
834
+ if (c.startsWith("S")) result.push({ idx: i, band: c[1] });
835
+ }
836
+ return result;
837
+ }
838
+ function snrIndicesWithBandV2(obsTypes) {
839
+ const codes = obsTypes["_v2"];
840
+ if (!codes) return [];
841
+ const result = [];
842
+ for (let i = 0; i < codes.length; i++) {
843
+ const c = codes[i];
844
+ if (c.startsWith("S")) result.push({ idx: i, band: c[1] });
845
+ }
846
+ return result;
847
+ }
848
+ function readObsValue(line, index) {
849
+ const start = index * 16;
850
+ if (start + 14 > line.length) return null;
851
+ const raw = line.substring(start, start + 14).trim();
852
+ if (!raw) return null;
853
+ const v = parseFloat(raw);
854
+ return isNaN(v) ? null : v;
855
+ }
856
+ function parseEpochLine3(line) {
857
+ if (line[0] !== ">") return null;
858
+ const yr = parseInt(line.substring(2, 6));
859
+ const mo = parseInt(line.substring(7, 9));
860
+ const dy = parseInt(line.substring(10, 12));
861
+ const hr = parseInt(line.substring(13, 15));
862
+ const mn = parseInt(line.substring(16, 18));
863
+ const sc = parseFloat(line.substring(19, 29));
864
+ const flag = parseInt(line.substring(31, 32));
865
+ const numSats = parseInt(line.substring(32, 35));
866
+ if ([yr, mo, dy, hr, mn].some(isNaN) || isNaN(sc)) return null;
867
+ const wholeSec = Math.floor(sc);
868
+ const ms = Math.round((sc - wholeSec) * 1e3);
869
+ const time = Date.UTC(yr, mo - 1, dy, hr, mn, wholeSec, ms);
870
+ return { time, flag: isNaN(flag) ? 0 : flag, numSats: isNaN(numSats) ? 0 : numSats };
871
+ }
872
+ function parseEpochLine2(line) {
873
+ const yr2 = parseInt(line.substring(1, 3));
874
+ const mo = parseInt(line.substring(4, 6));
875
+ const dy = parseInt(line.substring(7, 9));
876
+ const hr = parseInt(line.substring(10, 12));
877
+ const mn = parseInt(line.substring(13, 15));
878
+ const sc = parseFloat(line.substring(15, 26));
879
+ const flag = parseInt(line.substring(28, 29));
880
+ const numSats = parseInt(line.substring(29, 32));
881
+ if ([yr2, mo, dy, hr, mn].some(isNaN) || isNaN(sc)) return null;
882
+ const yr = yr2 >= 80 ? 1900 + yr2 : 2e3 + yr2;
883
+ const wholeSec = Math.floor(sc);
884
+ const ms = Math.round((sc - wholeSec) * 1e3);
885
+ const time = Date.UTC(yr, mo - 1, dy, hr, mn, wholeSec, ms);
886
+ const satIds = [];
887
+ const satPart = line.substring(32);
888
+ for (let i = 0; i + 3 <= satPart.length; i += 3) {
889
+ const id = satPart.substring(i, i + 3).trim();
890
+ if (id) satIds.push(id);
891
+ }
892
+ return { time, flag: isNaN(flag) ? 0 : flag, numSats: isNaN(numSats) ? 0 : numSats, satIds };
893
+ }
894
+ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode) {
895
+ const skipEpochs = !!workerMode;
896
+ const yield_ = workerMode ? noYield : yieldToMain;
897
+ const decoder = new TextDecoder("ascii");
898
+ let buffer = "";
899
+ let header = null;
900
+ const headerLines = [];
901
+ const epochs = [];
902
+ const satellitesSeen = {};
903
+ let epochInfo = null;
904
+ let satsPerSystem = {};
905
+ let snrValues = [];
906
+ let snrPerSystemAccum = {};
907
+ let snrPerSatAccum = {};
908
+ let snrPerSatBandAccum = {};
909
+ let satLinesRemaining = 0;
910
+ let eventSkipLines = 0;
911
+ let v2SatIds = [];
912
+ let v2SatIndex = 0;
913
+ let v2LinesPerSat = 0;
914
+ let v2CurrentSatLine = 0;
915
+ let v2SnrBandInfo = [];
916
+ let v2ContinuationSatsRemaining = 0;
917
+ let crxPhase = "epoch";
918
+ let crxSatList = [];
919
+ let crxSatIndex = 0;
920
+ let crxPrevEpochLine = "";
921
+ const crxDiffStates = /* @__PURE__ */ new Map();
922
+ function trackSat(sys, prn) {
923
+ if (!satellitesSeen[sys]) satellitesSeen[sys] = /* @__PURE__ */ new Set();
924
+ satellitesSeen[sys].add(prn);
925
+ satsPerSystem[sys] = (satsPerSystem[sys] ?? 0) + 1;
926
+ }
927
+ function pushSnr(sys, prn, val, band) {
928
+ if (skipEpochs) return;
929
+ snrValues.push(val);
930
+ if (!snrPerSystemAccum[sys]) snrPerSystemAccum[sys] = [];
931
+ snrPerSystemAccum[sys].push(val);
932
+ if (!snrPerSatAccum[prn]) snrPerSatAccum[prn] = [];
933
+ snrPerSatAccum[prn].push(val);
934
+ if (band) {
935
+ const key = `${prn}:${band}`;
936
+ if (!snrPerSatBandAccum[key]) snrPerSatBandAccum[key] = [];
937
+ snrPerSatBandAccum[key].push(val);
938
+ }
939
+ }
940
+ function finishEpoch() {
941
+ if (!epochInfo) return;
942
+ if (!skipEpochs) {
943
+ const snrBySys = {};
944
+ for (const [sys, vals] of Object.entries(snrPerSystemAccum)) {
945
+ if (vals.length > 0) snrBySys[sys] = vals.reduce((a, b) => a + b, 0) / vals.length;
946
+ }
947
+ const meanSnr = snrValues.length > 0 ? snrValues.reduce((a, b) => a + b, 0) / snrValues.length : null;
948
+ const snrPerSat = {};
949
+ for (const [prn, vals] of Object.entries(snrPerSatAccum)) {
950
+ if (vals.length > 0) snrPerSat[prn] = vals.reduce((a, b) => a + b, 0) / vals.length;
951
+ }
952
+ const snrPerSatBand = {};
953
+ for (const [key, vals] of Object.entries(snrPerSatBandAccum)) {
954
+ if (vals.length > 0) snrPerSatBand[key] = vals.reduce((a, b) => a + b, 0) / vals.length;
955
+ }
956
+ epochs.push({
957
+ time: epochInfo.time,
958
+ totalSats: Object.values(satsPerSystem).reduce((a, b) => a + b, 0),
959
+ satsPerSystem: { ...satsPerSystem },
960
+ meanSnr,
961
+ snrPerSystem: snrBySys,
962
+ snrPerSat,
963
+ snrPerSatBand
964
+ });
965
+ }
966
+ epochInfo = null;
967
+ satsPerSystem = {};
968
+ snrValues = [];
969
+ snrPerSystemAccum = {};
970
+ snrPerSatAccum = {};
971
+ snrPerSatBandAccum = {};
972
+ }
973
+ function resetEpochAccum(info) {
974
+ epochInfo = info;
975
+ satsPerSystem = {};
976
+ snrValues = [];
977
+ snrPerSystemAccum = {};
978
+ snrPerSatAccum = {};
979
+ snrPerSatBandAccum = {};
980
+ }
981
+ const snrCache = /* @__PURE__ */ new Map();
982
+ function getSnrIndices(sys) {
983
+ let cached = snrCache.get(sys);
984
+ if (!cached) {
985
+ cached = snrIndicesWithBand(header.obsTypes, sys);
986
+ snrCache.set(sys, cached);
987
+ }
988
+ return cached;
989
+ }
990
+ function processSatLineV3(line) {
991
+ if (!header || line.length < 3) return;
992
+ const sys = line[0];
993
+ const prn = line.substring(0, 3);
994
+ trackSat(sys, prn);
995
+ const obsLine = line.substring(3);
996
+ const codes = header.obsTypes[sys] ?? [];
997
+ if (onSatObs && epochInfo) {
998
+ const values = new Array(codes.length);
999
+ for (let i = 0; i < codes.length; i++) values[i] = readObsValue(obsLine, i);
1000
+ if (!skipEpochs) {
1001
+ for (const { idx, band } of getSnrIndices(sys)) {
1002
+ const val = values[idx] ?? null;
1003
+ if (val !== null && val > 0) pushSnr(sys, prn, val, band);
1004
+ }
1005
+ }
1006
+ onSatObs(epochInfo.time, prn, codes, values);
1007
+ } else if (!skipEpochs) {
1008
+ for (const { idx, band } of getSnrIndices(sys)) {
1009
+ const val = readObsValue(obsLine, idx);
1010
+ if (val !== null && val > 0) pushSnr(sys, prn, val, band);
1011
+ }
1012
+ }
1013
+ }
1014
+ let v2ObsAccum = [];
1015
+ function processSatLineV2(line) {
1016
+ if (!header || v2SatIndex >= v2SatIds.length) return;
1017
+ const satId = v2SatIds[v2SatIndex];
1018
+ let sys = satId[0];
1019
+ if (sys === " " || /\d/.test(sys)) sys = "G";
1020
+ const prn = sys + satId.substring(1).padStart(2, "0");
1021
+ if (v2CurrentSatLine === 0) {
1022
+ trackSat(sys, prn);
1023
+ if (onSatObs) v2ObsAccum = [];
1024
+ }
1025
+ const lineOffset = v2CurrentSatLine * 5;
1026
+ for (const { idx, band } of v2SnrBandInfo) {
1027
+ if (idx >= lineOffset && idx < lineOffset + 5) {
1028
+ const val = readObsValue(line, idx - lineOffset);
1029
+ if (val !== null && val > 0) pushSnr(sys, prn, val, band);
1030
+ }
1031
+ }
1032
+ if (onSatObs) {
1033
+ for (let i = 0; i < 5; i++) {
1034
+ if (lineOffset + i < (header.obsTypes["_v2"] ?? []).length) {
1035
+ v2ObsAccum.push(readObsValue(line, i));
1036
+ }
1037
+ }
1038
+ }
1039
+ v2CurrentSatLine++;
1040
+ if (v2CurrentSatLine >= v2LinesPerSat) {
1041
+ if (onSatObs && epochInfo) {
1042
+ const codes = header.obsTypes["_v2"] ?? [];
1043
+ onSatObs(epochInfo.time, prn, codes, v2ObsAccum);
1044
+ }
1045
+ v2CurrentSatLine = 0;
1046
+ v2SatIndex++;
1047
+ }
1048
+ }
1049
+ function processCrxSatDataLine(prn, line) {
1050
+ if (!header) return;
1051
+ const sys = prn[0];
1052
+ trackSat(sys, prn);
1053
+ const ntype = header.version >= 3 ? (header.obsTypes[sys] ?? []).length : (header.obsTypes["_v2"] ?? []).length;
1054
+ if (ntype === 0) return;
1055
+ const { fields } = parseCrxDataLine(line, ntype);
1056
+ let states = crxDiffStates.get(prn);
1057
+ if (!states) {
1058
+ states = new Array(ntype).fill(null);
1059
+ crxDiffStates.set(prn, states);
1060
+ }
1061
+ while (states.length < ntype) states.push(null);
1062
+ const sBandMap = skipEpochs ? null : new Map(
1063
+ (header.version >= 3 ? getSnrIndices(sys) : snrIndicesWithBandV2(header.obsTypes)).map((s) => [s.idx, s.band])
1064
+ );
1065
+ const obsValues = onSatObs ? new Array(ntype).fill(null) : [];
1066
+ for (let j = 0; j < ntype; j++) {
1067
+ const field = fields[j];
1068
+ if (field.empty) continue;
1069
+ const { state, result } = crxDecompress(states[j] ?? null, field);
1070
+ states[j] = state;
1071
+ if (onSatObs) obsValues[j] = result / 1e3;
1072
+ if (sBandMap) {
1073
+ const band = sBandMap.get(j);
1074
+ if (band !== void 0) {
1075
+ const snrFloat = result / 1e3;
1076
+ if (snrFloat > 0) pushSnr(sys, prn, snrFloat, band);
1077
+ }
1078
+ }
1079
+ }
1080
+ if (onSatObs && epochInfo) {
1081
+ const codes = header.version >= 3 ? header.obsTypes[sys] ?? [] : header.obsTypes["_v2"] ?? [];
1082
+ onSatObs(epochInfo.time, prn, codes, obsValues);
1083
+ }
1084
+ }
1085
+ let crxEpochTopFrom = ">";
1086
+ let crxEpochTopTo = ">";
1087
+ let crxEventSkipLines = 0;
1088
+ function processCrxLine(line) {
1089
+ if (!header) return;
1090
+ const isV3 = header.version >= 3;
1091
+ if (header.crxVersion < 3) {
1092
+ crxEpochTopFrom = "&";
1093
+ crxEpochTopTo = " ";
1094
+ }
1095
+ if (crxEventSkipLines > 0) {
1096
+ crxEventSkipLines--;
1097
+ return;
1098
+ }
1099
+ if (crxPhase === "epoch") {
1100
+ if (header.crxVersion >= 3 && line[0] === "&") return;
1101
+ const isInitEpoch = line[0] === crxEpochTopFrom;
1102
+ const isTextDiff = !isInitEpoch && crxPrevEpochLine.length > 0;
1103
+ if (isInitEpoch || isTextDiff) {
1104
+ let epochLine;
1105
+ if (isInitEpoch) {
1106
+ epochLine = crxEpochTopTo + line.substring(1);
1107
+ crxPrevEpochLine = "";
1108
+ } else {
1109
+ epochLine = crxRepair(crxPrevEpochLine, line);
1110
+ }
1111
+ if (isV3) {
1112
+ if (epochLine[0] !== ">") return;
1113
+ const info = parseEpochLine3(epochLine);
1114
+ if (!info) {
1115
+ crxPrevEpochLine = epochLine;
1116
+ return;
1117
+ }
1118
+ if (info.flag >= 2) {
1119
+ crxPrevEpochLine = epochLine;
1120
+ crxEventSkipLines = info.numSats;
1121
+ return;
1122
+ }
1123
+ if (info.flag !== 0) {
1124
+ crxPrevEpochLine = epochLine;
1125
+ return;
1126
+ }
1127
+ resetEpochAccum(info);
1128
+ const satPart = epochLine.substring(41).replace(/\s+$/, "");
1129
+ crxSatList = [];
1130
+ for (let i = 0; i + 3 <= satPart.length; i += 3) {
1131
+ crxSatList.push(satPart.substring(i, i + 3));
1132
+ }
1133
+ crxSatIndex = 0;
1134
+ crxPrevEpochLine = epochLine;
1135
+ crxPhase = "clock";
1136
+ } else {
1137
+ const info = parseEpochLine2(epochLine);
1138
+ if (!info) {
1139
+ crxPrevEpochLine = epochLine;
1140
+ return;
1141
+ }
1142
+ if (info.flag >= 2) {
1143
+ crxPrevEpochLine = epochLine;
1144
+ crxEventSkipLines = info.numSats;
1145
+ return;
1146
+ }
1147
+ if (info.flag !== 0) {
1148
+ crxPrevEpochLine = epochLine;
1149
+ return;
1150
+ }
1151
+ resetEpochAccum(info);
1152
+ crxSatList = (info.satIds ?? []).map((id) => {
1153
+ let sys = id[0];
1154
+ if (sys === " " || /\d/.test(sys)) sys = "G";
1155
+ return sys + id.substring(1).padStart(2, "0");
1156
+ });
1157
+ crxSatIndex = 0;
1158
+ crxPrevEpochLine = epochLine;
1159
+ crxPhase = "clock";
1160
+ }
1161
+ }
1162
+ } else if (crxPhase === "clock") {
1163
+ if (crxSatList.length > 0) {
1164
+ crxPhase = "satdata";
1165
+ } else {
1166
+ crxPhase = "epoch";
1167
+ finishEpoch();
1168
+ }
1169
+ } else if (crxPhase === "satdata") {
1170
+ const prn = crxSatList[crxSatIndex];
1171
+ processCrxSatDataLine(prn, line);
1172
+ crxSatIndex++;
1173
+ if (crxSatIndex >= crxSatList.length) {
1174
+ crxPhase = "epoch";
1175
+ finishEpoch();
1176
+ }
1177
+ }
1178
+ }
1179
+ function processRinexLine(line) {
1180
+ if (!header) return;
1181
+ if (eventSkipLines > 0) {
1182
+ eventSkipLines--;
1183
+ return;
1184
+ }
1185
+ if (header.version >= 3) {
1186
+ if (satLinesRemaining > 0) {
1187
+ processSatLineV3(line);
1188
+ satLinesRemaining--;
1189
+ if (satLinesRemaining === 0) finishEpoch();
1190
+ } else if (line[0] === ">") {
1191
+ const info = parseEpochLine3(line);
1192
+ if (!info) return;
1193
+ if (info.flag >= 2 && info.flag <= 5) {
1194
+ eventSkipLines = info.numSats;
1195
+ return;
1196
+ }
1197
+ if (info.flag === 0) {
1198
+ resetEpochAccum(info);
1199
+ satLinesRemaining = info.numSats;
1200
+ if (info.numSats === 0) finishEpoch();
1201
+ }
1202
+ }
1203
+ } else {
1204
+ if (v2ContinuationSatsRemaining > 0) {
1205
+ const satPart = line.substring(32);
1206
+ for (let i = 0; i + 3 <= satPart.length; i += 3) {
1207
+ const id = satPart.substring(i, i + 3).trim();
1208
+ if (id) v2SatIds.push(id);
1209
+ }
1210
+ v2ContinuationSatsRemaining--;
1211
+ if (v2ContinuationSatsRemaining === 0) {
1212
+ v2SatIndex = 0;
1213
+ v2CurrentSatLine = 0;
1214
+ satLinesRemaining = v2SatIds.length * v2LinesPerSat;
1215
+ if (satLinesRemaining === 0) finishEpoch();
1216
+ }
1217
+ } else if (satLinesRemaining > 0) {
1218
+ processSatLineV2(line);
1219
+ satLinesRemaining--;
1220
+ if (satLinesRemaining === 0) finishEpoch();
1221
+ } else {
1222
+ const info = parseEpochLine2(line);
1223
+ if (!info) return;
1224
+ if (info.flag >= 2 && info.flag <= 5) {
1225
+ eventSkipLines = info.numSats;
1226
+ return;
1227
+ }
1228
+ if (info.flag === 0) {
1229
+ resetEpochAccum(info);
1230
+ v2SatIds = info.satIds ?? [];
1231
+ const v2ObsPerSat = (header.obsTypes["_v2"] ?? []).length;
1232
+ v2LinesPerSat = Math.ceil(v2ObsPerSat / 5) || 1;
1233
+ v2SnrBandInfo = snrIndicesWithBandV2(header.obsTypes);
1234
+ v2SatIndex = 0;
1235
+ v2CurrentSatLine = 0;
1236
+ const continuationLines = Math.max(0, Math.ceil((info.numSats - 12) / 12));
1237
+ if (continuationLines > 0) {
1238
+ v2ContinuationSatsRemaining = continuationLines;
1239
+ } else {
1240
+ satLinesRemaining = v2SatIds.length * v2LinesPerSat;
1241
+ if (satLinesRemaining === 0) finishEpoch();
1242
+ }
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ function processLine(line) {
1248
+ if (!header) return;
1249
+ if (header.isCrx) {
1250
+ processCrxLine(line);
1251
+ } else {
1252
+ processRinexLine(line);
1253
+ }
1254
+ }
1255
+ function processChunkText(text, isLast) {
1256
+ buffer += text;
1257
+ const lastNl = buffer.lastIndexOf("\n");
1258
+ if (lastNl === -1 && !isLast) return;
1259
+ const chunk = isLast ? buffer : buffer.substring(0, lastNl);
1260
+ buffer = isLast ? "" : buffer.substring(lastNl + 1);
1261
+ const lines = chunk.split("\n");
1262
+ for (const rawLine of lines) {
1263
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
1264
+ if (!line && !header) continue;
1265
+ if (!header) {
1266
+ headerLines.push(line);
1267
+ if (line.includes("END OF HEADER")) header = parseHeader(headerLines);
1268
+ continue;
1269
+ }
1270
+ processLine(line);
1271
+ }
1272
+ }
1273
+ const magic = new Uint8Array(await file.slice(0, 2).arrayBuffer());
1274
+ const isGz = magic[0] === 31 && magic[1] === 139;
1275
+ if (magic[0] === 31 && magic[1] === 157) {
1276
+ throw new Error("Unix compress (.Z) files are not supported. Please decompress first (e.g. uncompress or gzip -d).");
1277
+ }
1278
+ if (isGz) {
1279
+ let bytesRead = 0;
1280
+ const raw = file.stream();
1281
+ const decompressed = raw.pipeThrough(new DecompressionStream("gzip"));
1282
+ const reader = decompressed.getReader();
1283
+ for (; ; ) {
1284
+ if (signal?.aborted) {
1285
+ reader.cancel();
1286
+ throw new DOMException("Aborted", "AbortError");
1287
+ }
1288
+ const { done, value } = await reader.read();
1289
+ if (done) break;
1290
+ bytesRead += value.byteLength;
1291
+ processChunkText(decoder.decode(value, { stream: true }), false);
1292
+ onProgress?.(Math.min(99, Math.round(bytesRead / (file.size * 4) * 100)));
1293
+ await yield_();
1294
+ }
1295
+ processChunkText(decoder.decode(), true);
1296
+ } else {
1297
+ for (let offset = 0; offset < file.size; offset += CHUNK_SIZE) {
1298
+ if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
1299
+ const end = Math.min(offset + CHUNK_SIZE, file.size);
1300
+ const slice = file.slice(offset, end);
1301
+ const arrayBuf = await slice.arrayBuffer();
1302
+ processChunkText(decoder.decode(arrayBuf, { stream: end < file.size }), false);
1303
+ onProgress?.(Math.min(99, Math.round(end / file.size * 100)));
1304
+ await yield_();
1305
+ }
1306
+ if (buffer.length > 0) {
1307
+ processChunkText("", true);
1308
+ }
1309
+ }
1310
+ if (epochInfo && satLinesRemaining === 0 && crxPhase === "epoch") finishEpoch();
1311
+ if (!header) throw new Error("No valid RINEX header found (missing END OF HEADER).");
1312
+ onProgress?.(100);
1313
+ return { header, epochs, stats: computeStats(header, epochs, satellitesSeen) };
1314
+ }
1315
+ function computeStats(header, epochs, satellitesSeen) {
1316
+ const n = epochs.length;
1317
+ const startTime = n > 0 ? new Date(epochs[0].time) : header.timeOfFirstObs;
1318
+ const endTime = n > 0 ? new Date(epochs[n - 1].time) : header.timeOfLastObs;
1319
+ let duration = null;
1320
+ if (startTime && endTime) duration = (endTime.getTime() - startTime.getTime()) / 1e3;
1321
+ let interval = header.interval;
1322
+ if (interval === null && n >= 2) interval = (epochs[1].time - epochs[0].time) / 1e3;
1323
+ const uniqueSatsPerSystem = {};
1324
+ let totalUnique = 0;
1325
+ const systems = [];
1326
+ for (const [sys, prns] of Object.entries(satellitesSeen)) {
1327
+ uniqueSatsPerSystem[sys] = prns.size;
1328
+ totalUnique += prns.size;
1329
+ systems.push(sys);
1330
+ }
1331
+ systems.sort(systemCmp);
1332
+ const meanSatellites = n > 0 ? epochs.reduce((s, e) => s + e.totalSats, 0) / n : 0;
1333
+ const snrEpochs = epochs.filter((e) => e.meanSnr !== null);
1334
+ const meanSnr = snrEpochs.length > 0 ? snrEpochs.reduce((s, e) => s + e.meanSnr, 0) / snrEpochs.length : null;
1335
+ return {
1336
+ totalEpochs: n,
1337
+ validEpochs: n,
1338
+ duration,
1339
+ startTime,
1340
+ endTime,
1341
+ interval,
1342
+ uniqueSatellites: totalUnique,
1343
+ uniqueSatsPerSystem,
1344
+ systems,
1345
+ meanSatellites,
1346
+ meanSnr
1347
+ };
1348
+ }
1349
+
1350
+ // src/analysis/quality-analysis.ts
1351
+ async function analyzeQuality(file, header, onProgress, signal) {
1352
+ const mpAccum = new MultipathAccumulator(header);
1353
+ const csAccum = new CycleSlipAccumulator(header, (time, prn, bands) => {
1354
+ mpAccum.notifySlip(time, prn, bands);
1355
+ });
1356
+ const compAccum = new CompletenessAccumulator(header);
1357
+ await parseRinexStream(
1358
+ file,
1359
+ onProgress,
1360
+ signal,
1361
+ (time, prn, codes, values) => {
1362
+ csAccum.onObservation(time, prn, codes, values);
1363
+ mpAccum.onObservation(time, prn, codes, values);
1364
+ compAccum.onObservation(time, prn, codes, values);
1365
+ },
1366
+ true
1367
+ /* workerMode: skip EpochSummary construction */
1368
+ );
1369
+ return {
1370
+ cycleSlips: csAccum.finalize(),
1371
+ completeness: compAccum.finalize(),
1372
+ multipath: mpAccum.finalize()
1373
+ };
1374
+ }
1375
+ // Annotate the CommonJS export names for ESM import in node:
1376
+ 0 && (module.exports = {
1377
+ CompletenessAccumulator,
1378
+ CycleSlipAccumulator,
1379
+ MultipathAccumulator,
1380
+ analyzeQuality
1381
+ });