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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CnavAssembler,
3
3
  cnavCrcOk
4
- } from "./chunk-Y5IWUPJQ.js";
4
+ } from "./chunk-6NCMACDO.js";
5
5
  import {
6
6
  getGpsLeap
7
7
  } from "./chunk-HVXYFUCB.js";
@@ -201,6 +201,8 @@ var CnavAssembler = class {
201
201
  };
202
202
 
203
203
  export {
204
+ CNAV_A_REF,
205
+ CNAV_OMEGA_DOT_REF,
204
206
  cnavCrcOk,
205
207
  CnavAssembler
206
208
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CnavAssembler,
3
3
  cnavCrcOk
4
- } from "./chunk-Y5IWUPJQ.js";
4
+ } from "./chunk-6NCMACDO.js";
5
5
  import {
6
6
  decodeGpsLnavFrame,
7
7
  getBitS,
@@ -1,3 +1,11 @@
1
+ import {
2
+ CNAV_A_REF,
3
+ CNAV_OMEGA_DOT_REF
4
+ } from "./chunk-6NCMACDO.js";
5
+ import {
6
+ GPS_PI
7
+ } from "./chunk-IQ5OIMQD.js";
8
+
1
9
  // src/rinex/format.ts
2
10
  function padL(s, w) {
3
11
  return s.length >= w ? s.slice(0, w) : s + " ".repeat(w - s.length);
@@ -212,19 +220,19 @@ var R4_MSG_LINES = {
212
220
  SBAS: 4,
213
221
  FDMA: 5,
214
222
  // RINEX 4 adds orbit-4 (status flags) vs 3 in RINEX 3
215
- // EPH types we skip (different field layout)
223
+ // EPH type parsed into RinexCnavEphemeris for G/J (skipped otherwise)
216
224
  CNAV: 9,
225
+ // EPH types we skip (different field layout)
217
226
  CNV1: 10,
218
227
  CNV2: 10,
219
228
  CNV3: 9,
220
229
  L1NV: 8,
221
230
  L1OC: 9,
222
- L3OC: 9,
223
- // Non-EPH records
224
- STO: 2,
225
- EOP: 3,
226
- ION: 3
231
+ L3OC: 9
227
232
  };
233
+ var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
234
+ var SEC_PER_WEEK = 7 * 86400;
235
+ var QZSS_CNAV_A_REF = 42164200;
228
236
  var SUPPORTED_EPH_MSGS = /* @__PURE__ */ new Set([
229
237
  "LNAV",
230
238
  "INAV",
@@ -288,6 +296,20 @@ function parseDataLine(line, colOffset = 4) {
288
296
  }
289
297
  return values;
290
298
  }
299
+ function parseDataLineSlots(line) {
300
+ const values = [];
301
+ for (let i = 0; i < 4; i++) {
302
+ const start = 4 + i * 19;
303
+ const s = start < line.length ? line.substring(start, start + 19).trim() : "";
304
+ if (s.length === 0) {
305
+ values.push(null);
306
+ continue;
307
+ }
308
+ const v = parseFloat19(s);
309
+ values.push(Number.isFinite(v) ? v : null);
310
+ }
311
+ return values;
312
+ }
291
313
  function buildKeplerEphemeris(sys, prn, date, epochVals, data) {
292
314
  const d = [];
293
315
  for (const line of data) {
@@ -350,6 +372,68 @@ function buildStateVectorEphemeris(sys, prn, date, epochVals, data) {
350
372
  zAcc: d[10] ?? 0
351
373
  };
352
374
  }
375
+ function buildCnavEphemeris(sys, prn, date, epochVals, d) {
376
+ const num = (r, c) => d[r]?.[c] ?? 0;
377
+ const opt = (r, c) => d[r]?.[c] ?? null;
378
+ const t = (date.getTime() - GPS_EPOCH_MS) / 1e3;
379
+ const week = Math.floor(t / SEC_PER_WEEK);
380
+ const toc = t - week * SEC_PER_WEEK;
381
+ const sqrtA = num(1, 3);
382
+ const a = sqrtA * sqrtA;
383
+ const aRef = sys === "J" ? QZSS_CNAV_A_REF : CNAV_A_REF;
384
+ const omegaDot = num(3, 3);
385
+ const wnOp = d[7]?.[1];
386
+ return {
387
+ system: sys,
388
+ prn,
389
+ week,
390
+ health: num(5, 1),
391
+ uraEd: num(5, 0),
392
+ uraNed0: num(4, 2),
393
+ uraNed1: num(4, 3),
394
+ uraNed2: num(5, 3),
395
+ top: num(2, 0),
396
+ toe: toc,
397
+ toeDate: date,
398
+ a,
399
+ deltaA: a - aRef,
400
+ aDot: num(0, 0),
401
+ deltaN0: num(0, 2),
402
+ deltaN0Dot: num(4, 1),
403
+ m0: num(0, 3),
404
+ e: num(1, 1),
405
+ omega: num(3, 2),
406
+ omega0: num(2, 2),
407
+ i0: num(3, 0),
408
+ omegaDot,
409
+ deltaOmegaDot: omegaDot - CNAV_OMEGA_DOT_REF * GPS_PI,
410
+ i0Dot: num(4, 0),
411
+ cis: num(2, 3),
412
+ cic: num(2, 1),
413
+ crs: num(0, 1),
414
+ crc: num(3, 1),
415
+ cus: num(1, 2),
416
+ cuc: num(1, 0),
417
+ toc,
418
+ tocDate: date,
419
+ af0: epochVals[0] ?? 0,
420
+ af1: epochVals[1] ?? 0,
421
+ af2: epochVals[2] ?? 0,
422
+ clockMsgType: 0,
423
+ // not recorded in RINEX
424
+ tgd: opt(5, 2),
425
+ iscL1ca: opt(6, 0),
426
+ iscL2c: opt(6, 1),
427
+ iscL5i5: opt(6, 2),
428
+ iscL5q5: opt(6, 3),
429
+ ...wnOp != null && { wnOp },
430
+ integrityFlag: false,
431
+ // not recorded in RINEX
432
+ l2cPhasing: false,
433
+ // not recorded in RINEX
434
+ tow: num(7, 0)
435
+ };
436
+ }
353
437
  function parseNavFile(text) {
354
438
  const lines = text.split("\n");
355
439
  const header = {
@@ -359,6 +443,7 @@ function parseNavFile(text) {
359
443
  ionoCorrections: {}
360
444
  };
361
445
  const ephemerides = [];
446
+ const cnav = [];
362
447
  let inHeader = true;
363
448
  let i = 0;
364
449
  while (i < lines.length) {
@@ -419,8 +504,15 @@ function parseNavFile(text) {
419
504
  if (isV4 && line.charAt(0) === ">") {
420
505
  const parts = line.substring(2).trim().split(/\s+/);
421
506
  const recType = parts[0] ?? "";
507
+ const svSys = (parts[1] ?? "").charAt(0);
422
508
  const msgType = parts[2] ?? recType;
423
- if (recType !== "EPH" || !SUPPORTED_EPH_MSGS.has(msgType)) {
509
+ if (recType !== "EPH") {
510
+ const totalLines = recType === "STO" ? 2 : recType === "EOP" ? 3 : recType === "ION" ? svSys === "E" ? 2 : 3 : 2;
511
+ i += totalLines + 1;
512
+ continue;
513
+ }
514
+ const isCnav = msgType === "CNAV" && (svSys === "G" || svSys === "J");
515
+ if (!SUPPORTED_EPH_MSGS.has(msgType) && !isCnav) {
424
516
  const totalLines = R4_MSG_LINES[msgType] ?? 2;
425
517
  i += totalLines + 1;
426
518
  continue;
@@ -432,6 +524,21 @@ function parseNavFile(text) {
432
524
  const sys2 = epochLine.charAt(0);
433
525
  const parsed = parseNavEpochV3(epochLine);
434
526
  const prn2 = `${sys2}${parsed.prn.substring(1)}`;
527
+ if (isCnav) {
528
+ const slotLines = [];
529
+ for (let j = 0; j < numDataLines2; j++) {
530
+ i++;
531
+ if (i >= lines.length) break;
532
+ slotLines.push(parseDataLineSlots(lines[i]));
533
+ }
534
+ if (slotLines.length === numDataLines2 && (sys2 === "G" || sys2 === "J")) {
535
+ cnav.push(
536
+ buildCnavEphemeris(sys2, prn2, parsed.date, parsed.values, slotLines)
537
+ );
538
+ }
539
+ i++;
540
+ continue;
541
+ }
435
542
  const dataLines2 = [];
436
543
  for (let j = 0; j < numDataLines2; j++) {
437
544
  i++;
@@ -505,7 +612,7 @@ function parseNavFile(text) {
505
612
  }
506
613
  i++;
507
614
  }
508
- return { header, ephemerides };
615
+ return cnav.length > 0 ? { header, ephemerides, cnav } : { header, ephemerides };
509
616
  }
510
617
 
511
618
  // src/rinex/nav-writer.ts
@@ -513,9 +620,13 @@ function fmtD(val) {
513
620
  if (val === 0) return " 0.000000000000E+00";
514
621
  const sign = val < 0 ? "-" : " ";
515
622
  const abs = Math.abs(val);
516
- const exp = Math.floor(Math.log10(abs));
623
+ let exp = Math.floor(Math.log10(abs));
517
624
  const mantissa = abs / 10 ** exp;
518
- const mStr = mantissa.toFixed(12);
625
+ let mStr = mantissa.toFixed(12);
626
+ if (mStr.length > 14) {
627
+ exp += 1;
628
+ mStr = (mantissa / 10).toFixed(12);
629
+ }
519
630
  const expSign = exp >= 0 ? "+" : "-";
520
631
  const expStr = String(Math.abs(exp)).padStart(2, "0");
521
632
  return `${sign}${mStr}E${expSign}${expStr}`;
@@ -590,7 +701,7 @@ function writeKeplerRecord(eph) {
590
701
  function writeGlonassRecord(eph) {
591
702
  const lines = [];
592
703
  lines.push(
593
- `${padL(eph.prn, 3)} ${fmtEpoch(eph.tocDate)}${fmtD(-eph.tauN)}${fmtD(eph.gammaN)}${fmtD(eph.messageFrameTime)}`
704
+ `${padL(eph.prn, 3)} ${fmtEpoch(eph.tocDate)}${fmtD(eph.tauN)}${fmtD(eph.gammaN)}${fmtD(eph.messageFrameTime)}`
594
705
  );
595
706
  lines.push(orbitLine([eph.x, eph.xDot, eph.xAcc, eph.health]));
596
707
  lines.push(orbitLine([eph.y, eph.yDot, eph.yAcc, eph.freqNum]));
@@ -610,6 +721,181 @@ function writeRinexNav(nav) {
610
721
  return header + records.join("\n") + "\n";
611
722
  }
612
723
 
724
+ // src/rinex/nav-writer-v4.ts
725
+ var BLANK19 = " ".repeat(19);
726
+ function fmtEpoch4(d) {
727
+ const p2 = (v) => String(v).padStart(2, "0");
728
+ return `${d.getUTCFullYear()} ${p2(d.getUTCMonth() + 1)} ${p2(d.getUTCDate())} ${p2(d.getUTCHours())} ${p2(d.getUTCMinutes())} ${p2(d.getUTCSeconds())}`;
729
+ }
730
+ function orbitLine4(values) {
731
+ const line = " " + values.map((v) => v == null ? BLANK19 : fmtD(v)).join("");
732
+ return line.replace(/ +$/, "");
733
+ }
734
+ function epochLine4(prn, date, vals) {
735
+ return `${padL(prn, 3)} ${fmtEpoch4(date)}${vals.map(fmtD).join("")}`;
736
+ }
737
+ var BDS_GEO = /* @__PURE__ */ new Set([
738
+ "C01",
739
+ "C02",
740
+ "C03",
741
+ "C04",
742
+ "C05",
743
+ "C59",
744
+ "C60",
745
+ "C61",
746
+ "C62",
747
+ "C63"
748
+ ]);
749
+ function msgLabel(eph) {
750
+ switch (eph.system) {
751
+ case "E":
752
+ return "INAV";
753
+ case "C":
754
+ return BDS_GEO.has(eph.prn) ? "D2" : "D1";
755
+ case "R":
756
+ return "FDMA";
757
+ case "S":
758
+ return "SBAS";
759
+ default:
760
+ return "LNAV";
761
+ }
762
+ }
763
+ function isKepler2(eph) {
764
+ return "af0" in eph;
765
+ }
766
+ function keplerRecord(eph) {
767
+ return [
768
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
769
+ orbitLine4([eph.iode, eph.crs, eph.deltaN, eph.m0]),
770
+ orbitLine4([eph.cuc, eph.e, eph.cus, eph.sqrtA]),
771
+ orbitLine4([eph.toe, eph.cic, eph.omega0, eph.cis]),
772
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
773
+ orbitLine4([eph.idot, 0, eph.week, 0]),
774
+ orbitLine4([0, eph.svHealth, eph.tgd, 0]),
775
+ orbitLine4([eph.toe, 0, 0, 0])
776
+ ].join("\n");
777
+ }
778
+ function stateVectorRecord(eph) {
779
+ const lines = [
780
+ epochLine4(eph.prn, eph.tocDate, [
781
+ eph.tauN,
782
+ eph.gammaN,
783
+ eph.messageFrameTime
784
+ ]),
785
+ orbitLine4([eph.x, eph.xDot, eph.xAcc, eph.health]),
786
+ orbitLine4([eph.y, eph.yDot, eph.yAcc, eph.freqNum]),
787
+ orbitLine4([eph.z, eph.zDot, eph.zAcc, 0])
788
+ ];
789
+ if (eph.system === "R") {
790
+ lines.push(orbitLine4([null, 999999999999e-3, 15, null]));
791
+ }
792
+ return lines.join("\n");
793
+ }
794
+ function resolveWnOp(week, wnOp) {
795
+ if (wnOp == null) return week;
796
+ return week - ((week - wnOp) % 256 + 256) % 256;
797
+ }
798
+ function cnavRecord(eph) {
799
+ return [
800
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
801
+ orbitLine4([eph.aDot, eph.crs, eph.deltaN0, eph.m0]),
802
+ orbitLine4([eph.cuc, eph.e, eph.cus, Math.sqrt(eph.a)]),
803
+ orbitLine4([eph.top, eph.cic, eph.omega0, eph.cis]),
804
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
805
+ orbitLine4([eph.i0Dot, eph.deltaN0Dot, eph.uraNed0, eph.uraNed1]),
806
+ orbitLine4([eph.uraEd, eph.health, eph.tgd, eph.uraNed2]),
807
+ orbitLine4([eph.iscL1ca, eph.iscL2c, eph.iscL5i5, eph.iscL5q5]),
808
+ orbitLine4([eph.tow, resolveWnOp(eph.week, eph.wnOp)])
809
+ ].join("\n");
810
+ }
811
+ var KLOBUCHAR_SOURCES = [
812
+ ["GPSA", "GPSB", "G", "LNAV"],
813
+ ["QZSA", "QZSB", "J", "LNAV"],
814
+ ["BDSA", "BDSB", "C", "D1D2"],
815
+ ["IRNA", "IRNB", "I", "LNAV"]
816
+ ];
817
+ function ionRecords(header, epoch) {
818
+ const records = [];
819
+ const iono = header.ionoCorrections;
820
+ for (const [alphaKey, betaKey, sys, msg] of KLOBUCHAR_SOURCES) {
821
+ const alpha = iono[alphaKey];
822
+ if (!alpha) continue;
823
+ const beta = iono[betaKey] ?? [0, 0, 0, 0];
824
+ records.push(
825
+ [
826
+ `> ION ${sys} ${msg}`,
827
+ ` ${fmtEpoch4(epoch)}${[alpha[0] ?? 0, alpha[1] ?? 0, alpha[2] ?? 0].map(fmtD).join("")}`,
828
+ orbitLine4([alpha[3] ?? 0, beta[0] ?? 0, beta[1] ?? 0, beta[2] ?? 0]),
829
+ orbitLine4([beta[3] ?? 0, null])
830
+ ].join("\n")
831
+ );
832
+ }
833
+ const gal = iono["GAL"];
834
+ if (gal) {
835
+ records.push(
836
+ [
837
+ "> ION E IFNV",
838
+ ` ${fmtEpoch4(epoch)}${[gal[0] ?? 0, gal[1] ?? 0, gal[2] ?? 0].map(fmtD).join("")}`,
839
+ orbitLine4([gal[3] ?? 0])
840
+ ].join("\n")
841
+ );
842
+ }
843
+ return records;
844
+ }
845
+ function writeHeader4(header) {
846
+ const lines = [];
847
+ lines.push(
848
+ hdrLine(
849
+ padL(" 4.01", 9) + " " + padL("NAVIGATION DATA", 20) + padL("M", 20),
850
+ "RINEX VERSION / TYPE"
851
+ )
852
+ );
853
+ const now = /* @__PURE__ */ new Date();
854
+ 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";
855
+ lines.push(
856
+ hdrLine(
857
+ padL("GNSSCalc", 20) + padL("", 20) + padL(dateStr, 20),
858
+ "PGM / RUN BY / DATE"
859
+ )
860
+ );
861
+ if (header.leapSeconds != null) {
862
+ lines.push(hdrLine(padR(String(header.leapSeconds), 6), "LEAP SECONDS"));
863
+ }
864
+ lines.push(hdrLine("", "END OF HEADER"));
865
+ return lines.join("\n") + "\n";
866
+ }
867
+ function writeRinexNav4(nav) {
868
+ const records = [];
869
+ for (const eph of nav.ephemerides) {
870
+ const label = msgLabel(eph);
871
+ records.push({
872
+ prn: eph.prn,
873
+ time: eph.tocDate.getTime(),
874
+ label,
875
+ body: `> EPH ${padL(eph.prn, 3)} ${label}
876
+ ` + (isKepler2(eph) ? keplerRecord(eph) : stateVectorRecord(eph))
877
+ });
878
+ }
879
+ for (const eph of nav.cnav ?? []) {
880
+ records.push({
881
+ prn: eph.prn,
882
+ time: eph.tocDate.getTime(),
883
+ label: "CNAV",
884
+ body: `> EPH ${padL(eph.prn, 3)} CNAV
885
+ ` + cnavRecord(eph)
886
+ });
887
+ }
888
+ records.sort(
889
+ (a, b) => a.prn.localeCompare(b.prn) || a.time - b.time || a.label.localeCompare(b.label)
890
+ );
891
+ const bodies = records.map((r) => r.body);
892
+ if (records.length > 0) {
893
+ const earliest = new Date(Math.min(...records.map((r) => r.time)));
894
+ bodies.push(...ionRecords(nav.header, earliest));
895
+ }
896
+ return writeHeader4(nav.header) + (bodies.length > 0 ? bodies.join("\n") + "\n" : "");
897
+ }
898
+
613
899
  // src/rinex/ionex.ts
614
900
  function gridRange(l1, l2, dl) {
615
901
  const out = [];
@@ -1193,7 +1479,9 @@ export {
1193
1479
  EMPTY_WARNINGS,
1194
1480
  WarningAccumulator,
1195
1481
  parseNavFile,
1482
+ fmtD,
1196
1483
  writeRinexNav,
1484
+ writeRinexNav4,
1197
1485
  parseIonex,
1198
1486
  parseSp3,
1199
1487
  sp3Position,