gnss-js 1.18.0 → 1.19.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/dist/index.cjs CHANGED
@@ -138,6 +138,7 @@ __export(src_exports, {
138
138
  ephInfoToEphemeris: () => ephInfoToEphemeris,
139
139
  euclidean3D: () => euclidean3D,
140
140
  fetchSourcetable: () => fetchSourcetable,
141
+ fmtD: () => fmtD,
141
142
  fmtF: () => fmtF,
142
143
  formatUTCTime: () => formatUTCTime,
143
144
  frequencyLabel: () => frequencyLabel,
@@ -265,6 +266,7 @@ __export(src_exports, {
265
266
  writeRinex2ObsBlob: () => writeRinex2ObsBlob,
266
267
  writeRinex4ObsBlob: () => writeRinex4ObsBlob,
267
268
  writeRinexNav: () => writeRinexNav,
269
+ writeRinexNav4: () => writeRinexNav4,
268
270
  writeRinexObsBlob: () => writeRinexObsBlob
269
271
  });
270
272
  module.exports = __toCommonJS(src_exports);
@@ -2185,6 +2187,343 @@ var WarningAccumulator = class {
2185
2187
  }
2186
2188
  };
2187
2189
 
2190
+ // src/navbits/index.ts
2191
+ var GPS_PI = 3.1415926535898;
2192
+ var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
2193
+ var SEC_PER_WEEK = 7 * 86400;
2194
+ function getBitU(buff, pos, len) {
2195
+ let bits = 0;
2196
+ for (let i = pos; i < pos + len; i++) {
2197
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
2198
+ }
2199
+ return bits;
2200
+ }
2201
+ function getBitS(buff, pos, len) {
2202
+ const bits = getBitU(buff, pos, len);
2203
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
2204
+ return bits - 2 ** len;
2205
+ }
2206
+ function setBitU(buff, pos, len, data) {
2207
+ let mask = 2 ** (len - 1);
2208
+ for (let i = pos; i < pos + len; i++, mask /= 2) {
2209
+ if (data >= mask) {
2210
+ buff[i >> 3] |= 1 << 7 - (i & 7);
2211
+ data -= mask;
2212
+ } else {
2213
+ buff[i >> 3] &= ~(1 << 7 - (i & 7));
2214
+ }
2215
+ }
2216
+ }
2217
+ function decodeGpsLnavFrame(subframes, opts = {}) {
2218
+ if (subframes.length < 90) return null;
2219
+ const b = subframes;
2220
+ let i = 24;
2221
+ const tow1 = getBitU(b, i, 17) * 6;
2222
+ i += 17 + 2;
2223
+ const id1 = getBitU(b, i, 3);
2224
+ i += 3 + 2;
2225
+ const week10 = getBitU(b, i, 10);
2226
+ i += 10;
2227
+ i += 2;
2228
+ i += 4;
2229
+ const svHealth = getBitU(b, i, 6);
2230
+ i += 6;
2231
+ const iodc0 = getBitU(b, i, 2);
2232
+ i += 2;
2233
+ i += 1 + 87;
2234
+ const tgdRaw = getBitS(b, i, 8);
2235
+ i += 8;
2236
+ const iodc1 = getBitU(b, i, 8);
2237
+ i += 8;
2238
+ const tocSec = getBitU(b, i, 16) * 16;
2239
+ i += 16;
2240
+ const af2 = getBitS(b, i, 8) * 2 ** -55;
2241
+ i += 8;
2242
+ const af1 = getBitS(b, i, 16) * 2 ** -43;
2243
+ i += 16;
2244
+ const af0 = getBitS(b, i, 22) * 2 ** -31;
2245
+ i = 240 + 24;
2246
+ i += 17 + 2;
2247
+ const id2 = getBitU(b, i, 3);
2248
+ i += 3 + 2;
2249
+ const iode = getBitU(b, i, 8);
2250
+ i += 8;
2251
+ const crs = getBitS(b, i, 16) * 2 ** -5;
2252
+ i += 16;
2253
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
2254
+ i += 16;
2255
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
2256
+ i += 32;
2257
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
2258
+ i += 16;
2259
+ const e = getBitU(b, i, 32) * 2 ** -33;
2260
+ i += 32;
2261
+ const cus = getBitS(b, i, 16) * 2 ** -29;
2262
+ i += 16;
2263
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
2264
+ i += 32;
2265
+ const toes = getBitU(b, i, 16) * 16;
2266
+ i = 480 + 24;
2267
+ i += 17 + 2;
2268
+ const id3 = getBitU(b, i, 3);
2269
+ i += 3 + 2;
2270
+ const cic = getBitS(b, i, 16) * 2 ** -29;
2271
+ i += 16;
2272
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
2273
+ i += 32;
2274
+ const cis = getBitS(b, i, 16) * 2 ** -29;
2275
+ i += 16;
2276
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
2277
+ i += 32;
2278
+ const crc = getBitS(b, i, 16) * 2 ** -5;
2279
+ i += 16;
2280
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
2281
+ i += 32;
2282
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
2283
+ i += 24;
2284
+ const iode3 = getBitU(b, i, 8);
2285
+ i += 8;
2286
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
2287
+ const iodc = iodc0 * 256 + iodc1;
2288
+ if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
2289
+ if (iode3 !== iode || iode !== (iodc & 255)) return null;
2290
+ const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
2291
+ let week = week10 + 1024 * Math.round((ref - week10) / 1024);
2292
+ if (toes < tow1 - 302400) week++;
2293
+ else if (toes > tow1 + 302400) week--;
2294
+ const prn = opts.prn ?? "G00";
2295
+ const tocDate = new Date(
2296
+ GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
2297
+ );
2298
+ return {
2299
+ system: prn[0] === "J" ? "J" : "G",
2300
+ prn,
2301
+ tocDate,
2302
+ // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
2303
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
2304
+ af0,
2305
+ af1,
2306
+ af2,
2307
+ iode,
2308
+ crs,
2309
+ deltaN,
2310
+ m0,
2311
+ cuc,
2312
+ e,
2313
+ cus,
2314
+ sqrtA,
2315
+ toe: toes,
2316
+ cic,
2317
+ omega0,
2318
+ cis,
2319
+ i0,
2320
+ crc,
2321
+ omega,
2322
+ omegaDot,
2323
+ idot,
2324
+ week,
2325
+ svHealth,
2326
+ tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
2327
+ // IS-GPS-200: -128 reserved
2328
+ };
2329
+ }
2330
+
2331
+ // src/navbits/cnav.ts
2332
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
2333
+ var SEC_PER_WEEK2 = 7 * 86400;
2334
+ var HALF_WEEK = 302400;
2335
+ var CNAV_PREAMBLE = 139;
2336
+ var CNAV_A_REF = 26559710;
2337
+ var CNAV_OMEGA_DOT_REF = -26e-10;
2338
+ function crc24q(buff, bitLen) {
2339
+ let crc = 0;
2340
+ for (let i = 0; i < bitLen; i++) {
2341
+ crc ^= (buff[i >> 3] >> 7 - (i & 7) & 1) << 23;
2342
+ const msb = crc & 8388608;
2343
+ crc = crc << 1 & 16777215;
2344
+ if (msb) crc ^= 8801531;
2345
+ }
2346
+ return crc;
2347
+ }
2348
+ function cnavCrcOk(msg) {
2349
+ if (msg.length < 38) return false;
2350
+ return crc24q(msg, 276) === getBitU(msg, 276, 24);
2351
+ }
2352
+ function getBitU33(b, pos) {
2353
+ return getBitU(b, pos, 1) * 2 ** 32 + getBitU(b, pos + 1, 32);
2354
+ }
2355
+ function getBitS33(b, pos) {
2356
+ const u = getBitU33(b, pos);
2357
+ return u < 2 ** 32 ? u : u - 2 ** 33;
2358
+ }
2359
+ function decodeType10(b, tow) {
2360
+ return {
2361
+ tow,
2362
+ week: getBitU(b, 38, 13),
2363
+ health: getBitU(b, 51, 3),
2364
+ top: getBitU(b, 54, 11) * 300,
2365
+ uraEd: getBitS(b, 65, 5),
2366
+ toe: getBitU(b, 70, 11) * 300,
2367
+ deltaA: getBitS(b, 81, 26) * 2 ** -9,
2368
+ aDot: getBitS(b, 107, 25) * 2 ** -21,
2369
+ deltaN0: getBitS(b, 132, 17) * 2 ** -44 * GPS_PI,
2370
+ deltaN0Dot: getBitS(b, 149, 23) * 2 ** -57 * GPS_PI,
2371
+ m0: getBitS33(b, 172) * 2 ** -32 * GPS_PI,
2372
+ e: getBitU33(b, 205) * 2 ** -34,
2373
+ omega: getBitS33(b, 238) * 2 ** -32 * GPS_PI,
2374
+ integrityFlag: getBitU(b, 271, 1) === 1,
2375
+ l2cPhasing: getBitU(b, 272, 1) === 1
2376
+ };
2377
+ }
2378
+ function decodeType11(b) {
2379
+ return {
2380
+ toe: getBitU(b, 38, 11) * 300,
2381
+ omega0: getBitS33(b, 49) * 2 ** -32 * GPS_PI,
2382
+ i0: getBitS33(b, 82) * 2 ** -32 * GPS_PI,
2383
+ deltaOmegaDot: getBitS(b, 115, 17) * 2 ** -44 * GPS_PI,
2384
+ i0Dot: getBitS(b, 132, 15) * 2 ** -44 * GPS_PI,
2385
+ cis: getBitS(b, 147, 16) * 2 ** -30,
2386
+ cic: getBitS(b, 163, 16) * 2 ** -30,
2387
+ crs: getBitS(b, 179, 24) * 2 ** -8,
2388
+ crc: getBitS(b, 203, 24) * 2 ** -8,
2389
+ cus: getBitS(b, 227, 21) * 2 ** -30,
2390
+ cuc: getBitS(b, 248, 21) * 2 ** -30
2391
+ };
2392
+ }
2393
+ var isc = (b, pos) => {
2394
+ const raw = getBitS(b, pos, 13);
2395
+ return raw === -4096 ? null : raw * 2 ** -35;
2396
+ };
2397
+ function decodeClock(b, msgType) {
2398
+ const clock = {
2399
+ msgType,
2400
+ top: getBitU(b, 38, 11) * 300,
2401
+ uraNed0: getBitS(b, 49, 5),
2402
+ uraNed1: getBitU(b, 54, 3),
2403
+ uraNed2: getBitU(b, 57, 3),
2404
+ toc: getBitU(b, 60, 11) * 300,
2405
+ af0: getBitS(b, 71, 26) * 2 ** -35,
2406
+ af1: getBitS(b, 97, 20) * 2 ** -48,
2407
+ af2: getBitS(b, 117, 10) * 2 ** -60
2408
+ };
2409
+ if (msgType === 30) {
2410
+ clock.tgd = isc(b, 127);
2411
+ clock.iscL1ca = isc(b, 140);
2412
+ clock.iscL2c = isc(b, 153);
2413
+ clock.iscL5i5 = isc(b, 166);
2414
+ clock.iscL5q5 = isc(b, 179);
2415
+ clock.ionoAlpha = [
2416
+ getBitS(b, 192, 8) * 2 ** -30,
2417
+ getBitS(b, 200, 8) * 2 ** -27,
2418
+ getBitS(b, 208, 8) * 2 ** -24,
2419
+ getBitS(b, 216, 8) * 2 ** -24
2420
+ ];
2421
+ clock.ionoBeta = [
2422
+ getBitS(b, 224, 8) * 2 ** 11,
2423
+ getBitS(b, 232, 8) * 2 ** 14,
2424
+ getBitS(b, 240, 8) * 2 ** 16,
2425
+ getBitS(b, 248, 8) * 2 ** 16
2426
+ ];
2427
+ clock.wnOp = getBitU(b, 256, 8);
2428
+ }
2429
+ return clock;
2430
+ }
2431
+ function resolveWeek(week, towSec, sec) {
2432
+ if (sec < towSec - HALF_WEEK) return week + 1;
2433
+ if (sec > towSec + HALF_WEEK) return week - 1;
2434
+ return week;
2435
+ }
2436
+ function gpsDate(week, sec) {
2437
+ return new Date(GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + sec) * 1e3);
2438
+ }
2439
+ var CnavAssembler = class {
2440
+ sats = /* @__PURE__ */ new Map();
2441
+ /**
2442
+ * Push one 300-bit CNAV message (38+ bytes, bit 0 = first bit of
2443
+ * the preamble). Returns the newly completed ephemeris, or null.
2444
+ * Messages that are not MT10/MT11/MT30-37, or whose preamble/PRN
2445
+ * are out of range, are ignored.
2446
+ */
2447
+ push(msg) {
2448
+ if (msg.length < 38 || getBitU(msg, 0, 8) !== CNAV_PREAMBLE) return null;
2449
+ const prn = getBitU(msg, 8, 6);
2450
+ if (prn < 1 || prn > 32) return null;
2451
+ const type = getBitU(msg, 14, 6);
2452
+ const tow = getBitU(msg, 20, 17) * 6;
2453
+ let sat = this.sats.get(prn);
2454
+ if (!sat) {
2455
+ sat = {};
2456
+ this.sats.set(prn, sat);
2457
+ }
2458
+ if (type === 10) sat.t10 = decodeType10(msg, tow);
2459
+ else if (type === 11) sat.t11 = decodeType11(msg);
2460
+ else if (type >= 30 && type <= 37) {
2461
+ sat.clock = decodeClock(msg, type);
2462
+ if (type === 30) sat.extras = sat.clock;
2463
+ } else return null;
2464
+ return this.tryEmit(prn, sat);
2465
+ }
2466
+ tryEmit(prn, sat) {
2467
+ const { t10, t11, clock, extras } = sat;
2468
+ if (!t10 || !t11 || !clock) return null;
2469
+ if (t10.toe !== t11.toe || clock.toc !== t10.toe) return null;
2470
+ const key = `${t10.week}:${t10.toe}:${clock.af0}:${clock.af1}`;
2471
+ if (key === sat.lastKey) return null;
2472
+ sat.lastKey = key;
2473
+ const weekToe = resolveWeek(t10.week, t10.tow, t10.toe);
2474
+ const a = CNAV_A_REF + t10.deltaA;
2475
+ return {
2476
+ system: "G",
2477
+ prn: `G${String(prn).padStart(2, "0")}`,
2478
+ week: t10.week,
2479
+ health: t10.health,
2480
+ uraEd: t10.uraEd,
2481
+ uraNed0: clock.uraNed0,
2482
+ uraNed1: clock.uraNed1,
2483
+ uraNed2: clock.uraNed2,
2484
+ top: t10.top,
2485
+ toe: t10.toe,
2486
+ toeDate: gpsDate(weekToe, t10.toe),
2487
+ a,
2488
+ deltaA: t10.deltaA,
2489
+ aDot: t10.aDot,
2490
+ deltaN0: t10.deltaN0,
2491
+ deltaN0Dot: t10.deltaN0Dot,
2492
+ m0: t10.m0,
2493
+ e: t10.e,
2494
+ omega: t10.omega,
2495
+ omega0: t11.omega0,
2496
+ i0: t11.i0,
2497
+ omegaDot: CNAV_OMEGA_DOT_REF * GPS_PI + t11.deltaOmegaDot,
2498
+ deltaOmegaDot: t11.deltaOmegaDot,
2499
+ i0Dot: t11.i0Dot,
2500
+ cis: t11.cis,
2501
+ cic: t11.cic,
2502
+ crs: t11.crs,
2503
+ crc: t11.crc,
2504
+ cus: t11.cus,
2505
+ cuc: t11.cuc,
2506
+ toc: clock.toc,
2507
+ tocDate: gpsDate(weekToe, clock.toc),
2508
+ af0: clock.af0,
2509
+ af1: clock.af1,
2510
+ af2: clock.af2,
2511
+ clockMsgType: clock.msgType,
2512
+ tgd: extras?.tgd ?? null,
2513
+ iscL1ca: extras?.iscL1ca ?? null,
2514
+ iscL2c: extras?.iscL2c ?? null,
2515
+ iscL5i5: extras?.iscL5i5 ?? null,
2516
+ iscL5q5: extras?.iscL5q5 ?? null,
2517
+ ...extras?.ionoAlpha && { ionoAlpha: extras.ionoAlpha },
2518
+ ...extras?.ionoBeta && { ionoBeta: extras.ionoBeta },
2519
+ ...extras?.wnOp !== void 0 && { wnOp: extras.wnOp },
2520
+ integrityFlag: t10.integrityFlag,
2521
+ l2cPhasing: t10.l2cPhasing,
2522
+ tow: t10.tow
2523
+ };
2524
+ }
2525
+ };
2526
+
2188
2527
  // src/rinex/nav.ts
2189
2528
  var DATA_LINES = {
2190
2529
  G: 7,
@@ -2208,19 +2547,19 @@ var R4_MSG_LINES = {
2208
2547
  SBAS: 4,
2209
2548
  FDMA: 5,
2210
2549
  // RINEX 4 adds orbit-4 (status flags) vs 3 in RINEX 3
2211
- // EPH types we skip (different field layout)
2550
+ // EPH type parsed into RinexCnavEphemeris for G/J (skipped otherwise)
2212
2551
  CNAV: 9,
2552
+ // EPH types we skip (different field layout)
2213
2553
  CNV1: 10,
2214
2554
  CNV2: 10,
2215
2555
  CNV3: 9,
2216
2556
  L1NV: 8,
2217
2557
  L1OC: 9,
2218
- L3OC: 9,
2219
- // Non-EPH records
2220
- STO: 2,
2221
- EOP: 3,
2222
- ION: 3
2558
+ L3OC: 9
2223
2559
  };
2560
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
2561
+ var SEC_PER_WEEK3 = 7 * 86400;
2562
+ var QZSS_CNAV_A_REF = 42164200;
2224
2563
  var SUPPORTED_EPH_MSGS = /* @__PURE__ */ new Set([
2225
2564
  "LNAV",
2226
2565
  "INAV",
@@ -2284,6 +2623,20 @@ function parseDataLine(line, colOffset = 4) {
2284
2623
  }
2285
2624
  return values;
2286
2625
  }
2626
+ function parseDataLineSlots(line) {
2627
+ const values = [];
2628
+ for (let i = 0; i < 4; i++) {
2629
+ const start = 4 + i * 19;
2630
+ const s = start < line.length ? line.substring(start, start + 19).trim() : "";
2631
+ if (s.length === 0) {
2632
+ values.push(null);
2633
+ continue;
2634
+ }
2635
+ const v = parseFloat19(s);
2636
+ values.push(Number.isFinite(v) ? v : null);
2637
+ }
2638
+ return values;
2639
+ }
2287
2640
  function buildKeplerEphemeris(sys, prn, date, epochVals, data) {
2288
2641
  const d = [];
2289
2642
  for (const line of data) {
@@ -2346,6 +2699,68 @@ function buildStateVectorEphemeris(sys, prn, date, epochVals, data) {
2346
2699
  zAcc: d[10] ?? 0
2347
2700
  };
2348
2701
  }
2702
+ function buildCnavEphemeris(sys, prn, date, epochVals, d) {
2703
+ const num = (r, c) => d[r]?.[c] ?? 0;
2704
+ const opt = (r, c) => d[r]?.[c] ?? null;
2705
+ const t = (date.getTime() - GPS_EPOCH_MS3) / 1e3;
2706
+ const week = Math.floor(t / SEC_PER_WEEK3);
2707
+ const toc = t - week * SEC_PER_WEEK3;
2708
+ const sqrtA = num(1, 3);
2709
+ const a = sqrtA * sqrtA;
2710
+ const aRef = sys === "J" ? QZSS_CNAV_A_REF : CNAV_A_REF;
2711
+ const omegaDot = num(3, 3);
2712
+ const wnOp = d[7]?.[1];
2713
+ return {
2714
+ system: sys,
2715
+ prn,
2716
+ week,
2717
+ health: num(5, 1),
2718
+ uraEd: num(5, 0),
2719
+ uraNed0: num(4, 2),
2720
+ uraNed1: num(4, 3),
2721
+ uraNed2: num(5, 3),
2722
+ top: num(2, 0),
2723
+ toe: toc,
2724
+ toeDate: date,
2725
+ a,
2726
+ deltaA: a - aRef,
2727
+ aDot: num(0, 0),
2728
+ deltaN0: num(0, 2),
2729
+ deltaN0Dot: num(4, 1),
2730
+ m0: num(0, 3),
2731
+ e: num(1, 1),
2732
+ omega: num(3, 2),
2733
+ omega0: num(2, 2),
2734
+ i0: num(3, 0),
2735
+ omegaDot,
2736
+ deltaOmegaDot: omegaDot - CNAV_OMEGA_DOT_REF * GPS_PI,
2737
+ i0Dot: num(4, 0),
2738
+ cis: num(2, 3),
2739
+ cic: num(2, 1),
2740
+ crs: num(0, 1),
2741
+ crc: num(3, 1),
2742
+ cus: num(1, 2),
2743
+ cuc: num(1, 0),
2744
+ toc,
2745
+ tocDate: date,
2746
+ af0: epochVals[0] ?? 0,
2747
+ af1: epochVals[1] ?? 0,
2748
+ af2: epochVals[2] ?? 0,
2749
+ clockMsgType: 0,
2750
+ // not recorded in RINEX
2751
+ tgd: opt(5, 2),
2752
+ iscL1ca: opt(6, 0),
2753
+ iscL2c: opt(6, 1),
2754
+ iscL5i5: opt(6, 2),
2755
+ iscL5q5: opt(6, 3),
2756
+ ...wnOp != null && { wnOp },
2757
+ integrityFlag: false,
2758
+ // not recorded in RINEX
2759
+ l2cPhasing: false,
2760
+ // not recorded in RINEX
2761
+ tow: num(7, 0)
2762
+ };
2763
+ }
2349
2764
  function parseNavFile(text) {
2350
2765
  const lines = text.split("\n");
2351
2766
  const header = {
@@ -2355,6 +2770,7 @@ function parseNavFile(text) {
2355
2770
  ionoCorrections: {}
2356
2771
  };
2357
2772
  const ephemerides = [];
2773
+ const cnav = [];
2358
2774
  let inHeader = true;
2359
2775
  let i = 0;
2360
2776
  while (i < lines.length) {
@@ -2415,8 +2831,15 @@ function parseNavFile(text) {
2415
2831
  if (isV4 && line.charAt(0) === ">") {
2416
2832
  const parts = line.substring(2).trim().split(/\s+/);
2417
2833
  const recType = parts[0] ?? "";
2834
+ const svSys = (parts[1] ?? "").charAt(0);
2418
2835
  const msgType = parts[2] ?? recType;
2419
- if (recType !== "EPH" || !SUPPORTED_EPH_MSGS.has(msgType)) {
2836
+ if (recType !== "EPH") {
2837
+ const totalLines = recType === "STO" ? 2 : recType === "EOP" ? 3 : recType === "ION" ? svSys === "E" ? 2 : 3 : 2;
2838
+ i += totalLines + 1;
2839
+ continue;
2840
+ }
2841
+ const isCnav = msgType === "CNAV" && (svSys === "G" || svSys === "J");
2842
+ if (!SUPPORTED_EPH_MSGS.has(msgType) && !isCnav) {
2420
2843
  const totalLines = R4_MSG_LINES[msgType] ?? 2;
2421
2844
  i += totalLines + 1;
2422
2845
  continue;
@@ -2428,6 +2851,21 @@ function parseNavFile(text) {
2428
2851
  const sys2 = epochLine.charAt(0);
2429
2852
  const parsed = parseNavEpochV3(epochLine);
2430
2853
  const prn2 = `${sys2}${parsed.prn.substring(1)}`;
2854
+ if (isCnav) {
2855
+ const slotLines = [];
2856
+ for (let j = 0; j < numDataLines2; j++) {
2857
+ i++;
2858
+ if (i >= lines.length) break;
2859
+ slotLines.push(parseDataLineSlots(lines[i]));
2860
+ }
2861
+ if (slotLines.length === numDataLines2 && (sys2 === "G" || sys2 === "J")) {
2862
+ cnav.push(
2863
+ buildCnavEphemeris(sys2, prn2, parsed.date, parsed.values, slotLines)
2864
+ );
2865
+ }
2866
+ i++;
2867
+ continue;
2868
+ }
2431
2869
  const dataLines2 = [];
2432
2870
  for (let j = 0; j < numDataLines2; j++) {
2433
2871
  i++;
@@ -2501,7 +2939,7 @@ function parseNavFile(text) {
2501
2939
  }
2502
2940
  i++;
2503
2941
  }
2504
- return { header, ephemerides };
2942
+ return cnav.length > 0 ? { header, ephemerides, cnav } : { header, ephemerides };
2505
2943
  }
2506
2944
 
2507
2945
  // src/rinex/nav-writer.ts
@@ -2509,9 +2947,13 @@ function fmtD(val) {
2509
2947
  if (val === 0) return " 0.000000000000E+00";
2510
2948
  const sign = val < 0 ? "-" : " ";
2511
2949
  const abs = Math.abs(val);
2512
- const exp = Math.floor(Math.log10(abs));
2950
+ let exp = Math.floor(Math.log10(abs));
2513
2951
  const mantissa = abs / 10 ** exp;
2514
- const mStr = mantissa.toFixed(12);
2952
+ let mStr = mantissa.toFixed(12);
2953
+ if (mStr.length > 14) {
2954
+ exp += 1;
2955
+ mStr = (mantissa / 10).toFixed(12);
2956
+ }
2515
2957
  const expSign = exp >= 0 ? "+" : "-";
2516
2958
  const expStr = String(Math.abs(exp)).padStart(2, "0");
2517
2959
  return `${sign}${mStr}E${expSign}${expStr}`;
@@ -2586,7 +3028,7 @@ function writeKeplerRecord(eph) {
2586
3028
  function writeGlonassRecord(eph) {
2587
3029
  const lines = [];
2588
3030
  lines.push(
2589
- `${padL(eph.prn, 3)} ${fmtEpoch(eph.tocDate)}${fmtD(-eph.tauN)}${fmtD(eph.gammaN)}${fmtD(eph.messageFrameTime)}`
3031
+ `${padL(eph.prn, 3)} ${fmtEpoch(eph.tocDate)}${fmtD(eph.tauN)}${fmtD(eph.gammaN)}${fmtD(eph.messageFrameTime)}`
2590
3032
  );
2591
3033
  lines.push(orbitLine([eph.x, eph.xDot, eph.xAcc, eph.health]));
2592
3034
  lines.push(orbitLine([eph.y, eph.yDot, eph.yAcc, eph.freqNum]));
@@ -2606,6 +3048,181 @@ function writeRinexNav(nav) {
2606
3048
  return header + records.join("\n") + "\n";
2607
3049
  }
2608
3050
 
3051
+ // src/rinex/nav-writer-v4.ts
3052
+ var BLANK19 = " ".repeat(19);
3053
+ function fmtEpoch4(d) {
3054
+ const p2 = (v) => String(v).padStart(2, "0");
3055
+ return `${d.getUTCFullYear()} ${p2(d.getUTCMonth() + 1)} ${p2(d.getUTCDate())} ${p2(d.getUTCHours())} ${p2(d.getUTCMinutes())} ${p2(d.getUTCSeconds())}`;
3056
+ }
3057
+ function orbitLine4(values) {
3058
+ const line = " " + values.map((v) => v == null ? BLANK19 : fmtD(v)).join("");
3059
+ return line.replace(/ +$/, "");
3060
+ }
3061
+ function epochLine4(prn, date, vals) {
3062
+ return `${padL(prn, 3)} ${fmtEpoch4(date)}${vals.map(fmtD).join("")}`;
3063
+ }
3064
+ var BDS_GEO = /* @__PURE__ */ new Set([
3065
+ "C01",
3066
+ "C02",
3067
+ "C03",
3068
+ "C04",
3069
+ "C05",
3070
+ "C59",
3071
+ "C60",
3072
+ "C61",
3073
+ "C62",
3074
+ "C63"
3075
+ ]);
3076
+ function msgLabel(eph) {
3077
+ switch (eph.system) {
3078
+ case "E":
3079
+ return "INAV";
3080
+ case "C":
3081
+ return BDS_GEO.has(eph.prn) ? "D2" : "D1";
3082
+ case "R":
3083
+ return "FDMA";
3084
+ case "S":
3085
+ return "SBAS";
3086
+ default:
3087
+ return "LNAV";
3088
+ }
3089
+ }
3090
+ function isKepler2(eph) {
3091
+ return "af0" in eph;
3092
+ }
3093
+ function keplerRecord(eph) {
3094
+ return [
3095
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
3096
+ orbitLine4([eph.iode, eph.crs, eph.deltaN, eph.m0]),
3097
+ orbitLine4([eph.cuc, eph.e, eph.cus, eph.sqrtA]),
3098
+ orbitLine4([eph.toe, eph.cic, eph.omega0, eph.cis]),
3099
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
3100
+ orbitLine4([eph.idot, 0, eph.week, 0]),
3101
+ orbitLine4([0, eph.svHealth, eph.tgd, 0]),
3102
+ orbitLine4([eph.toe, 0, 0, 0])
3103
+ ].join("\n");
3104
+ }
3105
+ function stateVectorRecord(eph) {
3106
+ const lines = [
3107
+ epochLine4(eph.prn, eph.tocDate, [
3108
+ eph.tauN,
3109
+ eph.gammaN,
3110
+ eph.messageFrameTime
3111
+ ]),
3112
+ orbitLine4([eph.x, eph.xDot, eph.xAcc, eph.health]),
3113
+ orbitLine4([eph.y, eph.yDot, eph.yAcc, eph.freqNum]),
3114
+ orbitLine4([eph.z, eph.zDot, eph.zAcc, 0])
3115
+ ];
3116
+ if (eph.system === "R") {
3117
+ lines.push(orbitLine4([null, 999999999999e-3, 15, null]));
3118
+ }
3119
+ return lines.join("\n");
3120
+ }
3121
+ function resolveWnOp(week, wnOp) {
3122
+ if (wnOp == null) return week;
3123
+ return week - ((week - wnOp) % 256 + 256) % 256;
3124
+ }
3125
+ function cnavRecord(eph) {
3126
+ return [
3127
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
3128
+ orbitLine4([eph.aDot, eph.crs, eph.deltaN0, eph.m0]),
3129
+ orbitLine4([eph.cuc, eph.e, eph.cus, Math.sqrt(eph.a)]),
3130
+ orbitLine4([eph.top, eph.cic, eph.omega0, eph.cis]),
3131
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
3132
+ orbitLine4([eph.i0Dot, eph.deltaN0Dot, eph.uraNed0, eph.uraNed1]),
3133
+ orbitLine4([eph.uraEd, eph.health, eph.tgd, eph.uraNed2]),
3134
+ orbitLine4([eph.iscL1ca, eph.iscL2c, eph.iscL5i5, eph.iscL5q5]),
3135
+ orbitLine4([eph.tow, resolveWnOp(eph.week, eph.wnOp)])
3136
+ ].join("\n");
3137
+ }
3138
+ var KLOBUCHAR_SOURCES = [
3139
+ ["GPSA", "GPSB", "G", "LNAV"],
3140
+ ["QZSA", "QZSB", "J", "LNAV"],
3141
+ ["BDSA", "BDSB", "C", "D1D2"],
3142
+ ["IRNA", "IRNB", "I", "LNAV"]
3143
+ ];
3144
+ function ionRecords(header, epoch) {
3145
+ const records = [];
3146
+ const iono = header.ionoCorrections;
3147
+ for (const [alphaKey, betaKey, sys, msg] of KLOBUCHAR_SOURCES) {
3148
+ const alpha = iono[alphaKey];
3149
+ if (!alpha) continue;
3150
+ const beta = iono[betaKey] ?? [0, 0, 0, 0];
3151
+ records.push(
3152
+ [
3153
+ `> ION ${sys} ${msg}`,
3154
+ ` ${fmtEpoch4(epoch)}${[alpha[0] ?? 0, alpha[1] ?? 0, alpha[2] ?? 0].map(fmtD).join("")}`,
3155
+ orbitLine4([alpha[3] ?? 0, beta[0] ?? 0, beta[1] ?? 0, beta[2] ?? 0]),
3156
+ orbitLine4([beta[3] ?? 0, null])
3157
+ ].join("\n")
3158
+ );
3159
+ }
3160
+ const gal = iono["GAL"];
3161
+ if (gal) {
3162
+ records.push(
3163
+ [
3164
+ "> ION E IFNV",
3165
+ ` ${fmtEpoch4(epoch)}${[gal[0] ?? 0, gal[1] ?? 0, gal[2] ?? 0].map(fmtD).join("")}`,
3166
+ orbitLine4([gal[3] ?? 0])
3167
+ ].join("\n")
3168
+ );
3169
+ }
3170
+ return records;
3171
+ }
3172
+ function writeHeader4(header) {
3173
+ const lines = [];
3174
+ lines.push(
3175
+ hdrLine(
3176
+ padL(" 4.01", 9) + " " + padL("NAVIGATION DATA", 20) + padL("M", 20),
3177
+ "RINEX VERSION / TYPE"
3178
+ )
3179
+ );
3180
+ const now = /* @__PURE__ */ new Date();
3181
+ const dateStr = now.getUTCFullYear().toString() + String(now.getUTCMonth() + 1).padStart(2, "0") + String(now.getUTCDate()).padStart(2, "0") + " " + String(now.getUTCHours()).padStart(2, "0") + String(now.getUTCMinutes()).padStart(2, "0") + String(now.getUTCSeconds()).padStart(2, "0") + " UTC";
3182
+ lines.push(
3183
+ hdrLine(
3184
+ padL("GNSSCalc", 20) + padL("", 20) + padL(dateStr, 20),
3185
+ "PGM / RUN BY / DATE"
3186
+ )
3187
+ );
3188
+ if (header.leapSeconds != null) {
3189
+ lines.push(hdrLine(padR(String(header.leapSeconds), 6), "LEAP SECONDS"));
3190
+ }
3191
+ lines.push(hdrLine("", "END OF HEADER"));
3192
+ return lines.join("\n") + "\n";
3193
+ }
3194
+ function writeRinexNav4(nav) {
3195
+ const records = [];
3196
+ for (const eph of nav.ephemerides) {
3197
+ const label2 = msgLabel(eph);
3198
+ records.push({
3199
+ prn: eph.prn,
3200
+ time: eph.tocDate.getTime(),
3201
+ label: label2,
3202
+ body: `> EPH ${padL(eph.prn, 3)} ${label2}
3203
+ ` + (isKepler2(eph) ? keplerRecord(eph) : stateVectorRecord(eph))
3204
+ });
3205
+ }
3206
+ for (const eph of nav.cnav ?? []) {
3207
+ records.push({
3208
+ prn: eph.prn,
3209
+ time: eph.tocDate.getTime(),
3210
+ label: "CNAV",
3211
+ body: `> EPH ${padL(eph.prn, 3)} CNAV
3212
+ ` + cnavRecord(eph)
3213
+ });
3214
+ }
3215
+ records.sort(
3216
+ (a, b) => a.prn.localeCompare(b.prn) || a.time - b.time || a.label.localeCompare(b.label)
3217
+ );
3218
+ const bodies = records.map((r) => r.body);
3219
+ if (records.length > 0) {
3220
+ const earliest = new Date(Math.min(...records.map((r) => r.time)));
3221
+ bodies.push(...ionRecords(nav.header, earliest));
3222
+ }
3223
+ return writeHeader4(nav.header) + (bodies.length > 0 ? bodies.join("\n") + "\n" : "");
3224
+ }
3225
+
2609
3226
  // src/rinex/ionex.ts
2610
3227
  function gridRange(l1, l2, dl) {
2611
3228
  const out = [];
@@ -3229,7 +3846,7 @@ var BitReader = class {
3229
3846
  return this.data.length * 8 - this.bitPos;
3230
3847
  }
3231
3848
  };
3232
- function crc24q(data, length) {
3849
+ function crc24q2(data, length) {
3233
3850
  let crc = 0;
3234
3851
  for (let i = 0; i < length; i++) {
3235
3852
  crc ^= data[i] << 16;
@@ -3271,7 +3888,7 @@ var Rtcm3Decoder = class {
3271
3888
  }
3272
3889
  const frameSize = 3 + length + 3;
3273
3890
  if (this.buffer.length < frameSize) break;
3274
- const crcComputed = crc24q(this.buffer, 3 + length);
3891
+ const crcComputed = crc24q2(this.buffer, 3 + length);
3275
3892
  const crcReceived = this.buffer[3 + length] << 16 | this.buffer[3 + length + 1] << 8 | this.buffer[3 + length + 2];
3276
3893
  if (crcComputed !== crcReceived) {
3277
3894
  this.buffer = this.buffer.slice(1);
@@ -4586,197 +5203,56 @@ function updateStreamStats(stats, frames, rawBytes) {
4586
5203
  if (sig.cn0 !== void 0) sysSet.add(`S${sig.rinexCode}`);
4587
5204
  }
4588
5205
  }
4589
- }
4590
- const eph = decodeEphemeris(frame);
4591
- if (eph) {
4592
- stats.ephemerides.set(eph.prn, eph);
4593
- if (eph.freqChannel !== void 0 && eph.prn.startsWith("R")) {
4594
- setGloFreqNumber(parseInt(eph.prn.slice(1), 10), eph.freqChannel);
4595
- }
4596
- }
4597
- updateStationMeta(stats.stationMeta, frame);
4598
- }
4599
- for (const [prn, sat] of stats.satellites) {
4600
- if (now - sat.lastSeen > 3e4) stats.satellites.delete(prn);
4601
- }
4602
- const elapsed = (now - stats.startTime) / 1e3;
4603
- if (elapsed > 0) {
4604
- stats.bytesPerSecond = stats.totalBytes / elapsed;
4605
- stats.framesPerSecond = stats.totalFrames / elapsed;
4606
- }
4607
- }
4608
-
4609
- // src/ubx/frame.ts
4610
- function* ubxFrames(data, stats = { badChecksums: 0 }) {
4611
- let i = 0;
4612
- while (i + 8 <= data.length) {
4613
- if (data[i] !== 181 || data[i + 1] !== 98) {
4614
- i++;
4615
- continue;
4616
- }
4617
- const len = data[i + 4] | data[i + 5] << 8;
4618
- const end = i + 6 + len + 2;
4619
- if (end > data.length) break;
4620
- let ckA = 0;
4621
- let ckB = 0;
4622
- for (let j = i + 2; j < i + 6 + len; j++) {
4623
- ckA = ckA + data[j] & 255;
4624
- ckB = ckB + ckA & 255;
4625
- }
4626
- if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
4627
- stats.badChecksums++;
4628
- i++;
4629
- continue;
4630
- }
4631
- yield {
4632
- msgClass: data[i + 2],
4633
- msgId: data[i + 3],
4634
- payload: data.subarray(i + 6, i + 6 + len),
4635
- payloadStart: i + 6
4636
- };
4637
- i = end;
4638
- }
4639
- }
4640
-
4641
- // src/navbits/index.ts
4642
- var GPS_PI = 3.1415926535898;
4643
- var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
4644
- var SEC_PER_WEEK = 7 * 86400;
4645
- function getBitU(buff, pos, len) {
4646
- let bits = 0;
4647
- for (let i = pos; i < pos + len; i++) {
4648
- bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
4649
- }
4650
- return bits;
4651
- }
4652
- function getBitS(buff, pos, len) {
4653
- const bits = getBitU(buff, pos, len);
4654
- if (len <= 0 || bits < 2 ** (len - 1)) return bits;
4655
- return bits - 2 ** len;
4656
- }
4657
- function setBitU(buff, pos, len, data) {
4658
- let mask = 2 ** (len - 1);
4659
- for (let i = pos; i < pos + len; i++, mask /= 2) {
4660
- if (data >= mask) {
4661
- buff[i >> 3] |= 1 << 7 - (i & 7);
4662
- data -= mask;
4663
- } else {
4664
- buff[i >> 3] &= ~(1 << 7 - (i & 7));
4665
- }
4666
- }
4667
- }
4668
- function decodeGpsLnavFrame(subframes, opts = {}) {
4669
- if (subframes.length < 90) return null;
4670
- const b = subframes;
4671
- let i = 24;
4672
- const tow1 = getBitU(b, i, 17) * 6;
4673
- i += 17 + 2;
4674
- const id1 = getBitU(b, i, 3);
4675
- i += 3 + 2;
4676
- const week10 = getBitU(b, i, 10);
4677
- i += 10;
4678
- i += 2;
4679
- i += 4;
4680
- const svHealth = getBitU(b, i, 6);
4681
- i += 6;
4682
- const iodc0 = getBitU(b, i, 2);
4683
- i += 2;
4684
- i += 1 + 87;
4685
- const tgdRaw = getBitS(b, i, 8);
4686
- i += 8;
4687
- const iodc1 = getBitU(b, i, 8);
4688
- i += 8;
4689
- const tocSec = getBitU(b, i, 16) * 16;
4690
- i += 16;
4691
- const af2 = getBitS(b, i, 8) * 2 ** -55;
4692
- i += 8;
4693
- const af1 = getBitS(b, i, 16) * 2 ** -43;
4694
- i += 16;
4695
- const af0 = getBitS(b, i, 22) * 2 ** -31;
4696
- i = 240 + 24;
4697
- i += 17 + 2;
4698
- const id2 = getBitU(b, i, 3);
4699
- i += 3 + 2;
4700
- const iode = getBitU(b, i, 8);
4701
- i += 8;
4702
- const crs = getBitS(b, i, 16) * 2 ** -5;
4703
- i += 16;
4704
- const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
4705
- i += 16;
4706
- const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4707
- i += 32;
4708
- const cuc = getBitS(b, i, 16) * 2 ** -29;
4709
- i += 16;
4710
- const e = getBitU(b, i, 32) * 2 ** -33;
4711
- i += 32;
4712
- const cus = getBitS(b, i, 16) * 2 ** -29;
4713
- i += 16;
4714
- const sqrtA = getBitU(b, i, 32) * 2 ** -19;
4715
- i += 32;
4716
- const toes = getBitU(b, i, 16) * 16;
4717
- i = 480 + 24;
4718
- i += 17 + 2;
4719
- const id3 = getBitU(b, i, 3);
4720
- i += 3 + 2;
4721
- const cic = getBitS(b, i, 16) * 2 ** -29;
4722
- i += 16;
4723
- const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4724
- i += 32;
4725
- const cis = getBitS(b, i, 16) * 2 ** -29;
4726
- i += 16;
4727
- const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4728
- i += 32;
4729
- const crc = getBitS(b, i, 16) * 2 ** -5;
4730
- i += 16;
4731
- const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4732
- i += 32;
4733
- const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
4734
- i += 24;
4735
- const iode3 = getBitU(b, i, 8);
4736
- i += 8;
4737
- const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
4738
- const iodc = iodc0 * 256 + iodc1;
4739
- if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
4740
- if (iode3 !== iode || iode !== (iodc & 255)) return null;
4741
- const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
4742
- let week = week10 + 1024 * Math.round((ref - week10) / 1024);
4743
- if (toes < tow1 - 302400) week++;
4744
- else if (toes > tow1 + 302400) week--;
4745
- const prn = opts.prn ?? "G00";
4746
- const tocDate = new Date(
4747
- GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
4748
- );
4749
- return {
4750
- system: prn[0] === "J" ? "J" : "G",
4751
- prn,
4752
- tocDate,
4753
- // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
4754
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
4755
- af0,
4756
- af1,
4757
- af2,
4758
- iode,
4759
- crs,
4760
- deltaN,
4761
- m0,
4762
- cuc,
4763
- e,
4764
- cus,
4765
- sqrtA,
4766
- toe: toes,
4767
- cic,
4768
- omega0,
4769
- cis,
4770
- i0,
4771
- crc,
4772
- omega,
4773
- omegaDot,
4774
- idot,
4775
- week,
4776
- svHealth,
4777
- tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
4778
- // IS-GPS-200: -128 reserved
4779
- };
5206
+ }
5207
+ const eph = decodeEphemeris(frame);
5208
+ if (eph) {
5209
+ stats.ephemerides.set(eph.prn, eph);
5210
+ if (eph.freqChannel !== void 0 && eph.prn.startsWith("R")) {
5211
+ setGloFreqNumber(parseInt(eph.prn.slice(1), 10), eph.freqChannel);
5212
+ }
5213
+ }
5214
+ updateStationMeta(stats.stationMeta, frame);
5215
+ }
5216
+ for (const [prn, sat] of stats.satellites) {
5217
+ if (now - sat.lastSeen > 3e4) stats.satellites.delete(prn);
5218
+ }
5219
+ const elapsed = (now - stats.startTime) / 1e3;
5220
+ if (elapsed > 0) {
5221
+ stats.bytesPerSecond = stats.totalBytes / elapsed;
5222
+ stats.framesPerSecond = stats.totalFrames / elapsed;
5223
+ }
5224
+ }
5225
+
5226
+ // src/ubx/frame.ts
5227
+ function* ubxFrames(data, stats = { badChecksums: 0 }) {
5228
+ let i = 0;
5229
+ while (i + 8 <= data.length) {
5230
+ if (data[i] !== 181 || data[i + 1] !== 98) {
5231
+ i++;
5232
+ continue;
5233
+ }
5234
+ const len = data[i + 4] | data[i + 5] << 8;
5235
+ const end = i + 6 + len + 2;
5236
+ if (end > data.length) break;
5237
+ let ckA = 0;
5238
+ let ckB = 0;
5239
+ for (let j = i + 2; j < i + 6 + len; j++) {
5240
+ ckA = ckA + data[j] & 255;
5241
+ ckB = ckB + ckA & 255;
5242
+ }
5243
+ if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
5244
+ stats.badChecksums++;
5245
+ i++;
5246
+ continue;
5247
+ }
5248
+ yield {
5249
+ msgClass: data[i + 2],
5250
+ msgId: data[i + 3],
5251
+ payload: data.subarray(i + 6, i + 6 + len),
5252
+ payloadStart: i + 6
5253
+ };
5254
+ i = end;
5255
+ }
4780
5256
  }
4781
5257
 
4782
5258
  // src/ubx/nav.ts
@@ -4886,202 +5362,6 @@ function parseUbxIonoUtc(data) {
4886
5362
  return { ionoCorrections, leapSeconds };
4887
5363
  }
4888
5364
 
4889
- // src/navbits/cnav.ts
4890
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
4891
- var SEC_PER_WEEK2 = 7 * 86400;
4892
- var HALF_WEEK = 302400;
4893
- var CNAV_PREAMBLE = 139;
4894
- var CNAV_A_REF = 26559710;
4895
- var CNAV_OMEGA_DOT_REF = -26e-10;
4896
- function crc24q2(buff, bitLen) {
4897
- let crc = 0;
4898
- for (let i = 0; i < bitLen; i++) {
4899
- crc ^= (buff[i >> 3] >> 7 - (i & 7) & 1) << 23;
4900
- const msb = crc & 8388608;
4901
- crc = crc << 1 & 16777215;
4902
- if (msb) crc ^= 8801531;
4903
- }
4904
- return crc;
4905
- }
4906
- function cnavCrcOk(msg) {
4907
- if (msg.length < 38) return false;
4908
- return crc24q2(msg, 276) === getBitU(msg, 276, 24);
4909
- }
4910
- function getBitU33(b, pos) {
4911
- return getBitU(b, pos, 1) * 2 ** 32 + getBitU(b, pos + 1, 32);
4912
- }
4913
- function getBitS33(b, pos) {
4914
- const u = getBitU33(b, pos);
4915
- return u < 2 ** 32 ? u : u - 2 ** 33;
4916
- }
4917
- function decodeType10(b, tow) {
4918
- return {
4919
- tow,
4920
- week: getBitU(b, 38, 13),
4921
- health: getBitU(b, 51, 3),
4922
- top: getBitU(b, 54, 11) * 300,
4923
- uraEd: getBitS(b, 65, 5),
4924
- toe: getBitU(b, 70, 11) * 300,
4925
- deltaA: getBitS(b, 81, 26) * 2 ** -9,
4926
- aDot: getBitS(b, 107, 25) * 2 ** -21,
4927
- deltaN0: getBitS(b, 132, 17) * 2 ** -44 * GPS_PI,
4928
- deltaN0Dot: getBitS(b, 149, 23) * 2 ** -57 * GPS_PI,
4929
- m0: getBitS33(b, 172) * 2 ** -32 * GPS_PI,
4930
- e: getBitU33(b, 205) * 2 ** -34,
4931
- omega: getBitS33(b, 238) * 2 ** -32 * GPS_PI,
4932
- integrityFlag: getBitU(b, 271, 1) === 1,
4933
- l2cPhasing: getBitU(b, 272, 1) === 1
4934
- };
4935
- }
4936
- function decodeType11(b) {
4937
- return {
4938
- toe: getBitU(b, 38, 11) * 300,
4939
- omega0: getBitS33(b, 49) * 2 ** -32 * GPS_PI,
4940
- i0: getBitS33(b, 82) * 2 ** -32 * GPS_PI,
4941
- deltaOmegaDot: getBitS(b, 115, 17) * 2 ** -44 * GPS_PI,
4942
- i0Dot: getBitS(b, 132, 15) * 2 ** -44 * GPS_PI,
4943
- cis: getBitS(b, 147, 16) * 2 ** -30,
4944
- cic: getBitS(b, 163, 16) * 2 ** -30,
4945
- crs: getBitS(b, 179, 24) * 2 ** -8,
4946
- crc: getBitS(b, 203, 24) * 2 ** -8,
4947
- cus: getBitS(b, 227, 21) * 2 ** -30,
4948
- cuc: getBitS(b, 248, 21) * 2 ** -30
4949
- };
4950
- }
4951
- var isc = (b, pos) => {
4952
- const raw = getBitS(b, pos, 13);
4953
- return raw === -4096 ? null : raw * 2 ** -35;
4954
- };
4955
- function decodeClock(b, msgType) {
4956
- const clock = {
4957
- msgType,
4958
- top: getBitU(b, 38, 11) * 300,
4959
- uraNed0: getBitS(b, 49, 5),
4960
- uraNed1: getBitU(b, 54, 3),
4961
- uraNed2: getBitU(b, 57, 3),
4962
- toc: getBitU(b, 60, 11) * 300,
4963
- af0: getBitS(b, 71, 26) * 2 ** -35,
4964
- af1: getBitS(b, 97, 20) * 2 ** -48,
4965
- af2: getBitS(b, 117, 10) * 2 ** -60
4966
- };
4967
- if (msgType === 30) {
4968
- clock.tgd = isc(b, 127);
4969
- clock.iscL1ca = isc(b, 140);
4970
- clock.iscL2c = isc(b, 153);
4971
- clock.iscL5i5 = isc(b, 166);
4972
- clock.iscL5q5 = isc(b, 179);
4973
- clock.ionoAlpha = [
4974
- getBitS(b, 192, 8) * 2 ** -30,
4975
- getBitS(b, 200, 8) * 2 ** -27,
4976
- getBitS(b, 208, 8) * 2 ** -24,
4977
- getBitS(b, 216, 8) * 2 ** -24
4978
- ];
4979
- clock.ionoBeta = [
4980
- getBitS(b, 224, 8) * 2 ** 11,
4981
- getBitS(b, 232, 8) * 2 ** 14,
4982
- getBitS(b, 240, 8) * 2 ** 16,
4983
- getBitS(b, 248, 8) * 2 ** 16
4984
- ];
4985
- clock.wnOp = getBitU(b, 256, 8);
4986
- }
4987
- return clock;
4988
- }
4989
- function resolveWeek(week, towSec, sec) {
4990
- if (sec < towSec - HALF_WEEK) return week + 1;
4991
- if (sec > towSec + HALF_WEEK) return week - 1;
4992
- return week;
4993
- }
4994
- function gpsDate(week, sec) {
4995
- return new Date(GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + sec) * 1e3);
4996
- }
4997
- var CnavAssembler = class {
4998
- sats = /* @__PURE__ */ new Map();
4999
- /**
5000
- * Push one 300-bit CNAV message (38+ bytes, bit 0 = first bit of
5001
- * the preamble). Returns the newly completed ephemeris, or null.
5002
- * Messages that are not MT10/MT11/MT30-37, or whose preamble/PRN
5003
- * are out of range, are ignored.
5004
- */
5005
- push(msg) {
5006
- if (msg.length < 38 || getBitU(msg, 0, 8) !== CNAV_PREAMBLE) return null;
5007
- const prn = getBitU(msg, 8, 6);
5008
- if (prn < 1 || prn > 32) return null;
5009
- const type = getBitU(msg, 14, 6);
5010
- const tow = getBitU(msg, 20, 17) * 6;
5011
- let sat = this.sats.get(prn);
5012
- if (!sat) {
5013
- sat = {};
5014
- this.sats.set(prn, sat);
5015
- }
5016
- if (type === 10) sat.t10 = decodeType10(msg, tow);
5017
- else if (type === 11) sat.t11 = decodeType11(msg);
5018
- else if (type >= 30 && type <= 37) {
5019
- sat.clock = decodeClock(msg, type);
5020
- if (type === 30) sat.extras = sat.clock;
5021
- } else return null;
5022
- return this.tryEmit(prn, sat);
5023
- }
5024
- tryEmit(prn, sat) {
5025
- const { t10, t11, clock, extras } = sat;
5026
- if (!t10 || !t11 || !clock) return null;
5027
- if (t10.toe !== t11.toe || clock.toc !== t10.toe) return null;
5028
- const key = `${t10.week}:${t10.toe}:${clock.af0}:${clock.af1}`;
5029
- if (key === sat.lastKey) return null;
5030
- sat.lastKey = key;
5031
- const weekToe = resolveWeek(t10.week, t10.tow, t10.toe);
5032
- const a = CNAV_A_REF + t10.deltaA;
5033
- return {
5034
- system: "G",
5035
- prn: `G${String(prn).padStart(2, "0")}`,
5036
- week: t10.week,
5037
- health: t10.health,
5038
- uraEd: t10.uraEd,
5039
- uraNed0: clock.uraNed0,
5040
- uraNed1: clock.uraNed1,
5041
- uraNed2: clock.uraNed2,
5042
- top: t10.top,
5043
- toe: t10.toe,
5044
- toeDate: gpsDate(weekToe, t10.toe),
5045
- a,
5046
- deltaA: t10.deltaA,
5047
- aDot: t10.aDot,
5048
- deltaN0: t10.deltaN0,
5049
- deltaN0Dot: t10.deltaN0Dot,
5050
- m0: t10.m0,
5051
- e: t10.e,
5052
- omega: t10.omega,
5053
- omega0: t11.omega0,
5054
- i0: t11.i0,
5055
- omegaDot: CNAV_OMEGA_DOT_REF * GPS_PI + t11.deltaOmegaDot,
5056
- deltaOmegaDot: t11.deltaOmegaDot,
5057
- i0Dot: t11.i0Dot,
5058
- cis: t11.cis,
5059
- cic: t11.cic,
5060
- crs: t11.crs,
5061
- crc: t11.crc,
5062
- cus: t11.cus,
5063
- cuc: t11.cuc,
5064
- toc: clock.toc,
5065
- tocDate: gpsDate(weekToe, clock.toc),
5066
- af0: clock.af0,
5067
- af1: clock.af1,
5068
- af2: clock.af2,
5069
- clockMsgType: clock.msgType,
5070
- tgd: extras?.tgd ?? null,
5071
- iscL1ca: extras?.iscL1ca ?? null,
5072
- iscL2c: extras?.iscL2c ?? null,
5073
- iscL5i5: extras?.iscL5i5 ?? null,
5074
- iscL5q5: extras?.iscL5q5 ?? null,
5075
- ...extras?.ionoAlpha && { ionoAlpha: extras.ionoAlpha },
5076
- ...extras?.ionoBeta && { ionoBeta: extras.ionoBeta },
5077
- ...extras?.wnOp !== void 0 && { wnOp: extras.wnOp },
5078
- integrityFlag: t10.integrityFlag,
5079
- l2cPhasing: t10.l2cPhasing,
5080
- tow: t10.tow
5081
- };
5082
- }
5083
- };
5084
-
5085
5365
  // src/ubx/cnav.ts
5086
5366
  function parseUbxCnav(data) {
5087
5367
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
@@ -5117,7 +5397,7 @@ function parseUbxCnav(data) {
5117
5397
  }
5118
5398
 
5119
5399
  // src/ubx/index.ts
5120
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5400
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5121
5401
  var MS_PER_WEEK2 = 7 * 864e5;
5122
5402
  var SIGNALS = {
5123
5403
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -5200,7 +5480,7 @@ function parseUbxRawx(data) {
5200
5480
  if (!codes.includes(sig[1])) codes.push(sig[1]);
5201
5481
  }
5202
5482
  epochs.push({
5203
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
5483
+ timeMs: GPS_EPOCH_MS4 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
5204
5484
  leapS: leapValid ? leapS : null,
5205
5485
  meas
5206
5486
  });
@@ -5271,10 +5551,10 @@ function svidToPrn(svid) {
5271
5551
 
5272
5552
  // src/sbf/nav.ts
5273
5553
  var PI2 = Math.PI;
5274
- var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5554
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5275
5555
  var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
5276
- var SEC_PER_WEEK3 = 7 * 86400;
5277
- var MS_PER_WEEK3 = SEC_PER_WEEK3 * 1e3;
5556
+ var SEC_PER_WEEK4 = 7 * 86400;
5557
+ var MS_PER_WEEK3 = SEC_PER_WEEK4 * 1e3;
5278
5558
  var F4_DNU = -2e10;
5279
5559
  var U4_DNU = 4294967295;
5280
5560
  function adjustWeek(refWeek, wn, mod) {
@@ -5283,8 +5563,8 @@ function adjustWeek(refWeek, wn, mod) {
5283
5563
  if (offset < -(mod / 2 - 1)) offset += mod;
5284
5564
  return refWeek - offset;
5285
5565
  }
5286
- var gpsMs = (week, sec) => GPS_EPOCH_MS4 + week * MS_PER_WEEK3 + sec * 1e3;
5287
- var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK3;
5566
+ var gpsMs = (week, sec) => GPS_EPOCH_MS5 + week * MS_PER_WEEK3 + sec * 1e3;
5567
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
5288
5568
  function decodeGpsQzsNav(view, b, sys) {
5289
5569
  const svid = view.getUint8(b + 14);
5290
5570
  const prn = sys === "G" ? svid : svid - 180;
@@ -5433,7 +5713,7 @@ function decodeGloNav(view, b) {
5433
5713
  const tocDate = new Date(toeGpsMs - leapMs);
5434
5714
  const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
5435
5715
  const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
5436
- const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS4) / 1e3 % SEC_PER_WEEK3;
5716
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS5) / 1e3 % SEC_PER_WEEK4;
5437
5717
  return {
5438
5718
  system: "R",
5439
5719
  prn,
@@ -5649,7 +5929,7 @@ function parseSbfCnav(data) {
5649
5929
 
5650
5930
  // src/sbf/index.ts
5651
5931
  var CLIGHT = 299792458;
5652
- var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5932
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
5653
5933
  var MS_PER_WEEK4 = 7 * 864e5;
5654
5934
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
5655
5935
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -6303,7 +6583,7 @@ function parseSbfMeas(data) {
6303
6583
  const tow = view.getUint32(i + 8, true);
6304
6584
  const wnc = view.getUint16(i + 12, true);
6305
6585
  if (tow !== 4294967295 && wnc !== 65535) {
6306
- ensureEpoch(GPS_EPOCH_MS5 + wnc * MS_PER_WEEK4 + tow);
6586
+ ensureEpoch(GPS_EPOCH_MS6 + wnc * MS_PER_WEEK4 + tow);
6307
6587
  if (id === 4109) decodeMeas3Ranges(i, tow);
6308
6588
  else if (id === 4110) decodeMeas3CN0(i, len);
6309
6589
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -6367,15 +6647,15 @@ var ID_GALEPHEMERIS = 1122;
6367
6647
  var ID_QZSSEPHEMERIS = 1336;
6368
6648
  var ID_GPSEPHEM = 7;
6369
6649
  var ID_BDSEPHEMERIS = 1696;
6370
- var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6650
+ var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6371
6651
  var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
6372
- var SEC_PER_WEEK4 = 7 * 86400;
6652
+ var SEC_PER_WEEK5 = 7 * 86400;
6373
6653
  var SEC_PER_DAY = 86400;
6374
- var MS_PER_WEEK5 = SEC_PER_WEEK4 * 1e3;
6375
- var gpsMs2 = (week, sec) => GPS_EPOCH_MS6 + week * MS_PER_WEEK5 + sec * 1e3;
6376
- var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
6654
+ var MS_PER_WEEK5 = SEC_PER_WEEK5 * 1e3;
6655
+ var gpsMs2 = (week, sec) => GPS_EPOCH_MS7 + week * MS_PER_WEEK5 + sec * 1e3;
6656
+ var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK5;
6377
6657
  function nearWeekMs(refMs, sow) {
6378
- const refWeek = Math.floor((refMs - GPS_EPOCH_MS6) / MS_PER_WEEK5);
6658
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS7) / MS_PER_WEEK5);
6379
6659
  let ms = gpsMs2(refWeek, sow);
6380
6660
  if (ms < refMs - MS_PER_WEEK5 / 2) ms += MS_PER_WEEK5;
6381
6661
  else if (ms > refMs + MS_PER_WEEK5 / 2) ms -= MS_PER_WEEK5;
@@ -6402,9 +6682,9 @@ function decodeGloEphemeris(view, p) {
6402
6682
  tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
6403
6683
  if (tof < tow - 43200) tof += SEC_PER_DAY;
6404
6684
  else if (tof > tow + 43200) tof -= SEC_PER_DAY;
6405
- const toeGpsMs = GPS_EPOCH_MS6 + (week * SEC_PER_WEEK4 + tow) * 1e3;
6406
- const tofGpsMs = GPS_EPOCH_MS6 + (week * SEC_PER_WEEK4 + tof) * 1e3;
6407
- const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS6) / 1e3;
6685
+ const toeGpsMs = GPS_EPOCH_MS7 + (week * SEC_PER_WEEK5 + tow) * 1e3;
6686
+ const tofGpsMs = GPS_EPOCH_MS7 + (week * SEC_PER_WEEK5 + tof) * 1e3;
6687
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS7) / 1e3;
6408
6688
  return {
6409
6689
  system: "R",
6410
6690
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -6412,7 +6692,7 @@ function decodeGloEphemeris(view, p) {
6412
6692
  tauN: -view.getFloat64(p + 100, true),
6413
6693
  // RINEX stores −τ_n
6414
6694
  gammaN: view.getFloat64(p + 116, true),
6415
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK4 + SEC_PER_WEEK4) % SEC_PER_WEEK4,
6695
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK5 + SEC_PER_WEEK5) % SEC_PER_WEEK5,
6416
6696
  x: view.getFloat64(p + 28, true) / 1e3,
6417
6697
  y: view.getFloat64(p + 36, true) / 1e3,
6418
6698
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -6469,7 +6749,7 @@ function decodeGalEphemeris(view, p, headerMs) {
6469
6749
  omega: view.getFloat64(p + 60, true),
6470
6750
  omegaDot: view.getFloat64(p + 140, true),
6471
6751
  idot: view.getFloat64(p + 124, true),
6472
- week: Math.floor((toeMs - GPS_EPOCH_MS6) / MS_PER_WEEK5),
6752
+ week: Math.floor((toeMs - GPS_EPOCH_MS7) / MS_PER_WEEK5),
6473
6753
  // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
6474
6754
  // 3-5, E5b in 6-8) — same packing as RTKLIB.
6475
6755
  svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
@@ -6623,7 +6903,7 @@ function parseNovatelNav(data) {
6623
6903
  }
6624
6904
 
6625
6905
  // src/novatel/index.ts
6626
- var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6906
+ var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
6627
6907
  var MS_PER_WEEK6 = 7 * 864e5;
6628
6908
  var HDR = 28;
6629
6909
  var ID_RANGE = 43;
@@ -6793,7 +7073,7 @@ function parseNovatelRange(data) {
6793
7073
  if (!codes.includes(code)) codes.push(code);
6794
7074
  }
6795
7075
  epochs.push({
6796
- timeMs: GPS_EPOCH_MS7 + week * MS_PER_WEEK6 + towMs,
7076
+ timeMs: GPS_EPOCH_MS8 + week * MS_PER_WEEK6 + towMs,
6797
7077
  meas
6798
7078
  });
6799
7079
  }
@@ -6845,7 +7125,7 @@ function parseNovatelRange(data) {
6845
7125
  if (!codes.includes(code)) codes.push(code);
6846
7126
  }
6847
7127
  epochs.push({
6848
- timeMs: GPS_EPOCH_MS7 + week * MS_PER_WEEK6 + towMs,
7128
+ timeMs: GPS_EPOCH_MS8 + week * MS_PER_WEEK6 + towMs,
6849
7129
  meas
6850
7130
  });
6851
7131
  }
@@ -7204,10 +7484,10 @@ function selectBest(ephs, timeMs) {
7204
7484
  }
7205
7485
  return best;
7206
7486
  }
7207
- var GPS_EPOCH_MS8 = START_GPS_TIME.getTime();
7487
+ var GPS_EPOCH_MS9 = START_GPS_TIME.getTime();
7208
7488
  var MS_PER_WEEK7 = 7 * 864e5;
7209
7489
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
7210
- var GAL_EPOCH_MS = GPS_EPOCH_MS8 + 1024 * MS_PER_WEEK7;
7490
+ var GAL_EPOCH_MS = GPS_EPOCH_MS9 + 1024 * MS_PER_WEEK7;
7211
7491
  function ephInfoToEphemeris(info) {
7212
7492
  const sys = info.prn.charAt(0);
7213
7493
  if (sys === "R") {
@@ -7258,8 +7538,8 @@ function ephInfoToEphemeris(info) {
7258
7538
  } else if (sys === "E") {
7259
7539
  epochMs = GAL_EPOCH_MS;
7260
7540
  } else {
7261
- epochMs = GPS_EPOCH_MS8;
7262
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS8) / MS_PER_WEEK7;
7541
+ epochMs = GPS_EPOCH_MS9;
7542
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS9) / MS_PER_WEEK7;
7263
7543
  week += 1024 * Math.round((weeksNow - week) / 1024);
7264
7544
  }
7265
7545
  const tocDate = new Date(epochMs + week * MS_PER_WEEK7 + tocSec * 1e3);
@@ -7795,8 +8075,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
7795
8075
  const dtsClock = satClockCorrection(eph, tTx);
7796
8076
  const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
7797
8077
  if (!Number.isFinite(sat.x)) continue;
7798
- const isKepler2 = eph.system !== "R" && eph.system !== "S";
7799
- const dts = dtsClock - (tgd && isKepler2 ? eph.tgd : 0);
8078
+ const isKepler3 = eph.system !== "R" && eph.system !== "S";
8079
+ const dts = dtsClock - (tgd && isKepler3 ? eph.tgd : 0);
7800
8080
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
7801
8081
  const [sx, sy, sz] = sagnac(sat, travel);
7802
8082
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
@@ -10277,6 +10557,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
10277
10557
  ephInfoToEphemeris,
10278
10558
  euclidean3D,
10279
10559
  fetchSourcetable,
10560
+ fmtD,
10280
10561
  fmtF,
10281
10562
  formatUTCTime,
10282
10563
  frequencyLabel,
@@ -10404,5 +10685,6 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
10404
10685
  writeRinex2ObsBlob,
10405
10686
  writeRinex4ObsBlob,
10406
10687
  writeRinexNav,
10688
+ writeRinexNav4,
10407
10689
  writeRinexObsBlob
10408
10690
  });