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.
- package/LICENSE +23 -0
- package/README.md +171 -5
- package/dist/analysis.cjs +1381 -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-354IRDOG.js +239 -0
- package/dist/chunk-37QNKGTC.js +65 -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-CB6EOOLA.js +753 -0
- package/dist/chunk-HKN3PUGN.js +800 -0
- package/dist/chunk-IS4UUDBV.js +577 -0
- package/dist/chunk-K7WZQFBV.js +236 -0
- package/dist/chunk-LEEU5OIO.js +75 -0
- package/dist/chunk-PRSZIWKM.js +1482 -0
- package/dist/chunk-SDRRAJT5.js +433 -0
- package/dist/chunk-WR7LCB52.js +143 -0
- package/dist/chunk-Y3R57B5P.js +473 -0
- package/dist/chunk-YDMYO3YK.js +220 -0
- package/dist/constants.cjs +394 -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 +334 -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-BUWzfmBy.d.cts +114 -0
- package/dist/ephemeris-BUWzfmBy.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 +5774 -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 +169 -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 +504 -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 +1375 -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 +1519 -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,753 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SYSTEM_NAMES
|
|
3
|
+
} from "./chunk-354IRDOG.js";
|
|
4
|
+
|
|
5
|
+
// src/rinex/crx.ts
|
|
6
|
+
function crxRepair(old, diff) {
|
|
7
|
+
const chars = old.split("");
|
|
8
|
+
for (let i = 0; i < diff.length; i++) {
|
|
9
|
+
const dc = diff[i];
|
|
10
|
+
if (dc === " ") {
|
|
11
|
+
} else if (dc === "&") {
|
|
12
|
+
chars[i] = " ";
|
|
13
|
+
} else {
|
|
14
|
+
chars[i] = dc;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (diff.length > chars.length) {
|
|
18
|
+
for (let i = chars.length; i < diff.length; i++) {
|
|
19
|
+
chars[i] = diff[i] === "&" ? " " : diff[i];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return chars.join("");
|
|
23
|
+
}
|
|
24
|
+
function parseCrxDataLine(line, ntype) {
|
|
25
|
+
const fields = [];
|
|
26
|
+
let pos = 0;
|
|
27
|
+
for (let j = 0; j < ntype; j++) {
|
|
28
|
+
if (pos >= line.length || line[pos] === "\0") {
|
|
29
|
+
fields.push({ empty: true, init: false, arcOrder: -1, value: 0 });
|
|
30
|
+
pos++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (line[pos] === " ") {
|
|
34
|
+
fields.push({ empty: true, init: false, arcOrder: -1, value: 0 });
|
|
35
|
+
pos++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const start = pos;
|
|
39
|
+
while (pos < line.length && line[pos] !== " ") pos++;
|
|
40
|
+
const token = line.substring(start, pos);
|
|
41
|
+
if (pos < line.length && line[pos] === " ") pos++;
|
|
42
|
+
if (token.length >= 3 && token[1] === "&") {
|
|
43
|
+
const arcOrder = parseInt(token[0]);
|
|
44
|
+
const value = parseInt(token.substring(2));
|
|
45
|
+
fields.push({ empty: false, init: true, arcOrder, value: isNaN(value) ? 0 : value });
|
|
46
|
+
} else {
|
|
47
|
+
const value = parseInt(token);
|
|
48
|
+
fields.push({ empty: false, init: false, arcOrder: -1, value: isNaN(value) ? 0 : value });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const flagStr = pos < line.length ? line.substring(pos) : "";
|
|
52
|
+
return { fields, flagStr };
|
|
53
|
+
}
|
|
54
|
+
function crxDecompress(prev, field) {
|
|
55
|
+
if (field.init) {
|
|
56
|
+
const a = new Array(Math.max(field.arcOrder + 1, 1)).fill(0);
|
|
57
|
+
a[0] = field.value;
|
|
58
|
+
return { state: { accum: a, order: 0, arcOrder: field.arcOrder }, result: field.value };
|
|
59
|
+
}
|
|
60
|
+
if (!prev) {
|
|
61
|
+
return { state: { accum: [0], order: 0, arcOrder: 0 }, result: 0 };
|
|
62
|
+
}
|
|
63
|
+
let order = prev.order;
|
|
64
|
+
if (order < prev.arcOrder) order++;
|
|
65
|
+
const accum = [...prev.accum];
|
|
66
|
+
while (accum.length <= order) accum.push(0);
|
|
67
|
+
accum[order] = field.value;
|
|
68
|
+
for (let k = order; k >= 1; k--) {
|
|
69
|
+
accum[k - 1] += accum[k];
|
|
70
|
+
}
|
|
71
|
+
return { state: { accum, order, arcOrder: prev.arcOrder }, result: accum[0] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/rinex/parser.ts
|
|
75
|
+
var CHUNK_SIZE = 2 * 1024 * 1024;
|
|
76
|
+
var yieldToMain = () => new Promise((r) => setTimeout(r, 0));
|
|
77
|
+
var noYield = () => Promise.resolve();
|
|
78
|
+
var SYSTEM_ORDER = ["G", "R", "E", "C", "J", "I", "S"];
|
|
79
|
+
var SYSTEM_RANK = Object.fromEntries(SYSTEM_ORDER.map((s, i) => [s, i]));
|
|
80
|
+
function systemCmp(a, b) {
|
|
81
|
+
const ra = SYSTEM_RANK[a.charAt(0)] ?? 99;
|
|
82
|
+
const rb = SYSTEM_RANK[b.charAt(0)] ?? 99;
|
|
83
|
+
if (ra !== rb) return ra - rb;
|
|
84
|
+
return a.localeCompare(b);
|
|
85
|
+
}
|
|
86
|
+
function systemName(code) {
|
|
87
|
+
return SYSTEM_NAMES[code] ?? code;
|
|
88
|
+
}
|
|
89
|
+
function parseHeader(lines) {
|
|
90
|
+
const h = {
|
|
91
|
+
version: 0,
|
|
92
|
+
type: "",
|
|
93
|
+
satSystem: "",
|
|
94
|
+
markerName: "",
|
|
95
|
+
markerType: "",
|
|
96
|
+
observer: "",
|
|
97
|
+
agency: "",
|
|
98
|
+
receiverNumber: "",
|
|
99
|
+
receiverType: "",
|
|
100
|
+
receiverVersion: "",
|
|
101
|
+
antNumber: "",
|
|
102
|
+
antType: "",
|
|
103
|
+
approxPosition: null,
|
|
104
|
+
antDelta: null,
|
|
105
|
+
interval: null,
|
|
106
|
+
timeOfFirstObs: null,
|
|
107
|
+
timeOfLastObs: null,
|
|
108
|
+
obsTypes: {},
|
|
109
|
+
glonassSlots: {},
|
|
110
|
+
isCrx: false,
|
|
111
|
+
crxVersion: 0
|
|
112
|
+
};
|
|
113
|
+
let currentObsSys = "";
|
|
114
|
+
for (const raw of lines) {
|
|
115
|
+
const label = raw.substring(60).trim();
|
|
116
|
+
const data = raw.substring(0, 60);
|
|
117
|
+
switch (label) {
|
|
118
|
+
case "CRINEX VERS / TYPE": {
|
|
119
|
+
h.isCrx = true;
|
|
120
|
+
h.crxVersion = parseFloat(data.substring(0, 9)) || 0;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "RINEX VERSION / TYPE": {
|
|
124
|
+
h.version = parseFloat(data.substring(0, 9));
|
|
125
|
+
h.type = data.substring(20, 21).trim();
|
|
126
|
+
h.satSystem = data.substring(40, 41).trim();
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "MARKER NAME":
|
|
130
|
+
h.markerName = data.trim();
|
|
131
|
+
break;
|
|
132
|
+
case "MARKER TYPE":
|
|
133
|
+
h.markerType = data.trim();
|
|
134
|
+
break;
|
|
135
|
+
case "OBSERVER / AGENCY":
|
|
136
|
+
h.observer = data.substring(0, 20).trim();
|
|
137
|
+
h.agency = data.substring(20).trim();
|
|
138
|
+
break;
|
|
139
|
+
case "REC # / TYPE / VERS":
|
|
140
|
+
h.receiverNumber = data.substring(0, 20).trim();
|
|
141
|
+
h.receiverType = data.substring(20, 40).trim();
|
|
142
|
+
h.receiverVersion = data.substring(40, 60).trim();
|
|
143
|
+
break;
|
|
144
|
+
case "ANT # / TYPE":
|
|
145
|
+
h.antNumber = data.substring(0, 20).trim();
|
|
146
|
+
h.antType = data.substring(20).trim();
|
|
147
|
+
break;
|
|
148
|
+
case "ANTENNA: DELTA H/E/N": {
|
|
149
|
+
const d = data.trim().split(/\s+/).map(Number);
|
|
150
|
+
if (d.length >= 3 && !d.slice(0, 3).some(isNaN)) {
|
|
151
|
+
h.antDelta = [d[0], d[1], d[2]];
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "APPROX POSITION XYZ": {
|
|
156
|
+
const p = data.trim().split(/\s+/).map(Number);
|
|
157
|
+
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])) {
|
|
158
|
+
h.approxPosition = [p[0], p[1], p[2]];
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "INTERVAL":
|
|
163
|
+
h.interval = parseFloat(data);
|
|
164
|
+
break;
|
|
165
|
+
case "TIME OF FIRST OBS":
|
|
166
|
+
h.timeOfFirstObs = parseHeaderTime(data);
|
|
167
|
+
break;
|
|
168
|
+
case "TIME OF LAST OBS":
|
|
169
|
+
h.timeOfLastObs = parseHeaderTime(data);
|
|
170
|
+
break;
|
|
171
|
+
case "SYS / # / OBS TYPES": {
|
|
172
|
+
const sys = data[0]?.trim();
|
|
173
|
+
if (sys) {
|
|
174
|
+
currentObsSys = sys;
|
|
175
|
+
const count = parseInt(data.substring(3, 6));
|
|
176
|
+
const codes = data.substring(7, 60).trim().split(/\s+/).filter(Boolean);
|
|
177
|
+
h.obsTypes[currentObsSys] = codes;
|
|
178
|
+
void count;
|
|
179
|
+
} else if (currentObsSys && h.obsTypes[currentObsSys]) {
|
|
180
|
+
const codes = data.substring(7, 60).trim().split(/\s+/).filter(Boolean);
|
|
181
|
+
h.obsTypes[currentObsSys].push(...codes);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case "GLONASS SLOT / FRQ #": {
|
|
186
|
+
const nSats = parseInt(data.substring(0, 3));
|
|
187
|
+
void nSats;
|
|
188
|
+
for (let i = 0; i < 8; i++) {
|
|
189
|
+
const off = 4 + i * 7;
|
|
190
|
+
const sat = data.substring(off, off + 3).trim();
|
|
191
|
+
const ch = data.substring(off + 3, off + 7).trim();
|
|
192
|
+
if (sat && sat[0] === "R" && ch) {
|
|
193
|
+
const slot = parseInt(sat.substring(1));
|
|
194
|
+
const k = parseInt(ch);
|
|
195
|
+
if (!isNaN(slot) && !isNaN(k)) h.glonassSlots[slot] = k;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "# / TYPES OF OBSERV": {
|
|
201
|
+
const existing = h.obsTypes["_v2"];
|
|
202
|
+
const codes = data.substring(6, 60).trim().split(/\s+/).filter(Boolean);
|
|
203
|
+
if (!existing || existing.length === 0) {
|
|
204
|
+
h.obsTypes["_v2"] = codes;
|
|
205
|
+
} else {
|
|
206
|
+
existing.push(...codes);
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return h;
|
|
213
|
+
}
|
|
214
|
+
function parseHeaderTime(data) {
|
|
215
|
+
const p = data.trim().split(/\s+/).map(Number);
|
|
216
|
+
if (p.length < 6) return null;
|
|
217
|
+
const yr = p[0], mo = p[1], dy = p[2], hr = p[3], mn = p[4], sc = p[5];
|
|
218
|
+
if ([yr, mo, dy, hr, mn, sc].some(isNaN)) return null;
|
|
219
|
+
const wholeSec = Math.floor(sc);
|
|
220
|
+
const ms = Math.round((sc - wholeSec) * 1e3);
|
|
221
|
+
return new Date(Date.UTC(yr, mo - 1, dy, hr, mn, wholeSec, ms));
|
|
222
|
+
}
|
|
223
|
+
function snrIndicesWithBand(obsTypes, system) {
|
|
224
|
+
const codes = obsTypes[system];
|
|
225
|
+
if (!codes) return [];
|
|
226
|
+
const result = [];
|
|
227
|
+
for (let i = 0; i < codes.length; i++) {
|
|
228
|
+
const c = codes[i];
|
|
229
|
+
if (c.startsWith("S")) result.push({ idx: i, band: c[1] });
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function snrIndicesWithBandV2(obsTypes) {
|
|
234
|
+
const codes = obsTypes["_v2"];
|
|
235
|
+
if (!codes) return [];
|
|
236
|
+
const result = [];
|
|
237
|
+
for (let i = 0; i < codes.length; i++) {
|
|
238
|
+
const c = codes[i];
|
|
239
|
+
if (c.startsWith("S")) result.push({ idx: i, band: c[1] });
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
function readObsValue(line, index) {
|
|
244
|
+
const start = index * 16;
|
|
245
|
+
if (start + 14 > line.length) return null;
|
|
246
|
+
const raw = line.substring(start, start + 14).trim();
|
|
247
|
+
if (!raw) return null;
|
|
248
|
+
const v = parseFloat(raw);
|
|
249
|
+
return isNaN(v) ? null : v;
|
|
250
|
+
}
|
|
251
|
+
function parseEpochLine3(line) {
|
|
252
|
+
if (line[0] !== ">") return null;
|
|
253
|
+
const yr = parseInt(line.substring(2, 6));
|
|
254
|
+
const mo = parseInt(line.substring(7, 9));
|
|
255
|
+
const dy = parseInt(line.substring(10, 12));
|
|
256
|
+
const hr = parseInt(line.substring(13, 15));
|
|
257
|
+
const mn = parseInt(line.substring(16, 18));
|
|
258
|
+
const sc = parseFloat(line.substring(19, 29));
|
|
259
|
+
const flag = parseInt(line.substring(31, 32));
|
|
260
|
+
const numSats = parseInt(line.substring(32, 35));
|
|
261
|
+
if ([yr, mo, dy, hr, mn].some(isNaN) || isNaN(sc)) return null;
|
|
262
|
+
const wholeSec = Math.floor(sc);
|
|
263
|
+
const ms = Math.round((sc - wholeSec) * 1e3);
|
|
264
|
+
const time = Date.UTC(yr, mo - 1, dy, hr, mn, wholeSec, ms);
|
|
265
|
+
return { time, flag: isNaN(flag) ? 0 : flag, numSats: isNaN(numSats) ? 0 : numSats };
|
|
266
|
+
}
|
|
267
|
+
function parseEpochLine2(line) {
|
|
268
|
+
const yr2 = parseInt(line.substring(1, 3));
|
|
269
|
+
const mo = parseInt(line.substring(4, 6));
|
|
270
|
+
const dy = parseInt(line.substring(7, 9));
|
|
271
|
+
const hr = parseInt(line.substring(10, 12));
|
|
272
|
+
const mn = parseInt(line.substring(13, 15));
|
|
273
|
+
const sc = parseFloat(line.substring(15, 26));
|
|
274
|
+
const flag = parseInt(line.substring(28, 29));
|
|
275
|
+
const numSats = parseInt(line.substring(29, 32));
|
|
276
|
+
if ([yr2, mo, dy, hr, mn].some(isNaN) || isNaN(sc)) return null;
|
|
277
|
+
const yr = yr2 >= 80 ? 1900 + yr2 : 2e3 + yr2;
|
|
278
|
+
const wholeSec = Math.floor(sc);
|
|
279
|
+
const ms = Math.round((sc - wholeSec) * 1e3);
|
|
280
|
+
const time = Date.UTC(yr, mo - 1, dy, hr, mn, wholeSec, ms);
|
|
281
|
+
const satIds = [];
|
|
282
|
+
const satPart = line.substring(32);
|
|
283
|
+
for (let i = 0; i + 3 <= satPart.length; i += 3) {
|
|
284
|
+
const id = satPart.substring(i, i + 3).trim();
|
|
285
|
+
if (id) satIds.push(id);
|
|
286
|
+
}
|
|
287
|
+
return { time, flag: isNaN(flag) ? 0 : flag, numSats: isNaN(numSats) ? 0 : numSats, satIds };
|
|
288
|
+
}
|
|
289
|
+
async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode) {
|
|
290
|
+
const skipEpochs = !!workerMode;
|
|
291
|
+
const yield_ = workerMode ? noYield : yieldToMain;
|
|
292
|
+
const decoder = new TextDecoder("ascii");
|
|
293
|
+
let buffer = "";
|
|
294
|
+
let header = null;
|
|
295
|
+
const headerLines = [];
|
|
296
|
+
const epochs = [];
|
|
297
|
+
const satellitesSeen = {};
|
|
298
|
+
let epochInfo = null;
|
|
299
|
+
let satsPerSystem = {};
|
|
300
|
+
let snrValues = [];
|
|
301
|
+
let snrPerSystemAccum = {};
|
|
302
|
+
let snrPerSatAccum = {};
|
|
303
|
+
let snrPerSatBandAccum = {};
|
|
304
|
+
let satLinesRemaining = 0;
|
|
305
|
+
let eventSkipLines = 0;
|
|
306
|
+
let v2SatIds = [];
|
|
307
|
+
let v2SatIndex = 0;
|
|
308
|
+
let v2LinesPerSat = 0;
|
|
309
|
+
let v2CurrentSatLine = 0;
|
|
310
|
+
let v2SnrBandInfo = [];
|
|
311
|
+
let v2ContinuationSatsRemaining = 0;
|
|
312
|
+
let crxPhase = "epoch";
|
|
313
|
+
let crxSatList = [];
|
|
314
|
+
let crxSatIndex = 0;
|
|
315
|
+
let crxPrevEpochLine = "";
|
|
316
|
+
const crxDiffStates = /* @__PURE__ */ new Map();
|
|
317
|
+
function trackSat(sys, prn) {
|
|
318
|
+
if (!satellitesSeen[sys]) satellitesSeen[sys] = /* @__PURE__ */ new Set();
|
|
319
|
+
satellitesSeen[sys].add(prn);
|
|
320
|
+
satsPerSystem[sys] = (satsPerSystem[sys] ?? 0) + 1;
|
|
321
|
+
}
|
|
322
|
+
function pushSnr(sys, prn, val, band) {
|
|
323
|
+
if (skipEpochs) return;
|
|
324
|
+
snrValues.push(val);
|
|
325
|
+
if (!snrPerSystemAccum[sys]) snrPerSystemAccum[sys] = [];
|
|
326
|
+
snrPerSystemAccum[sys].push(val);
|
|
327
|
+
if (!snrPerSatAccum[prn]) snrPerSatAccum[prn] = [];
|
|
328
|
+
snrPerSatAccum[prn].push(val);
|
|
329
|
+
if (band) {
|
|
330
|
+
const key = `${prn}:${band}`;
|
|
331
|
+
if (!snrPerSatBandAccum[key]) snrPerSatBandAccum[key] = [];
|
|
332
|
+
snrPerSatBandAccum[key].push(val);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function finishEpoch() {
|
|
336
|
+
if (!epochInfo) return;
|
|
337
|
+
if (!skipEpochs) {
|
|
338
|
+
const snrBySys = {};
|
|
339
|
+
for (const [sys, vals] of Object.entries(snrPerSystemAccum)) {
|
|
340
|
+
if (vals.length > 0) snrBySys[sys] = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
341
|
+
}
|
|
342
|
+
const meanSnr = snrValues.length > 0 ? snrValues.reduce((a, b) => a + b, 0) / snrValues.length : null;
|
|
343
|
+
const snrPerSat = {};
|
|
344
|
+
for (const [prn, vals] of Object.entries(snrPerSatAccum)) {
|
|
345
|
+
if (vals.length > 0) snrPerSat[prn] = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
346
|
+
}
|
|
347
|
+
const snrPerSatBand = {};
|
|
348
|
+
for (const [key, vals] of Object.entries(snrPerSatBandAccum)) {
|
|
349
|
+
if (vals.length > 0) snrPerSatBand[key] = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
350
|
+
}
|
|
351
|
+
epochs.push({
|
|
352
|
+
time: epochInfo.time,
|
|
353
|
+
totalSats: Object.values(satsPerSystem).reduce((a, b) => a + b, 0),
|
|
354
|
+
satsPerSystem: { ...satsPerSystem },
|
|
355
|
+
meanSnr,
|
|
356
|
+
snrPerSystem: snrBySys,
|
|
357
|
+
snrPerSat,
|
|
358
|
+
snrPerSatBand
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
epochInfo = null;
|
|
362
|
+
satsPerSystem = {};
|
|
363
|
+
snrValues = [];
|
|
364
|
+
snrPerSystemAccum = {};
|
|
365
|
+
snrPerSatAccum = {};
|
|
366
|
+
snrPerSatBandAccum = {};
|
|
367
|
+
}
|
|
368
|
+
function resetEpochAccum(info) {
|
|
369
|
+
epochInfo = info;
|
|
370
|
+
satsPerSystem = {};
|
|
371
|
+
snrValues = [];
|
|
372
|
+
snrPerSystemAccum = {};
|
|
373
|
+
snrPerSatAccum = {};
|
|
374
|
+
snrPerSatBandAccum = {};
|
|
375
|
+
}
|
|
376
|
+
const snrCache = /* @__PURE__ */ new Map();
|
|
377
|
+
function getSnrIndices(sys) {
|
|
378
|
+
let cached = snrCache.get(sys);
|
|
379
|
+
if (!cached) {
|
|
380
|
+
cached = snrIndicesWithBand(header.obsTypes, sys);
|
|
381
|
+
snrCache.set(sys, cached);
|
|
382
|
+
}
|
|
383
|
+
return cached;
|
|
384
|
+
}
|
|
385
|
+
function processSatLineV3(line) {
|
|
386
|
+
if (!header || line.length < 3) return;
|
|
387
|
+
const sys = line[0];
|
|
388
|
+
const prn = line.substring(0, 3);
|
|
389
|
+
trackSat(sys, prn);
|
|
390
|
+
const obsLine = line.substring(3);
|
|
391
|
+
const codes = header.obsTypes[sys] ?? [];
|
|
392
|
+
if (onSatObs && epochInfo) {
|
|
393
|
+
const values = new Array(codes.length);
|
|
394
|
+
for (let i = 0; i < codes.length; i++) values[i] = readObsValue(obsLine, i);
|
|
395
|
+
if (!skipEpochs) {
|
|
396
|
+
for (const { idx, band } of getSnrIndices(sys)) {
|
|
397
|
+
const val = values[idx] ?? null;
|
|
398
|
+
if (val !== null && val > 0) pushSnr(sys, prn, val, band);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
onSatObs(epochInfo.time, prn, codes, values);
|
|
402
|
+
} else if (!skipEpochs) {
|
|
403
|
+
for (const { idx, band } of getSnrIndices(sys)) {
|
|
404
|
+
const val = readObsValue(obsLine, idx);
|
|
405
|
+
if (val !== null && val > 0) pushSnr(sys, prn, val, band);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
let v2ObsAccum = [];
|
|
410
|
+
function processSatLineV2(line) {
|
|
411
|
+
if (!header || v2SatIndex >= v2SatIds.length) return;
|
|
412
|
+
const satId = v2SatIds[v2SatIndex];
|
|
413
|
+
let sys = satId[0];
|
|
414
|
+
if (sys === " " || /\d/.test(sys)) sys = "G";
|
|
415
|
+
const prn = sys + satId.substring(1).padStart(2, "0");
|
|
416
|
+
if (v2CurrentSatLine === 0) {
|
|
417
|
+
trackSat(sys, prn);
|
|
418
|
+
if (onSatObs) v2ObsAccum = [];
|
|
419
|
+
}
|
|
420
|
+
const lineOffset = v2CurrentSatLine * 5;
|
|
421
|
+
for (const { idx, band } of v2SnrBandInfo) {
|
|
422
|
+
if (idx >= lineOffset && idx < lineOffset + 5) {
|
|
423
|
+
const val = readObsValue(line, idx - lineOffset);
|
|
424
|
+
if (val !== null && val > 0) pushSnr(sys, prn, val, band);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (onSatObs) {
|
|
428
|
+
for (let i = 0; i < 5; i++) {
|
|
429
|
+
if (lineOffset + i < (header.obsTypes["_v2"] ?? []).length) {
|
|
430
|
+
v2ObsAccum.push(readObsValue(line, i));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
v2CurrentSatLine++;
|
|
435
|
+
if (v2CurrentSatLine >= v2LinesPerSat) {
|
|
436
|
+
if (onSatObs && epochInfo) {
|
|
437
|
+
const codes = header.obsTypes["_v2"] ?? [];
|
|
438
|
+
onSatObs(epochInfo.time, prn, codes, v2ObsAccum);
|
|
439
|
+
}
|
|
440
|
+
v2CurrentSatLine = 0;
|
|
441
|
+
v2SatIndex++;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function processCrxSatDataLine(prn, line) {
|
|
445
|
+
if (!header) return;
|
|
446
|
+
const sys = prn[0];
|
|
447
|
+
trackSat(sys, prn);
|
|
448
|
+
const ntype = header.version >= 3 ? (header.obsTypes[sys] ?? []).length : (header.obsTypes["_v2"] ?? []).length;
|
|
449
|
+
if (ntype === 0) return;
|
|
450
|
+
const { fields } = parseCrxDataLine(line, ntype);
|
|
451
|
+
let states = crxDiffStates.get(prn);
|
|
452
|
+
if (!states) {
|
|
453
|
+
states = new Array(ntype).fill(null);
|
|
454
|
+
crxDiffStates.set(prn, states);
|
|
455
|
+
}
|
|
456
|
+
while (states.length < ntype) states.push(null);
|
|
457
|
+
const sBandMap = skipEpochs ? null : new Map(
|
|
458
|
+
(header.version >= 3 ? getSnrIndices(sys) : snrIndicesWithBandV2(header.obsTypes)).map((s) => [s.idx, s.band])
|
|
459
|
+
);
|
|
460
|
+
const obsValues = onSatObs ? new Array(ntype).fill(null) : [];
|
|
461
|
+
for (let j = 0; j < ntype; j++) {
|
|
462
|
+
const field = fields[j];
|
|
463
|
+
if (field.empty) continue;
|
|
464
|
+
const { state, result } = crxDecompress(states[j] ?? null, field);
|
|
465
|
+
states[j] = state;
|
|
466
|
+
if (onSatObs) obsValues[j] = result / 1e3;
|
|
467
|
+
if (sBandMap) {
|
|
468
|
+
const band = sBandMap.get(j);
|
|
469
|
+
if (band !== void 0) {
|
|
470
|
+
const snrFloat = result / 1e3;
|
|
471
|
+
if (snrFloat > 0) pushSnr(sys, prn, snrFloat, band);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (onSatObs && epochInfo) {
|
|
476
|
+
const codes = header.version >= 3 ? header.obsTypes[sys] ?? [] : header.obsTypes["_v2"] ?? [];
|
|
477
|
+
onSatObs(epochInfo.time, prn, codes, obsValues);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
let crxEpochTopFrom = ">";
|
|
481
|
+
let crxEpochTopTo = ">";
|
|
482
|
+
let crxEventSkipLines = 0;
|
|
483
|
+
function processCrxLine(line) {
|
|
484
|
+
if (!header) return;
|
|
485
|
+
const isV3 = header.version >= 3;
|
|
486
|
+
if (header.crxVersion < 3) {
|
|
487
|
+
crxEpochTopFrom = "&";
|
|
488
|
+
crxEpochTopTo = " ";
|
|
489
|
+
}
|
|
490
|
+
if (crxEventSkipLines > 0) {
|
|
491
|
+
crxEventSkipLines--;
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (crxPhase === "epoch") {
|
|
495
|
+
if (header.crxVersion >= 3 && line[0] === "&") return;
|
|
496
|
+
const isInitEpoch = line[0] === crxEpochTopFrom;
|
|
497
|
+
const isTextDiff = !isInitEpoch && crxPrevEpochLine.length > 0;
|
|
498
|
+
if (isInitEpoch || isTextDiff) {
|
|
499
|
+
let epochLine;
|
|
500
|
+
if (isInitEpoch) {
|
|
501
|
+
epochLine = crxEpochTopTo + line.substring(1);
|
|
502
|
+
crxPrevEpochLine = "";
|
|
503
|
+
} else {
|
|
504
|
+
epochLine = crxRepair(crxPrevEpochLine, line);
|
|
505
|
+
}
|
|
506
|
+
if (isV3) {
|
|
507
|
+
if (epochLine[0] !== ">") return;
|
|
508
|
+
const info = parseEpochLine3(epochLine);
|
|
509
|
+
if (!info) {
|
|
510
|
+
crxPrevEpochLine = epochLine;
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (info.flag >= 2) {
|
|
514
|
+
crxPrevEpochLine = epochLine;
|
|
515
|
+
crxEventSkipLines = info.numSats;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (info.flag !== 0) {
|
|
519
|
+
crxPrevEpochLine = epochLine;
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
resetEpochAccum(info);
|
|
523
|
+
const satPart = epochLine.substring(41).replace(/\s+$/, "");
|
|
524
|
+
crxSatList = [];
|
|
525
|
+
for (let i = 0; i + 3 <= satPart.length; i += 3) {
|
|
526
|
+
crxSatList.push(satPart.substring(i, i + 3));
|
|
527
|
+
}
|
|
528
|
+
crxSatIndex = 0;
|
|
529
|
+
crxPrevEpochLine = epochLine;
|
|
530
|
+
crxPhase = "clock";
|
|
531
|
+
} else {
|
|
532
|
+
const info = parseEpochLine2(epochLine);
|
|
533
|
+
if (!info) {
|
|
534
|
+
crxPrevEpochLine = epochLine;
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (info.flag >= 2) {
|
|
538
|
+
crxPrevEpochLine = epochLine;
|
|
539
|
+
crxEventSkipLines = info.numSats;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (info.flag !== 0) {
|
|
543
|
+
crxPrevEpochLine = epochLine;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
resetEpochAccum(info);
|
|
547
|
+
crxSatList = (info.satIds ?? []).map((id) => {
|
|
548
|
+
let sys = id[0];
|
|
549
|
+
if (sys === " " || /\d/.test(sys)) sys = "G";
|
|
550
|
+
return sys + id.substring(1).padStart(2, "0");
|
|
551
|
+
});
|
|
552
|
+
crxSatIndex = 0;
|
|
553
|
+
crxPrevEpochLine = epochLine;
|
|
554
|
+
crxPhase = "clock";
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} else if (crxPhase === "clock") {
|
|
558
|
+
if (crxSatList.length > 0) {
|
|
559
|
+
crxPhase = "satdata";
|
|
560
|
+
} else {
|
|
561
|
+
crxPhase = "epoch";
|
|
562
|
+
finishEpoch();
|
|
563
|
+
}
|
|
564
|
+
} else if (crxPhase === "satdata") {
|
|
565
|
+
const prn = crxSatList[crxSatIndex];
|
|
566
|
+
processCrxSatDataLine(prn, line);
|
|
567
|
+
crxSatIndex++;
|
|
568
|
+
if (crxSatIndex >= crxSatList.length) {
|
|
569
|
+
crxPhase = "epoch";
|
|
570
|
+
finishEpoch();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function processRinexLine(line) {
|
|
575
|
+
if (!header) return;
|
|
576
|
+
if (eventSkipLines > 0) {
|
|
577
|
+
eventSkipLines--;
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (header.version >= 3) {
|
|
581
|
+
if (satLinesRemaining > 0) {
|
|
582
|
+
processSatLineV3(line);
|
|
583
|
+
satLinesRemaining--;
|
|
584
|
+
if (satLinesRemaining === 0) finishEpoch();
|
|
585
|
+
} else if (line[0] === ">") {
|
|
586
|
+
const info = parseEpochLine3(line);
|
|
587
|
+
if (!info) return;
|
|
588
|
+
if (info.flag >= 2 && info.flag <= 5) {
|
|
589
|
+
eventSkipLines = info.numSats;
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (info.flag === 0) {
|
|
593
|
+
resetEpochAccum(info);
|
|
594
|
+
satLinesRemaining = info.numSats;
|
|
595
|
+
if (info.numSats === 0) finishEpoch();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
if (v2ContinuationSatsRemaining > 0) {
|
|
600
|
+
const satPart = line.substring(32);
|
|
601
|
+
for (let i = 0; i + 3 <= satPart.length; i += 3) {
|
|
602
|
+
const id = satPart.substring(i, i + 3).trim();
|
|
603
|
+
if (id) v2SatIds.push(id);
|
|
604
|
+
}
|
|
605
|
+
v2ContinuationSatsRemaining--;
|
|
606
|
+
if (v2ContinuationSatsRemaining === 0) {
|
|
607
|
+
v2SatIndex = 0;
|
|
608
|
+
v2CurrentSatLine = 0;
|
|
609
|
+
satLinesRemaining = v2SatIds.length * v2LinesPerSat;
|
|
610
|
+
if (satLinesRemaining === 0) finishEpoch();
|
|
611
|
+
}
|
|
612
|
+
} else if (satLinesRemaining > 0) {
|
|
613
|
+
processSatLineV2(line);
|
|
614
|
+
satLinesRemaining--;
|
|
615
|
+
if (satLinesRemaining === 0) finishEpoch();
|
|
616
|
+
} else {
|
|
617
|
+
const info = parseEpochLine2(line);
|
|
618
|
+
if (!info) return;
|
|
619
|
+
if (info.flag >= 2 && info.flag <= 5) {
|
|
620
|
+
eventSkipLines = info.numSats;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (info.flag === 0) {
|
|
624
|
+
resetEpochAccum(info);
|
|
625
|
+
v2SatIds = info.satIds ?? [];
|
|
626
|
+
const v2ObsPerSat = (header.obsTypes["_v2"] ?? []).length;
|
|
627
|
+
v2LinesPerSat = Math.ceil(v2ObsPerSat / 5) || 1;
|
|
628
|
+
v2SnrBandInfo = snrIndicesWithBandV2(header.obsTypes);
|
|
629
|
+
v2SatIndex = 0;
|
|
630
|
+
v2CurrentSatLine = 0;
|
|
631
|
+
const continuationLines = Math.max(0, Math.ceil((info.numSats - 12) / 12));
|
|
632
|
+
if (continuationLines > 0) {
|
|
633
|
+
v2ContinuationSatsRemaining = continuationLines;
|
|
634
|
+
} else {
|
|
635
|
+
satLinesRemaining = v2SatIds.length * v2LinesPerSat;
|
|
636
|
+
if (satLinesRemaining === 0) finishEpoch();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function processLine(line) {
|
|
643
|
+
if (!header) return;
|
|
644
|
+
if (header.isCrx) {
|
|
645
|
+
processCrxLine(line);
|
|
646
|
+
} else {
|
|
647
|
+
processRinexLine(line);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function processChunkText(text, isLast) {
|
|
651
|
+
buffer += text;
|
|
652
|
+
const lastNl = buffer.lastIndexOf("\n");
|
|
653
|
+
if (lastNl === -1 && !isLast) return;
|
|
654
|
+
const chunk = isLast ? buffer : buffer.substring(0, lastNl);
|
|
655
|
+
buffer = isLast ? "" : buffer.substring(lastNl + 1);
|
|
656
|
+
const lines = chunk.split("\n");
|
|
657
|
+
for (const rawLine of lines) {
|
|
658
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
659
|
+
if (!line && !header) continue;
|
|
660
|
+
if (!header) {
|
|
661
|
+
headerLines.push(line);
|
|
662
|
+
if (line.includes("END OF HEADER")) header = parseHeader(headerLines);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
processLine(line);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
const magic = new Uint8Array(await file.slice(0, 2).arrayBuffer());
|
|
669
|
+
const isGz = magic[0] === 31 && magic[1] === 139;
|
|
670
|
+
if (magic[0] === 31 && magic[1] === 157) {
|
|
671
|
+
throw new Error("Unix compress (.Z) files are not supported. Please decompress first (e.g. uncompress or gzip -d).");
|
|
672
|
+
}
|
|
673
|
+
if (isGz) {
|
|
674
|
+
let bytesRead = 0;
|
|
675
|
+
const raw = file.stream();
|
|
676
|
+
const decompressed = raw.pipeThrough(new DecompressionStream("gzip"));
|
|
677
|
+
const reader = decompressed.getReader();
|
|
678
|
+
for (; ; ) {
|
|
679
|
+
if (signal?.aborted) {
|
|
680
|
+
reader.cancel();
|
|
681
|
+
throw new DOMException("Aborted", "AbortError");
|
|
682
|
+
}
|
|
683
|
+
const { done, value } = await reader.read();
|
|
684
|
+
if (done) break;
|
|
685
|
+
bytesRead += value.byteLength;
|
|
686
|
+
processChunkText(decoder.decode(value, { stream: true }), false);
|
|
687
|
+
onProgress?.(Math.min(99, Math.round(bytesRead / (file.size * 4) * 100)));
|
|
688
|
+
await yield_();
|
|
689
|
+
}
|
|
690
|
+
processChunkText(decoder.decode(), true);
|
|
691
|
+
} else {
|
|
692
|
+
for (let offset = 0; offset < file.size; offset += CHUNK_SIZE) {
|
|
693
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
694
|
+
const end = Math.min(offset + CHUNK_SIZE, file.size);
|
|
695
|
+
const slice = file.slice(offset, end);
|
|
696
|
+
const arrayBuf = await slice.arrayBuffer();
|
|
697
|
+
processChunkText(decoder.decode(arrayBuf, { stream: end < file.size }), false);
|
|
698
|
+
onProgress?.(Math.min(99, Math.round(end / file.size * 100)));
|
|
699
|
+
await yield_();
|
|
700
|
+
}
|
|
701
|
+
if (buffer.length > 0) {
|
|
702
|
+
processChunkText("", true);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (epochInfo && satLinesRemaining === 0 && crxPhase === "epoch") finishEpoch();
|
|
706
|
+
if (!header) throw new Error("No valid RINEX header found (missing END OF HEADER).");
|
|
707
|
+
onProgress?.(100);
|
|
708
|
+
return { header, epochs, stats: computeStats(header, epochs, satellitesSeen) };
|
|
709
|
+
}
|
|
710
|
+
function computeStats(header, epochs, satellitesSeen) {
|
|
711
|
+
const n = epochs.length;
|
|
712
|
+
const startTime = n > 0 ? new Date(epochs[0].time) : header.timeOfFirstObs;
|
|
713
|
+
const endTime = n > 0 ? new Date(epochs[n - 1].time) : header.timeOfLastObs;
|
|
714
|
+
let duration = null;
|
|
715
|
+
if (startTime && endTime) duration = (endTime.getTime() - startTime.getTime()) / 1e3;
|
|
716
|
+
let interval = header.interval;
|
|
717
|
+
if (interval === null && n >= 2) interval = (epochs[1].time - epochs[0].time) / 1e3;
|
|
718
|
+
const uniqueSatsPerSystem = {};
|
|
719
|
+
let totalUnique = 0;
|
|
720
|
+
const systems = [];
|
|
721
|
+
for (const [sys, prns] of Object.entries(satellitesSeen)) {
|
|
722
|
+
uniqueSatsPerSystem[sys] = prns.size;
|
|
723
|
+
totalUnique += prns.size;
|
|
724
|
+
systems.push(sys);
|
|
725
|
+
}
|
|
726
|
+
systems.sort(systemCmp);
|
|
727
|
+
const meanSatellites = n > 0 ? epochs.reduce((s, e) => s + e.totalSats, 0) / n : 0;
|
|
728
|
+
const snrEpochs = epochs.filter((e) => e.meanSnr !== null);
|
|
729
|
+
const meanSnr = snrEpochs.length > 0 ? snrEpochs.reduce((s, e) => s + e.meanSnr, 0) / snrEpochs.length : null;
|
|
730
|
+
return {
|
|
731
|
+
totalEpochs: n,
|
|
732
|
+
validEpochs: n,
|
|
733
|
+
duration,
|
|
734
|
+
startTime,
|
|
735
|
+
endTime,
|
|
736
|
+
interval,
|
|
737
|
+
uniqueSatellites: totalUnique,
|
|
738
|
+
uniqueSatsPerSystem,
|
|
739
|
+
systems,
|
|
740
|
+
meanSatellites,
|
|
741
|
+
meanSnr
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export {
|
|
746
|
+
crxRepair,
|
|
747
|
+
parseCrxDataLine,
|
|
748
|
+
crxDecompress,
|
|
749
|
+
SYSTEM_ORDER,
|
|
750
|
+
systemCmp,
|
|
751
|
+
systemName,
|
|
752
|
+
parseRinexStream
|
|
753
|
+
};
|