gnss-js 1.17.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,
@@ -222,12 +223,14 @@ __export(src_exports, {
222
223
  parseNovatelRange: () => parseNovatelRange,
223
224
  parseRinexStream: () => parseRinexStream,
224
225
  parseSbfAlmanac: () => parseSbfAlmanac,
226
+ parseSbfCnav: () => parseSbfCnav,
225
227
  parseSbfIonoUtc: () => parseSbfIonoUtc,
226
228
  parseSbfMeas: () => parseSbfMeas,
227
229
  parseSbfNav: () => parseSbfNav,
228
230
  parseSinexBiasDcb: () => parseSinexBiasDcb,
229
231
  parseSourcetable: () => parseSourcetable,
230
232
  parseSp3: () => parseSp3,
233
+ parseUbxCnav: () => parseUbxCnav,
231
234
  parseUbxIonoUtc: () => parseUbxIonoUtc,
232
235
  parseUbxNav: () => parseUbxNav,
233
236
  parseUbxRawx: () => parseUbxRawx,
@@ -263,6 +266,7 @@ __export(src_exports, {
263
266
  writeRinex2ObsBlob: () => writeRinex2ObsBlob,
264
267
  writeRinex4ObsBlob: () => writeRinex4ObsBlob,
265
268
  writeRinexNav: () => writeRinexNav,
269
+ writeRinexNav4: () => writeRinexNav4,
266
270
  writeRinexObsBlob: () => writeRinexObsBlob
267
271
  });
268
272
  module.exports = __toCommonJS(src_exports);
@@ -2183,6 +2187,343 @@ var WarningAccumulator = class {
2183
2187
  }
2184
2188
  };
2185
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
+
2186
2527
  // src/rinex/nav.ts
2187
2528
  var DATA_LINES = {
2188
2529
  G: 7,
@@ -2206,19 +2547,19 @@ var R4_MSG_LINES = {
2206
2547
  SBAS: 4,
2207
2548
  FDMA: 5,
2208
2549
  // RINEX 4 adds orbit-4 (status flags) vs 3 in RINEX 3
2209
- // EPH types we skip (different field layout)
2550
+ // EPH type parsed into RinexCnavEphemeris for G/J (skipped otherwise)
2210
2551
  CNAV: 9,
2552
+ // EPH types we skip (different field layout)
2211
2553
  CNV1: 10,
2212
2554
  CNV2: 10,
2213
2555
  CNV3: 9,
2214
2556
  L1NV: 8,
2215
2557
  L1OC: 9,
2216
- L3OC: 9,
2217
- // Non-EPH records
2218
- STO: 2,
2219
- EOP: 3,
2220
- ION: 3
2558
+ L3OC: 9
2221
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;
2222
2563
  var SUPPORTED_EPH_MSGS = /* @__PURE__ */ new Set([
2223
2564
  "LNAV",
2224
2565
  "INAV",
@@ -2282,6 +2623,20 @@ function parseDataLine(line, colOffset = 4) {
2282
2623
  }
2283
2624
  return values;
2284
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
+ }
2285
2640
  function buildKeplerEphemeris(sys, prn, date, epochVals, data) {
2286
2641
  const d = [];
2287
2642
  for (const line of data) {
@@ -2344,6 +2699,68 @@ function buildStateVectorEphemeris(sys, prn, date, epochVals, data) {
2344
2699
  zAcc: d[10] ?? 0
2345
2700
  };
2346
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
+ }
2347
2764
  function parseNavFile(text) {
2348
2765
  const lines = text.split("\n");
2349
2766
  const header = {
@@ -2353,6 +2770,7 @@ function parseNavFile(text) {
2353
2770
  ionoCorrections: {}
2354
2771
  };
2355
2772
  const ephemerides = [];
2773
+ const cnav = [];
2356
2774
  let inHeader = true;
2357
2775
  let i = 0;
2358
2776
  while (i < lines.length) {
@@ -2413,8 +2831,15 @@ function parseNavFile(text) {
2413
2831
  if (isV4 && line.charAt(0) === ">") {
2414
2832
  const parts = line.substring(2).trim().split(/\s+/);
2415
2833
  const recType = parts[0] ?? "";
2834
+ const svSys = (parts[1] ?? "").charAt(0);
2416
2835
  const msgType = parts[2] ?? recType;
2417
- 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) {
2418
2843
  const totalLines = R4_MSG_LINES[msgType] ?? 2;
2419
2844
  i += totalLines + 1;
2420
2845
  continue;
@@ -2426,6 +2851,21 @@ function parseNavFile(text) {
2426
2851
  const sys2 = epochLine.charAt(0);
2427
2852
  const parsed = parseNavEpochV3(epochLine);
2428
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
+ }
2429
2869
  const dataLines2 = [];
2430
2870
  for (let j = 0; j < numDataLines2; j++) {
2431
2871
  i++;
@@ -2499,7 +2939,7 @@ function parseNavFile(text) {
2499
2939
  }
2500
2940
  i++;
2501
2941
  }
2502
- return { header, ephemerides };
2942
+ return cnav.length > 0 ? { header, ephemerides, cnav } : { header, ephemerides };
2503
2943
  }
2504
2944
 
2505
2945
  // src/rinex/nav-writer.ts
@@ -2507,9 +2947,13 @@ function fmtD(val) {
2507
2947
  if (val === 0) return " 0.000000000000E+00";
2508
2948
  const sign = val < 0 ? "-" : " ";
2509
2949
  const abs = Math.abs(val);
2510
- const exp = Math.floor(Math.log10(abs));
2950
+ let exp = Math.floor(Math.log10(abs));
2511
2951
  const mantissa = abs / 10 ** exp;
2512
- 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
+ }
2513
2957
  const expSign = exp >= 0 ? "+" : "-";
2514
2958
  const expStr = String(Math.abs(exp)).padStart(2, "0");
2515
2959
  return `${sign}${mStr}E${expSign}${expStr}`;
@@ -2584,7 +3028,7 @@ function writeKeplerRecord(eph) {
2584
3028
  function writeGlonassRecord(eph) {
2585
3029
  const lines = [];
2586
3030
  lines.push(
2587
- `${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)}`
2588
3032
  );
2589
3033
  lines.push(orbitLine([eph.x, eph.xDot, eph.xAcc, eph.health]));
2590
3034
  lines.push(orbitLine([eph.y, eph.yDot, eph.yAcc, eph.freqNum]));
@@ -2604,6 +3048,181 @@ function writeRinexNav(nav) {
2604
3048
  return header + records.join("\n") + "\n";
2605
3049
  }
2606
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
+
2607
3226
  // src/rinex/ionex.ts
2608
3227
  function gridRange(l1, l2, dl) {
2609
3228
  const out = [];
@@ -3227,7 +3846,7 @@ var BitReader = class {
3227
3846
  return this.data.length * 8 - this.bitPos;
3228
3847
  }
3229
3848
  };
3230
- function crc24q(data, length) {
3849
+ function crc24q2(data, length) {
3231
3850
  let crc = 0;
3232
3851
  for (let i = 0; i < length; i++) {
3233
3852
  crc ^= data[i] << 16;
@@ -3269,7 +3888,7 @@ var Rtcm3Decoder = class {
3269
3888
  }
3270
3889
  const frameSize = 3 + length + 3;
3271
3890
  if (this.buffer.length < frameSize) break;
3272
- const crcComputed = crc24q(this.buffer, 3 + length);
3891
+ const crcComputed = crc24q2(this.buffer, 3 + length);
3273
3892
  const crcReceived = this.buffer[3 + length] << 16 | this.buffer[3 + length + 1] << 8 | this.buffer[3 + length + 2];
3274
3893
  if (crcComputed !== crcReceived) {
3275
3894
  this.buffer = this.buffer.slice(1);
@@ -4636,147 +5255,6 @@ function* ubxFrames(data, stats = { badChecksums: 0 }) {
4636
5255
  }
4637
5256
  }
4638
5257
 
4639
- // src/navbits/index.ts
4640
- var GPS_PI = 3.1415926535898;
4641
- var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
4642
- var SEC_PER_WEEK = 7 * 86400;
4643
- function getBitU(buff, pos, len) {
4644
- let bits = 0;
4645
- for (let i = pos; i < pos + len; i++) {
4646
- bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
4647
- }
4648
- return bits;
4649
- }
4650
- function getBitS(buff, pos, len) {
4651
- const bits = getBitU(buff, pos, len);
4652
- if (len <= 0 || bits < 2 ** (len - 1)) return bits;
4653
- return bits - 2 ** len;
4654
- }
4655
- function setBitU(buff, pos, len, data) {
4656
- let mask = 2 ** (len - 1);
4657
- for (let i = pos; i < pos + len; i++, mask /= 2) {
4658
- if (data >= mask) {
4659
- buff[i >> 3] |= 1 << 7 - (i & 7);
4660
- data -= mask;
4661
- } else {
4662
- buff[i >> 3] &= ~(1 << 7 - (i & 7));
4663
- }
4664
- }
4665
- }
4666
- function decodeGpsLnavFrame(subframes, opts = {}) {
4667
- if (subframes.length < 90) return null;
4668
- const b = subframes;
4669
- let i = 24;
4670
- const tow1 = getBitU(b, i, 17) * 6;
4671
- i += 17 + 2;
4672
- const id1 = getBitU(b, i, 3);
4673
- i += 3 + 2;
4674
- const week10 = getBitU(b, i, 10);
4675
- i += 10;
4676
- i += 2;
4677
- i += 4;
4678
- const svHealth = getBitU(b, i, 6);
4679
- i += 6;
4680
- const iodc0 = getBitU(b, i, 2);
4681
- i += 2;
4682
- i += 1 + 87;
4683
- const tgdRaw = getBitS(b, i, 8);
4684
- i += 8;
4685
- const iodc1 = getBitU(b, i, 8);
4686
- i += 8;
4687
- const tocSec = getBitU(b, i, 16) * 16;
4688
- i += 16;
4689
- const af2 = getBitS(b, i, 8) * 2 ** -55;
4690
- i += 8;
4691
- const af1 = getBitS(b, i, 16) * 2 ** -43;
4692
- i += 16;
4693
- const af0 = getBitS(b, i, 22) * 2 ** -31;
4694
- i = 240 + 24;
4695
- i += 17 + 2;
4696
- const id2 = getBitU(b, i, 3);
4697
- i += 3 + 2;
4698
- const iode = getBitU(b, i, 8);
4699
- i += 8;
4700
- const crs = getBitS(b, i, 16) * 2 ** -5;
4701
- i += 16;
4702
- const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
4703
- i += 16;
4704
- const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4705
- i += 32;
4706
- const cuc = getBitS(b, i, 16) * 2 ** -29;
4707
- i += 16;
4708
- const e = getBitU(b, i, 32) * 2 ** -33;
4709
- i += 32;
4710
- const cus = getBitS(b, i, 16) * 2 ** -29;
4711
- i += 16;
4712
- const sqrtA = getBitU(b, i, 32) * 2 ** -19;
4713
- i += 32;
4714
- const toes = getBitU(b, i, 16) * 16;
4715
- i = 480 + 24;
4716
- i += 17 + 2;
4717
- const id3 = getBitU(b, i, 3);
4718
- i += 3 + 2;
4719
- const cic = getBitS(b, i, 16) * 2 ** -29;
4720
- i += 16;
4721
- const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4722
- i += 32;
4723
- const cis = getBitS(b, i, 16) * 2 ** -29;
4724
- i += 16;
4725
- const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4726
- i += 32;
4727
- const crc = getBitS(b, i, 16) * 2 ** -5;
4728
- i += 16;
4729
- const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
4730
- i += 32;
4731
- const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
4732
- i += 24;
4733
- const iode3 = getBitU(b, i, 8);
4734
- i += 8;
4735
- const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
4736
- const iodc = iodc0 * 256 + iodc1;
4737
- if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
4738
- if (iode3 !== iode || iode !== (iodc & 255)) return null;
4739
- const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
4740
- let week = week10 + 1024 * Math.round((ref - week10) / 1024);
4741
- if (toes < tow1 - 302400) week++;
4742
- else if (toes > tow1 + 302400) week--;
4743
- const prn = opts.prn ?? "G00";
4744
- const tocDate = new Date(
4745
- GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
4746
- );
4747
- return {
4748
- system: prn[0] === "J" ? "J" : "G",
4749
- prn,
4750
- tocDate,
4751
- // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
4752
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
4753
- af0,
4754
- af1,
4755
- af2,
4756
- iode,
4757
- crs,
4758
- deltaN,
4759
- m0,
4760
- cuc,
4761
- e,
4762
- cus,
4763
- sqrtA,
4764
- toe: toes,
4765
- cic,
4766
- omega0,
4767
- cis,
4768
- i0,
4769
- crc,
4770
- omega,
4771
- omegaDot,
4772
- idot,
4773
- week,
4774
- svHealth,
4775
- tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
4776
- // IS-GPS-200: -128 reserved
4777
- };
4778
- }
4779
-
4780
5258
  // src/ubx/nav.ts
4781
5259
  var PREAMB_CNAV = 139;
4782
5260
  function readLnavSubframe(view, f) {
@@ -4884,8 +5362,42 @@ function parseUbxIonoUtc(data) {
4884
5362
  return { ionoCorrections, leapSeconds };
4885
5363
  }
4886
5364
 
5365
+ // src/ubx/cnav.ts
5366
+ function parseUbxCnav(data) {
5367
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5368
+ const assembler = new CnavAssembler();
5369
+ const ephemerides = [];
5370
+ let badCrc = 0;
5371
+ let messages = 0;
5372
+ for (const f of ubxFrames(data)) {
5373
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
5374
+ const p = f.payload;
5375
+ if (p.length < 8 + 40) continue;
5376
+ if (p[0] !== 0 || p[2] !== 3 && p[2] !== 4) continue;
5377
+ const svId = p[1];
5378
+ if (svId < 1 || svId > 32) continue;
5379
+ messages++;
5380
+ const msg = new Uint8Array(40);
5381
+ const base = f.payloadStart + 8;
5382
+ for (let k = 0; k < 10; k++) {
5383
+ const w = view.getUint32(base + 4 * k, true);
5384
+ msg[4 * k] = w >>> 24;
5385
+ msg[4 * k + 1] = w >>> 16 & 255;
5386
+ msg[4 * k + 2] = w >>> 8 & 255;
5387
+ msg[4 * k + 3] = w & 255;
5388
+ }
5389
+ if (!cnavCrcOk(msg)) {
5390
+ badCrc++;
5391
+ continue;
5392
+ }
5393
+ const eph = assembler.push(msg);
5394
+ if (eph) ephemerides.push(eph);
5395
+ }
5396
+ return { ephemerides, badCrc, messages };
5397
+ }
5398
+
4887
5399
  // src/ubx/index.ts
4888
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
5400
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
4889
5401
  var MS_PER_WEEK2 = 7 * 864e5;
4890
5402
  var SIGNALS = {
4891
5403
  0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
@@ -4968,7 +5480,7 @@ function parseUbxRawx(data) {
4968
5480
  if (!codes.includes(sig[1])) codes.push(sig[1]);
4969
5481
  }
4970
5482
  epochs.push({
4971
- timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
5483
+ timeMs: GPS_EPOCH_MS4 + week * MS_PER_WEEK2 + Math.round(rcvTow * 1e3),
4972
5484
  leapS: leapValid ? leapS : null,
4973
5485
  meas
4974
5486
  });
@@ -5039,10 +5551,10 @@ function svidToPrn(svid) {
5039
5551
 
5040
5552
  // src/sbf/nav.ts
5041
5553
  var PI2 = Math.PI;
5042
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
5554
+ var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
5043
5555
  var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
5044
- var SEC_PER_WEEK2 = 7 * 86400;
5045
- var MS_PER_WEEK3 = SEC_PER_WEEK2 * 1e3;
5556
+ var SEC_PER_WEEK4 = 7 * 86400;
5557
+ var MS_PER_WEEK3 = SEC_PER_WEEK4 * 1e3;
5046
5558
  var F4_DNU = -2e10;
5047
5559
  var U4_DNU = 4294967295;
5048
5560
  function adjustWeek(refWeek, wn, mod) {
@@ -5051,8 +5563,8 @@ function adjustWeek(refWeek, wn, mod) {
5051
5563
  if (offset < -(mod / 2 - 1)) offset += mod;
5052
5564
  return refWeek - offset;
5053
5565
  }
5054
- var gpsMs = (week, sec) => GPS_EPOCH_MS3 + week * MS_PER_WEEK3 + sec * 1e3;
5055
- var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK2;
5566
+ var gpsMs = (week, sec) => GPS_EPOCH_MS5 + week * MS_PER_WEEK3 + sec * 1e3;
5567
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
5056
5568
  function decodeGpsQzsNav(view, b, sys) {
5057
5569
  const svid = view.getUint8(b + 14);
5058
5570
  const prn = sys === "G" ? svid : svid - 180;
@@ -5201,7 +5713,7 @@ function decodeGloNav(view, b) {
5201
5713
  const tocDate = new Date(toeGpsMs - leapMs);
5202
5714
  const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
5203
5715
  const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
5204
- const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS3) / 1e3 % SEC_PER_WEEK2;
5716
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS5) / 1e3 % SEC_PER_WEEK4;
5205
5717
  return {
5206
5718
  system: "R",
5207
5719
  prn,
@@ -5379,9 +5891,45 @@ function parseSbfIonoUtc(data) {
5379
5891
  return { ionoCorrections, leapSeconds };
5380
5892
  }
5381
5893
 
5894
+ // src/sbf/rawnav.ts
5895
+ var SIGNAL_OF_BLOCK = {
5896
+ 4018: "L2C",
5897
+ 4019: "L5"
5898
+ };
5899
+ function parseSbfCnav(data) {
5900
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
5901
+ const ephemerides = [];
5902
+ const assemblers = {
5903
+ L2C: new CnavAssembler(),
5904
+ L5: new CnavAssembler()
5905
+ };
5906
+ let badCrc = 0;
5907
+ let messages = 0;
5908
+ scanSbfFrames(data, view, (id, b, len) => {
5909
+ const signal = SIGNAL_OF_BLOCK[id];
5910
+ if (!signal || len < 60) return;
5911
+ messages++;
5912
+ const msg = new Uint8Array(40);
5913
+ for (let k = 0; k < 10; k++) {
5914
+ const w = view.getUint32(b + 20 + 4 * k, true);
5915
+ msg[4 * k] = w >>> 24;
5916
+ msg[4 * k + 1] = w >>> 16 & 255;
5917
+ msg[4 * k + 2] = w >>> 8 & 255;
5918
+ msg[4 * k + 3] = w & 255;
5919
+ }
5920
+ if (!cnavCrcOk(msg)) {
5921
+ badCrc++;
5922
+ return;
5923
+ }
5924
+ const eph = assemblers[signal].push(msg);
5925
+ if (eph) ephemerides.push({ ...eph, signal });
5926
+ });
5927
+ return { ephemerides, badCrc, messages };
5928
+ }
5929
+
5382
5930
  // src/sbf/index.ts
5383
5931
  var CLIGHT = 299792458;
5384
- var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
5932
+ var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
5385
5933
  var MS_PER_WEEK4 = 7 * 864e5;
5386
5934
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
5387
5935
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -6035,7 +6583,7 @@ function parseSbfMeas(data) {
6035
6583
  const tow = view.getUint32(i + 8, true);
6036
6584
  const wnc = view.getUint16(i + 12, true);
6037
6585
  if (tow !== 4294967295 && wnc !== 65535) {
6038
- ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK4 + tow);
6586
+ ensureEpoch(GPS_EPOCH_MS6 + wnc * MS_PER_WEEK4 + tow);
6039
6587
  if (id === 4109) decodeMeas3Ranges(i, tow);
6040
6588
  else if (id === 4110) decodeMeas3CN0(i, len);
6041
6589
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -6097,16 +6645,17 @@ var ID_RAWEPHEM = 41;
6097
6645
  var ID_GLOEPHEMERIS = 723;
6098
6646
  var ID_GALEPHEMERIS = 1122;
6099
6647
  var ID_QZSSEPHEMERIS = 1336;
6648
+ var ID_GPSEPHEM = 7;
6100
6649
  var ID_BDSEPHEMERIS = 1696;
6101
- var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
6650
+ var GPS_EPOCH_MS7 = Date.UTC(1980, 0, 6);
6102
6651
  var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
6103
- var SEC_PER_WEEK3 = 7 * 86400;
6652
+ var SEC_PER_WEEK5 = 7 * 86400;
6104
6653
  var SEC_PER_DAY = 86400;
6105
- var MS_PER_WEEK5 = SEC_PER_WEEK3 * 1e3;
6106
- var gpsMs2 = (week, sec) => GPS_EPOCH_MS5 + week * MS_PER_WEEK5 + sec * 1e3;
6107
- var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK3;
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;
6108
6657
  function nearWeekMs(refMs, sow) {
6109
- const refWeek = Math.floor((refMs - GPS_EPOCH_MS5) / MS_PER_WEEK5);
6658
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS7) / MS_PER_WEEK5);
6110
6659
  let ms = gpsMs2(refWeek, sow);
6111
6660
  if (ms < refMs - MS_PER_WEEK5 / 2) ms += MS_PER_WEEK5;
6112
6661
  else if (ms > refMs + MS_PER_WEEK5 / 2) ms -= MS_PER_WEEK5;
@@ -6133,9 +6682,9 @@ function decodeGloEphemeris(view, p) {
6133
6682
  tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
6134
6683
  if (tof < tow - 43200) tof += SEC_PER_DAY;
6135
6684
  else if (tof > tow + 43200) tof -= SEC_PER_DAY;
6136
- const toeGpsMs = GPS_EPOCH_MS5 + (week * SEC_PER_WEEK3 + tow) * 1e3;
6137
- const tofGpsMs = GPS_EPOCH_MS5 + (week * SEC_PER_WEEK3 + tof) * 1e3;
6138
- const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS5) / 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;
6139
6688
  return {
6140
6689
  system: "R",
6141
6690
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -6143,7 +6692,7 @@ function decodeGloEphemeris(view, p) {
6143
6692
  tauN: -view.getFloat64(p + 100, true),
6144
6693
  // RINEX stores −τ_n
6145
6694
  gammaN: view.getFloat64(p + 116, true),
6146
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK3 + SEC_PER_WEEK3) % SEC_PER_WEEK3,
6695
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK5 + SEC_PER_WEEK5) % SEC_PER_WEEK5,
6147
6696
  x: view.getFloat64(p + 28, true) / 1e3,
6148
6697
  y: view.getFloat64(p + 36, true) / 1e3,
6149
6698
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -6200,7 +6749,7 @@ function decodeGalEphemeris(view, p, headerMs) {
6200
6749
  omega: view.getFloat64(p + 60, true),
6201
6750
  omegaDot: view.getFloat64(p + 140, true),
6202
6751
  idot: view.getFloat64(p + 124, true),
6203
- week: Math.floor((toeMs - GPS_EPOCH_MS5) / MS_PER_WEEK5),
6752
+ week: Math.floor((toeMs - GPS_EPOCH_MS7) / MS_PER_WEEK5),
6204
6753
  // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
6205
6754
  // 3-5, E5b in 6-8) — same packing as RTKLIB.
6206
6755
  svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
@@ -6251,9 +6800,10 @@ function decodeBdsEphemeris2(view, p) {
6251
6800
  // TGD1 (B1) — RINEX slot
6252
6801
  };
6253
6802
  }
6254
- function decodeQzssEphemeris2(view, p) {
6803
+ function decodeGpsQzssEphemeris(view, p, sys) {
6255
6804
  const prn = view.getUint32(p, true);
6256
- if (prn < 193 || prn > 202) return null;
6805
+ if (sys === "G" && (prn < 1 || prn > 32)) return null;
6806
+ if (sys === "J" && (prn < 193 || prn > 202)) return null;
6257
6807
  const health = view.getUint32(p + 12, true) & 63;
6258
6808
  const iode1 = view.getUint32(p + 16, true);
6259
6809
  const week = view.getUint32(p + 24, true);
@@ -6262,8 +6812,8 @@ function decodeQzssEphemeris2(view, p) {
6262
6812
  const tocs = view.getFloat64(p + 164, true);
6263
6813
  const tocDate = new Date(nearWeekMs(toeMs, tocs));
6264
6814
  return {
6265
- system: "J",
6266
- prn: `J${String(prn - 192).padStart(2, "0")}`,
6815
+ system: sys,
6816
+ prn: `${sys}${String(sys === "J" ? prn - 192 : prn).padStart(2, "0")}`,
6267
6817
  toc: sowOf2(tocDate.getTime()),
6268
6818
  tocDate,
6269
6819
  af0: view.getFloat64(p + 180, true),
@@ -6321,8 +6871,11 @@ function parseNovatelNav(data) {
6321
6871
  } else if (frame.id === ID_BDSEPHEMERIS && frame.msgLen >= 196) {
6322
6872
  const eph = decodeBdsEphemeris2(view, frame.payload);
6323
6873
  if (eph) pushKepler(eph.prn, eph);
6874
+ } else if (frame.id === ID_GPSEPHEM && frame.msgLen >= 204) {
6875
+ const eph = decodeGpsQzssEphemeris(view, frame.payload, "G");
6876
+ if (eph) pushKepler(eph.prn, eph);
6324
6877
  } else if (frame.id === ID_QZSSEPHEMERIS && frame.msgLen >= 204) {
6325
- const eph = decodeQzssEphemeris2(view, frame.payload);
6878
+ const eph = decodeGpsQzssEphemeris(view, frame.payload, "J");
6326
6879
  if (eph) pushKepler(eph.prn, eph);
6327
6880
  } else if (frame.id === ID_IONUTC && frame.msgLen >= 108) {
6328
6881
  const p = frame.payload;
@@ -6350,7 +6903,7 @@ function parseNovatelNav(data) {
6350
6903
  }
6351
6904
 
6352
6905
  // src/novatel/index.ts
6353
- var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
6906
+ var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
6354
6907
  var MS_PER_WEEK6 = 7 * 864e5;
6355
6908
  var HDR = 28;
6356
6909
  var ID_RANGE = 43;
@@ -6520,7 +7073,7 @@ function parseNovatelRange(data) {
6520
7073
  if (!codes.includes(code)) codes.push(code);
6521
7074
  }
6522
7075
  epochs.push({
6523
- timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK6 + towMs,
7076
+ timeMs: GPS_EPOCH_MS8 + week * MS_PER_WEEK6 + towMs,
6524
7077
  meas
6525
7078
  });
6526
7079
  }
@@ -6572,7 +7125,7 @@ function parseNovatelRange(data) {
6572
7125
  if (!codes.includes(code)) codes.push(code);
6573
7126
  }
6574
7127
  epochs.push({
6575
- timeMs: GPS_EPOCH_MS6 + week * MS_PER_WEEK6 + towMs,
7128
+ timeMs: GPS_EPOCH_MS8 + week * MS_PER_WEEK6 + towMs,
6576
7129
  meas
6577
7130
  });
6578
7131
  }
@@ -6931,10 +7484,10 @@ function selectBest(ephs, timeMs) {
6931
7484
  }
6932
7485
  return best;
6933
7486
  }
6934
- var GPS_EPOCH_MS7 = START_GPS_TIME.getTime();
7487
+ var GPS_EPOCH_MS9 = START_GPS_TIME.getTime();
6935
7488
  var MS_PER_WEEK7 = 7 * 864e5;
6936
7489
  var BDS_EPOCH_MS = START_BDS_TIME.getTime();
6937
- var GAL_EPOCH_MS = GPS_EPOCH_MS7 + 1024 * MS_PER_WEEK7;
7490
+ var GAL_EPOCH_MS = GPS_EPOCH_MS9 + 1024 * MS_PER_WEEK7;
6938
7491
  function ephInfoToEphemeris(info) {
6939
7492
  const sys = info.prn.charAt(0);
6940
7493
  if (sys === "R") {
@@ -6985,8 +7538,8 @@ function ephInfoToEphemeris(info) {
6985
7538
  } else if (sys === "E") {
6986
7539
  epochMs = GAL_EPOCH_MS;
6987
7540
  } else {
6988
- epochMs = GPS_EPOCH_MS7;
6989
- const weeksNow = (info.lastReceived - GPS_EPOCH_MS7) / MS_PER_WEEK7;
7541
+ epochMs = GPS_EPOCH_MS9;
7542
+ const weeksNow = (info.lastReceived - GPS_EPOCH_MS9) / MS_PER_WEEK7;
6990
7543
  week += 1024 * Math.round((weeksNow - week) / 1024);
6991
7544
  }
6992
7545
  const tocDate = new Date(epochMs + week * MS_PER_WEEK7 + tocSec * 1e3);
@@ -7522,8 +8075,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
7522
8075
  const dtsClock = satClockCorrection(eph, tTx);
7523
8076
  const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
7524
8077
  if (!Number.isFinite(sat.x)) continue;
7525
- const isKepler2 = eph.system !== "R" && eph.system !== "S";
7526
- 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);
7527
8080
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
7528
8081
  const [sx, sy, sz] = sagnac(sat, travel);
7529
8082
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
@@ -10004,6 +10557,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
10004
10557
  ephInfoToEphemeris,
10005
10558
  euclidean3D,
10006
10559
  fetchSourcetable,
10560
+ fmtD,
10007
10561
  fmtF,
10008
10562
  formatUTCTime,
10009
10563
  frequencyLabel,
@@ -10088,12 +10642,14 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
10088
10642
  parseNovatelRange,
10089
10643
  parseRinexStream,
10090
10644
  parseSbfAlmanac,
10645
+ parseSbfCnav,
10091
10646
  parseSbfIonoUtc,
10092
10647
  parseSbfMeas,
10093
10648
  parseSbfNav,
10094
10649
  parseSinexBiasDcb,
10095
10650
  parseSourcetable,
10096
10651
  parseSp3,
10652
+ parseUbxCnav,
10097
10653
  parseUbxIonoUtc,
10098
10654
  parseUbxNav,
10099
10655
  parseUbxRawx,
@@ -10129,5 +10685,6 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
10129
10685
  writeRinex2ObsBlob,
10130
10686
  writeRinex4ObsBlob,
10131
10687
  writeRinexNav,
10688
+ writeRinexNav4,
10132
10689
  writeRinexObsBlob
10133
10690
  });