gnss-js 1.26.0 → 1.28.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.
@@ -0,0 +1,120 @@
1
+ // src/rinex/sp3.ts
2
+ var BAD_CLOCK = 999999.999999;
3
+ function parseSp3(text) {
4
+ const lines = text.split("\n");
5
+ const epochs = [];
6
+ const satellites = {};
7
+ let version = "";
8
+ let intervalSec = 0;
9
+ let timeSystem = "GPS";
10
+ let timeSystemSet = false;
11
+ let epochIdx = -1;
12
+ for (const line of lines) {
13
+ if (!version && line.startsWith("#") && line.charAt(1) !== "#") {
14
+ version = line.charAt(1);
15
+ }
16
+ if (line.startsWith("##")) {
17
+ const f = line.slice(2).trim().split(/\s+/);
18
+ intervalSec = Number(f[2]) || 0;
19
+ } else if (line.startsWith("%c") && !timeSystemSet) {
20
+ const sys = line.slice(9, 12).trim();
21
+ if (sys && !/^c+$/.test(sys)) {
22
+ timeSystem = sys;
23
+ timeSystemSet = true;
24
+ }
25
+ } else if (line.startsWith("*")) {
26
+ const f = line.slice(1).trim().split(/\s+/).map(Number);
27
+ if (f.length >= 6 && f.every(Number.isFinite)) {
28
+ const sec = f[5];
29
+ epochs.push(
30
+ Date.UTC(
31
+ f[0],
32
+ f[1] - 1,
33
+ f[2],
34
+ f[3],
35
+ f[4],
36
+ Math.floor(sec),
37
+ Math.round(sec % 1 * 1e3)
38
+ )
39
+ );
40
+ epochIdx++;
41
+ }
42
+ } else if (line.startsWith("P") && epochIdx >= 0) {
43
+ const prn = line.slice(1, 4).replace(/\s/g, "0");
44
+ const f = line.slice(4).trim().split(/\s+/).map(Number);
45
+ if (f.length < 3) continue;
46
+ const [xKm, yKm, zKm] = f;
47
+ let arr = satellites[prn];
48
+ if (!arr) satellites[prn] = arr = [];
49
+ while (arr.length < epochIdx) arr.push(null);
50
+ if (xKm === 0 && yKm === 0 && zKm === 0) {
51
+ arr.push(null);
52
+ continue;
53
+ }
54
+ const clkUs = f[3];
55
+ arr.push({
56
+ x: xKm * 1e3,
57
+ y: yKm * 1e3,
58
+ z: zKm * 1e3,
59
+ clk: clkUs !== void 0 && Math.abs(clkUs) < BAD_CLOCK ? clkUs * 1e-6 : null
60
+ });
61
+ }
62
+ }
63
+ for (const arr of Object.values(satellites)) {
64
+ while (arr.length < epochs.length) arr.push(null);
65
+ }
66
+ return { version, epochs, intervalSec, timeSystem, satellites };
67
+ }
68
+ function sp3Position(sp3, prn, tMs, order = 9) {
69
+ const samples = sp3.satellites[prn];
70
+ const { epochs } = sp3;
71
+ const n = epochs.length;
72
+ if (!samples || n < order) return null;
73
+ if (tMs < epochs[0] || tMs > epochs[n - 1]) return null;
74
+ let lo = 0;
75
+ let hi = n - 1;
76
+ while (lo < hi) {
77
+ const mid = lo + hi + 1 >> 1;
78
+ if (epochs[mid] <= tMs) lo = mid;
79
+ else hi = mid - 1;
80
+ }
81
+ let start = lo - (order >> 1) + (order % 2 === 0 ? 1 : 0);
82
+ start = Math.max(0, Math.min(n - order, start));
83
+ for (let i = start; i < start + order; i++) {
84
+ if (!samples[i]) return null;
85
+ }
86
+ const t0 = epochs[start];
87
+ const t = (tMs - t0) / 1e3;
88
+ let x = 0;
89
+ let y = 0;
90
+ let z = 0;
91
+ for (let i = 0; i < order; i++) {
92
+ const ti = (epochs[start + i] - t0) / 1e3;
93
+ let w = 1;
94
+ for (let j = 0; j < order; j++) {
95
+ if (j === i) continue;
96
+ const tj = (epochs[start + j] - t0) / 1e3;
97
+ w *= (t - tj) / (ti - tj);
98
+ }
99
+ const s = samples[start + i];
100
+ x += w * s.x;
101
+ y += w * s.y;
102
+ z += w * s.z;
103
+ }
104
+ let clk = null;
105
+ const a = samples[lo];
106
+ const b = samples[Math.min(lo + 1, n - 1)];
107
+ if (a?.clk != null && b?.clk != null) {
108
+ const span = epochs[Math.min(lo + 1, n - 1)] - epochs[lo];
109
+ const f = span > 0 ? (tMs - epochs[lo]) / span : 0;
110
+ clk = a.clk * (1 - f) + b.clk * f;
111
+ } else if (a?.clk != null) {
112
+ clk = a.clk;
113
+ }
114
+ return { x, y, z, clk };
115
+ }
116
+
117
+ export {
118
+ parseSp3,
119
+ sp3Position
120
+ };
@@ -6,6 +6,442 @@ import {
6
6
  GPS_PI
7
7
  } from "./chunk-IQ5OIMQD.js";
8
8
 
9
+ // src/rinex/crx-writer.ts
10
+ var ARC_ORDER = 3;
11
+ function emptyData() {
12
+ return { u: [0, 0, 0, 0], l: [0, 0, 0, 0], order: -1 };
13
+ }
14
+ function cAtol(s) {
15
+ let i = 0;
16
+ while (i < s.length && (s[i] === " " || s[i] === " ")) i++;
17
+ let sign = 1;
18
+ if (s[i] === "+" || s[i] === "-") {
19
+ if (s[i] === "-") sign = -1;
20
+ i++;
21
+ }
22
+ let n = 0;
23
+ let any = false;
24
+ while (i < s.length && s[i] >= "0" && s[i] <= "9") {
25
+ n = n * 10 + (s.charCodeAt(i) - 48);
26
+ i++;
27
+ any = true;
28
+ }
29
+ return any ? sign * n : 0;
30
+ }
31
+ function strdiff(s1, s2) {
32
+ let ds = "";
33
+ const n = Math.min(s1.length, s2.length);
34
+ let i = 0;
35
+ for (; i < n; i++) {
36
+ if (s2[i] === s1[i]) ds += " ";
37
+ else if (s2[i] === " ") ds += "&";
38
+ else ds += s2[i];
39
+ }
40
+ if (s1.length > s2.length) {
41
+ for (let k = i; k < s1.length; k++) ds += s1[k] === " " ? " " : "&";
42
+ } else if (s2.length > s1.length) {
43
+ ds += s2.slice(i);
44
+ }
45
+ let end = ds.length;
46
+ while (end > 0 && ds[end - 1] === " ") end--;
47
+ return ds.slice(0, end);
48
+ }
49
+ function readValue(field14) {
50
+ const c = field14.padEnd(14, " ").split("");
51
+ const p7 = c[7];
52
+ c[10] = c[9];
53
+ c[9] = c[8];
54
+ let l = cAtol(c.slice(9, 14).join(""));
55
+ let u;
56
+ if (p7 === " ") {
57
+ u = 0;
58
+ } else if (p7 === "-") {
59
+ u = 0;
60
+ l = -l;
61
+ } else {
62
+ c[8] = ".";
63
+ u = cAtol(c.slice(0, 14).join(""));
64
+ if (u < 0) l = -l;
65
+ }
66
+ return { u, l };
67
+ }
68
+ function putdiff(dddu, dddl) {
69
+ dddu += Math.trunc(dddl / 1e5);
70
+ dddl %= 1e5;
71
+ if (dddu < 0 && dddl > 0) {
72
+ dddu++;
73
+ dddl -= 1e5;
74
+ } else if (dddu > 0 && dddl < 0) {
75
+ dddu--;
76
+ dddl += 1e5;
77
+ }
78
+ if (dddu === 0) return String(dddl);
79
+ return `${dddu}${String(Math.abs(dddl)).padStart(5, "0")}`;
80
+ }
81
+ function putClock(du, dl, cOrder) {
82
+ du += Math.trunc(dl / 1e8);
83
+ dl %= 1e8;
84
+ if (du < 0 && dl > 0) {
85
+ du++;
86
+ dl -= 1e8;
87
+ } else if (du > 0 && dl < 0) {
88
+ du--;
89
+ dl += 1e8;
90
+ }
91
+ let out = "";
92
+ if (cOrder === 0) out += `${ARC_ORDER}&`;
93
+ if (du === 0) out += String(dl);
94
+ else out += `${du}${String(Math.abs(dl)).padStart(8, "0")}`;
95
+ return out;
96
+ }
97
+ function chop(line) {
98
+ let end = line.length;
99
+ if (end > 0 && line[end - 1] === "\r") end--;
100
+ while (end > 0 && line[end - 1] === " ") end--;
101
+ return line.slice(0, end);
102
+ }
103
+ function readSat(lines, cursor, rinexVersion, ntype, ntypeGnss) {
104
+ let line = chop(lines[cursor.li++] ?? "");
105
+ let maxField;
106
+ let ntypeRec;
107
+ let firstRec;
108
+ let satId = "";
109
+ if (rinexVersion === 2) {
110
+ maxField = 5;
111
+ ntypeRec = ntype;
112
+ firstRec = 0;
113
+ } else {
114
+ satId = line.slice(0, 3);
115
+ const sys = line[0];
116
+ maxField = ntypeRec = ntypeGnss[sys] ?? -1;
117
+ if (maxField < 0) return null;
118
+ firstRec = 3;
119
+ }
120
+ const data = [];
121
+ let flag = "";
122
+ for (let i = 0; i < ntypeRec; i += maxField) {
123
+ const nfield = Math.min(ntypeRec - i, maxField);
124
+ const need = firstRec + 16 * nfield;
125
+ let body = line;
126
+ if (body.length < need) body = body.padEnd(need, " ");
127
+ for (let j = 0; j < nfield; j++) {
128
+ const p = firstRec + j * 16;
129
+ const rec = body.slice(p, p + 16);
130
+ const d = emptyData();
131
+ if (rec[10] === ".") {
132
+ flag += rec[14] ?? " ";
133
+ flag += rec[15] ?? " ";
134
+ const v = readValue(rec.slice(0, 14));
135
+ d.u[0] = v.u;
136
+ d.l[0] = v.l;
137
+ d.order = 0;
138
+ } else if (rec.slice(0, 14).trim() === "") {
139
+ flag += rec[14] ?? " ";
140
+ flag += rec[15] ?? " ";
141
+ d.order = -1;
142
+ } else {
143
+ return null;
144
+ }
145
+ data.push(d);
146
+ }
147
+ if (i + maxField < ntypeRec) {
148
+ line = chop(lines[cursor.li++] ?? "");
149
+ }
150
+ }
151
+ return { sat: { data, flag, ntypeRec }, satId };
152
+ }
153
+ var MONTHS = [
154
+ "Jan",
155
+ "Feb",
156
+ "Mar",
157
+ "Apr",
158
+ "May",
159
+ "Jun",
160
+ "Jul",
161
+ "Aug",
162
+ "Sep",
163
+ "Oct",
164
+ "Nov",
165
+ "Dec"
166
+ ];
167
+ function crinexDate() {
168
+ const d = /* @__PURE__ */ new Date();
169
+ const dd = String(d.getUTCDate()).padStart(2, "0");
170
+ const mon = MONTHS[d.getUTCMonth()];
171
+ const yy = String(d.getUTCFullYear() % 100).padStart(2, "0");
172
+ const hh = String(d.getUTCHours()).padStart(2, "0");
173
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
174
+ return `${dd}-${mon}-${yy} ${hh}:${mm}`;
175
+ }
176
+ function writeCrx(rinexText, opts = {}) {
177
+ const rawLines = rinexText.split("\n");
178
+ const cursor = { li: 0 };
179
+ const out = [];
180
+ const firstLine = chop(rawLines[cursor.li++] ?? "");
181
+ if (firstLine.slice(60, 80) !== "RINEX VERSION / TYPE" || firstLine[20] !== "O") {
182
+ throw new Error("writeCrx: not a RINEX observation file");
183
+ }
184
+ const rinexVersion = parseInt(firstLine, 10);
185
+ const rinexVersionFull = readVersionFull(firstLine);
186
+ if (rinexVersionFull < 0) throw new Error("writeCrx: invalid RINEX version");
187
+ const crxVersion = rinexVersionFull >= 402 ? "3.1" : rinexVersion === 2 ? "1.0" : "3.0";
188
+ out.push(
189
+ `${crxVersion.padEnd(20)}${"COMPACT RINEX FORMAT".padEnd(40)}${"CRINEX VERS / TYPE".padEnd(20)}`
190
+ );
191
+ const prog = (opts.prog ?? "gnss-js writeCrx").slice(0, 40);
192
+ const date = opts.date ?? crinexDate();
193
+ out.push(`${prog.padEnd(40)}${date.padEnd(20)}CRINEX PROG / DATE`);
194
+ out.push(firstLine);
195
+ let ntype = 0;
196
+ const ntypeGnss = {};
197
+ let line;
198
+ do {
199
+ line = chop(rawLines[cursor.li++] ?? "");
200
+ out.push(line);
201
+ const label = line.slice(60).trimStart();
202
+ if (label.startsWith("# / TYPES OF OBSERV") && line[5] !== " ") {
203
+ ntype = parseInt(line, 10);
204
+ } else if (label.startsWith("SYS / # / OBS TYPES") && line[0] !== " ") {
205
+ ntypeGnss[line[0]] = parseInt(line.slice(3), 10);
206
+ }
207
+ if (cursor.li >= rawLines.length) break;
208
+ } while (!line.slice(60).trimStart().startsWith("END OF HEADER"));
209
+ const v2 = rinexVersion === 2;
210
+ const off = v2 ? { event: 28, nsat: 29, satlst: 32, clock: 68, satOld: 32, shiftClk: 1 } : {
211
+ event: 31,
212
+ nsat: 32,
213
+ satlst: 41,
214
+ clock: 41,
215
+ satOld: 41,
216
+ shiftClk: 4,
217
+ psec: 57
218
+ };
219
+ let oldline = "&";
220
+ let nsatOld = 0;
221
+ let clk0 = { u: [0, 0, 0, 0], l: [0, 0, 0, 0] };
222
+ let dy0 = [];
223
+ let flag0 = [];
224
+ let oldpsec = "";
225
+ let clkOrderPrev = -1;
226
+ const resetArcs = () => {
227
+ oldline = "&";
228
+ nsatOld = 0;
229
+ clkOrderPrev = -1;
230
+ };
231
+ for (; ; ) {
232
+ if (cursor.li >= rawLines.length) break;
233
+ let epoch = rawLines[cursor.li];
234
+ if (epoch === void 0) break;
235
+ if (epoch === "" && cursor.li === rawLines.length - 1) break;
236
+ cursor.li++;
237
+ epoch = chop(epoch);
238
+ if (epoch === "") continue;
239
+ let newline;
240
+ if (v2) {
241
+ newline = epoch;
242
+ } else {
243
+ newline = epoch.length < 41 ? epoch.padEnd(41, " ") : epoch;
244
+ }
245
+ const eventFlag = parseInt(newline[off.event] ?? "0", 10) || 0;
246
+ if (eventFlag > 1) {
247
+ putEventData(rawLines, cursor, v2, newline, out, ntypeGnss, (n) => {
248
+ ntype = n;
249
+ });
250
+ resetArcs();
251
+ clk0 = { u: [0, 0, 0, 0], l: [0, 0, 0, 0] };
252
+ dy0 = [];
253
+ flag0 = [];
254
+ continue;
255
+ }
256
+ let newpsec = "";
257
+ if (rinexVersionFull >= 402 && newline.length === 62) {
258
+ const psec = newline.slice(off.psec, off.psec + 5);
259
+ if (/^\d{5}$/.test(psec)) newpsec = psec;
260
+ newline = newline.slice(0, off.psec).replace(/\s+$/, "");
261
+ }
262
+ let clkOrder = -1;
263
+ const clk1 = { u: [0, 0, 0, 0], l: [0, 0, 0, 0] };
264
+ if (newline.length > off.clock) {
265
+ clkOrder = readClock(
266
+ newline.slice(off.clock),
267
+ off.shiftClk,
268
+ clk1,
269
+ clkOrderPrev
270
+ );
271
+ if (clkOrder > -1) {
272
+ newline = newline.slice(0, off.clock).replace(/\s+$/, "");
273
+ if (!v2 && newline.length < 41) newline = newline.padEnd(41, " ");
274
+ }
275
+ }
276
+ clkOrderPrev = clkOrder;
277
+ const nsat = parseInt((newline.slice(off.nsat) || "0").trim(), 10) || 0;
278
+ if (v2 && nsat > 12) {
279
+ newline = readMoreSat(rawLines, cursor, nsat, newline);
280
+ }
281
+ const sats = [];
282
+ const satIds = [];
283
+ let bad = false;
284
+ let satlist = v2 ? newline.slice(off.satlst, off.satlst + nsat * 3) : "";
285
+ for (let i = 0; i < nsat; i++) {
286
+ const r = readSat(rawLines, cursor, rinexVersion, ntype, ntypeGnss);
287
+ if (!r) {
288
+ bad = true;
289
+ break;
290
+ }
291
+ sats.push(r.sat);
292
+ satIds.push(v2 ? satlist.slice(i * 3, i * 3 + 3) : r.satId);
293
+ }
294
+ if (bad) continue;
295
+ if (!v2) satlist = satIds.join("");
296
+ const fullNew = v2 ? newline : newline.slice(0, 41) + satlist;
297
+ const oldSatList = v2 ? oldline.slice(off.satOld, off.satOld + nsatOld * 3) : oldline.slice(off.satOld);
298
+ const sattbl = [];
299
+ const dup = /* @__PURE__ */ new Set();
300
+ for (let i = 0; i < nsat; i++) {
301
+ const id = satIds[i];
302
+ if (dup.has(id)) {
303
+ bad = true;
304
+ break;
305
+ }
306
+ dup.add(id);
307
+ let found = -1;
308
+ for (let j = 0; j < nsatOld; j++) {
309
+ if (oldSatList.slice(j * 3, j * 3 + 3) === id) {
310
+ found = j;
311
+ break;
312
+ }
313
+ }
314
+ sattbl.push(found);
315
+ }
316
+ if (bad) continue;
317
+ out.push(strdiff(oldline, fullNew));
318
+ let clockLine = "";
319
+ if (clkOrder > -1) {
320
+ if (clkOrder > 0) processClock(clk1, clk0, clkOrder);
321
+ clockLine += putClock(clk1.u[clkOrder], clk1.l[clkOrder], clkOrder);
322
+ }
323
+ if (newpsec.length === 5) {
324
+ clockLine += " " + strdiff(oldpsec, newpsec);
325
+ }
326
+ out.push(clockLine);
327
+ const dataLines = buildData(sats, sattbl, dy0, flag0, rinexVersion);
328
+ for (const dl of dataLines) out.push(dl);
329
+ oldline = fullNew;
330
+ nsatOld = nsat;
331
+ clk0 = clk1;
332
+ oldpsec = newpsec;
333
+ dy0 = sats.map((s) => s.data);
334
+ flag0 = sats.map((s) => s.flag);
335
+ }
336
+ return out.join("\n") + "\n";
337
+ }
338
+ function readClock(field, shiftClk, clk1, prevOrder) {
339
+ const c = field.split("");
340
+ if (c[2] !== ".") return -1;
341
+ for (let k = 0; k < shiftClk; k++) c[2 + k] = c[3 + k];
342
+ c[2 + shiftClk] = ".";
343
+ const s = c.join("");
344
+ const m = /(-?\d+)\.(\d+)/.exec(s);
345
+ if (!m) return -1;
346
+ clk1.u[0] = parseInt(m[1], 10);
347
+ clk1.l[0] = parseInt(m[2], 10);
348
+ if (c[0] === "-" || c[1] === "-") clk1.l[0] = -clk1.l[0];
349
+ return Math.min(prevOrder + 1, ARC_ORDER);
350
+ }
351
+ function processClock(clk1, clk0, clkOrder) {
352
+ for (let i = 0; i < clkOrder; i++) {
353
+ clk1.u[i + 1] = clk1.u[i] - clk0.u[i];
354
+ clk1.l[i + 1] = clk1.l[i] - clk0.l[i];
355
+ }
356
+ }
357
+ function takeDiff(py1, py0) {
358
+ py1.order = py0.order;
359
+ if (py1.order < ARC_ORDER) py1.order++;
360
+ for (let k = 0; k < py1.order; k++) {
361
+ py1.u[k + 1] = py1.u[k] - py0.u[k];
362
+ py1.l[k + 1] = py1.l[k] - py0.l[k];
363
+ }
364
+ }
365
+ function buildData(sats, sattbl, dy0, flag0, rinexVersion) {
366
+ const lines = [];
367
+ for (let i = 0; i < sats.length; i++) {
368
+ const sat = sats[i];
369
+ const i0 = sattbl[i];
370
+ const prevFlag = (i0 >= 0 ? flag0[i0] ?? "" : "").split("");
371
+ let line = "";
372
+ for (let j = 0; j < sat.ntypeRec; j++) {
373
+ const py1 = sat.data[j];
374
+ if (py1.order >= 0) {
375
+ if (i0 < 0 || dy0[i0]?.[j]?.order === -1) {
376
+ py1.order = 0;
377
+ line += `${ARC_ORDER}&`;
378
+ } else {
379
+ takeDiff(py1, dy0[i0][j]);
380
+ if (Math.abs(py1.u[py1.order]) > 1e5) {
381
+ py1.order = 0;
382
+ line += `${ARC_ORDER}&`;
383
+ }
384
+ }
385
+ line += putdiff(py1.u[py1.order], py1.l[py1.order]);
386
+ } else if (i0 >= 0 && rinexVersion === 2) {
387
+ prevFlag[j * 2] = " ";
388
+ prevFlag[j * 2 + 1] = " ";
389
+ }
390
+ line += " ";
391
+ }
392
+ if (i0 < 0 && rinexVersion !== 2) {
393
+ for (const ch of sat.flag) line += ch === " " ? "&" : ch;
394
+ } else {
395
+ line += i0 < 0 ? strdiff("", sat.flag) : strdiff(prevFlag.join(""), sat.flag);
396
+ line = line.replace(/ +$/, "");
397
+ }
398
+ lines.push(line);
399
+ }
400
+ return lines;
401
+ }
402
+ function putEventData(lines, cursor, v2, epochLine, out, ntypeGnss, setNtype) {
403
+ if (v2) {
404
+ out.push("&" + epochLine.slice(1));
405
+ if (epochLine.length > 29) {
406
+ const n = parseInt(epochLine.slice(29), 10) || 0;
407
+ for (let i = 0; i < n; i++) {
408
+ const l = chop(lines[cursor.li++] ?? "");
409
+ out.push(l);
410
+ if (l.slice(60).trimStart().startsWith("# / TYPES OF OBSERV") && l[5] !== " ") {
411
+ setNtype(parseInt(l, 10));
412
+ }
413
+ }
414
+ }
415
+ } else {
416
+ out.push(epochLine.replace(/\s+$/, ""));
417
+ const n = parseInt(epochLine.slice(32), 10) || 0;
418
+ for (let i = 0; i < n; i++) {
419
+ const l = chop(lines[cursor.li++] ?? "");
420
+ out.push(l);
421
+ if (l.slice(60).trimStart().startsWith("SYS / # / OBS TYPES") && l[0] !== " ") {
422
+ ntypeGnss[l[0]] = parseInt(l.slice(3), 10);
423
+ }
424
+ }
425
+ }
426
+ }
427
+ function readMoreSat(lines, cursor, n, epochLine) {
428
+ let result = epochLine;
429
+ let remaining = n;
430
+ while (remaining > 12) {
431
+ const l = lines[cursor.li++] ?? "";
432
+ const chopped = chop(l);
433
+ result += chopped[2] === " " ? chopped.slice(32) : chopped;
434
+ remaining -= 12;
435
+ }
436
+ return result;
437
+ }
438
+ function readVersionFull(line) {
439
+ if (line[5] === "2" && line[6] === " ") return 200;
440
+ const d = (c) => c !== void 0 && c >= "0" && c <= "9";
441
+ if (!d(line[5]) || line[6] !== "." || !d(line[7]) || !d(line[8])) return -1;
442
+ return (line.charCodeAt(5) - 48) * 100 + (line.charCodeAt(7) - 48) * 10 + (line.charCodeAt(8) - 48);
443
+ }
444
+
9
445
  // src/rinex/format.ts
10
446
  function padL(s, w) {
11
447
  return s.length >= w ? s.slice(0, w) : s + " ".repeat(w - s.length);
@@ -981,122 +1417,6 @@ function parseIonex(text) {
981
1417
  return { epochs, lats, lons, maps };
982
1418
  }
983
1419
 
984
- // src/rinex/sp3.ts
985
- var BAD_CLOCK = 999999.999999;
986
- function parseSp3(text) {
987
- const lines = text.split("\n");
988
- const epochs = [];
989
- const satellites = {};
990
- let version = "";
991
- let intervalSec = 0;
992
- let timeSystem = "GPS";
993
- let timeSystemSet = false;
994
- let epochIdx = -1;
995
- for (const line of lines) {
996
- if (!version && line.startsWith("#") && line.charAt(1) !== "#") {
997
- version = line.charAt(1);
998
- }
999
- if (line.startsWith("##")) {
1000
- const f = line.slice(2).trim().split(/\s+/);
1001
- intervalSec = Number(f[2]) || 0;
1002
- } else if (line.startsWith("%c") && !timeSystemSet) {
1003
- const sys = line.slice(9, 12).trim();
1004
- if (sys && !/^c+$/.test(sys)) {
1005
- timeSystem = sys;
1006
- timeSystemSet = true;
1007
- }
1008
- } else if (line.startsWith("*")) {
1009
- const f = line.slice(1).trim().split(/\s+/).map(Number);
1010
- if (f.length >= 6 && f.every(Number.isFinite)) {
1011
- const sec = f[5];
1012
- epochs.push(
1013
- Date.UTC(
1014
- f[0],
1015
- f[1] - 1,
1016
- f[2],
1017
- f[3],
1018
- f[4],
1019
- Math.floor(sec),
1020
- Math.round(sec % 1 * 1e3)
1021
- )
1022
- );
1023
- epochIdx++;
1024
- }
1025
- } else if (line.startsWith("P") && epochIdx >= 0) {
1026
- const prn = line.slice(1, 4).replace(/\s/g, "0");
1027
- const f = line.slice(4).trim().split(/\s+/).map(Number);
1028
- if (f.length < 3) continue;
1029
- const [xKm, yKm, zKm] = f;
1030
- let arr = satellites[prn];
1031
- if (!arr) satellites[prn] = arr = [];
1032
- while (arr.length < epochIdx) arr.push(null);
1033
- if (xKm === 0 && yKm === 0 && zKm === 0) {
1034
- arr.push(null);
1035
- continue;
1036
- }
1037
- const clkUs = f[3];
1038
- arr.push({
1039
- x: xKm * 1e3,
1040
- y: yKm * 1e3,
1041
- z: zKm * 1e3,
1042
- clk: clkUs !== void 0 && Math.abs(clkUs) < BAD_CLOCK ? clkUs * 1e-6 : null
1043
- });
1044
- }
1045
- }
1046
- for (const arr of Object.values(satellites)) {
1047
- while (arr.length < epochs.length) arr.push(null);
1048
- }
1049
- return { version, epochs, intervalSec, timeSystem, satellites };
1050
- }
1051
- function sp3Position(sp3, prn, tMs, order = 9) {
1052
- const samples = sp3.satellites[prn];
1053
- const { epochs } = sp3;
1054
- const n = epochs.length;
1055
- if (!samples || n < order) return null;
1056
- if (tMs < epochs[0] || tMs > epochs[n - 1]) return null;
1057
- let lo = 0;
1058
- let hi = n - 1;
1059
- while (lo < hi) {
1060
- const mid = lo + hi + 1 >> 1;
1061
- if (epochs[mid] <= tMs) lo = mid;
1062
- else hi = mid - 1;
1063
- }
1064
- let start = lo - (order >> 1) + (order % 2 === 0 ? 1 : 0);
1065
- start = Math.max(0, Math.min(n - order, start));
1066
- for (let i = start; i < start + order; i++) {
1067
- if (!samples[i]) return null;
1068
- }
1069
- const t0 = epochs[start];
1070
- const t = (tMs - t0) / 1e3;
1071
- let x = 0;
1072
- let y = 0;
1073
- let z = 0;
1074
- for (let i = 0; i < order; i++) {
1075
- const ti = (epochs[start + i] - t0) / 1e3;
1076
- let w = 1;
1077
- for (let j = 0; j < order; j++) {
1078
- if (j === i) continue;
1079
- const tj = (epochs[start + j] - t0) / 1e3;
1080
- w *= (t - tj) / (ti - tj);
1081
- }
1082
- const s = samples[start + i];
1083
- x += w * s.x;
1084
- y += w * s.y;
1085
- z += w * s.z;
1086
- }
1087
- let clk = null;
1088
- const a = samples[lo];
1089
- const b = samples[Math.min(lo + 1, n - 1)];
1090
- if (a?.clk != null && b?.clk != null) {
1091
- const span = epochs[Math.min(lo + 1, n - 1)] - epochs[lo];
1092
- const f = span > 0 ? (tMs - epochs[lo]) / span : 0;
1093
- clk = a.clk * (1 - f) + b.clk * f;
1094
- } else if (a?.clk != null) {
1095
- clk = a.clk;
1096
- }
1097
- return { x, y, z, clk };
1098
- }
1099
-
1100
1420
  // src/rinex/obs-writer-common.ts
1101
1421
  function createGzipLineSink() {
1102
1422
  const encoder = new TextEncoder();
@@ -1472,6 +1792,7 @@ function writeRinex4ObsBlob(header, epochs, obsTypes) {
1472
1792
  }
1473
1793
 
1474
1794
  export {
1795
+ writeCrx,
1475
1796
  padL,
1476
1797
  padR,
1477
1798
  fmtF,
@@ -1483,8 +1804,6 @@ export {
1483
1804
  writeRinexNav,
1484
1805
  writeRinexNav4,
1485
1806
  parseIonex,
1486
- parseSp3,
1487
- sp3Position,
1488
1807
  createGzipLineSink,
1489
1808
  stationHeaderLines,
1490
1809
  writeRinexObsBlob,