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/rinex.cjs CHANGED
@@ -26,6 +26,7 @@ __export(rinex_exports, {
26
26
  createGzipLineSink: () => createGzipLineSink,
27
27
  crxDecompress: () => crxDecompress,
28
28
  crxRepair: () => crxRepair,
29
+ fmtD: () => fmtD,
29
30
  fmtF: () => fmtF,
30
31
  hdrLine: () => hdrLine,
31
32
  padL: () => padL,
@@ -43,6 +44,7 @@ __export(rinex_exports, {
43
44
  writeRinex2ObsBlob: () => writeRinex2ObsBlob,
44
45
  writeRinex4ObsBlob: () => writeRinex4ObsBlob,
45
46
  writeRinexNav: () => writeRinexNav,
47
+ writeRinexNav4: () => writeRinexNav4,
46
48
  writeRinexObsBlob: () => writeRinexObsBlob
47
49
  });
48
50
  module.exports = __toCommonJS(rinex_exports);
@@ -1038,6 +1040,17 @@ var WarningAccumulator = class {
1038
1040
  }
1039
1041
  };
1040
1042
 
1043
+ // src/navbits/index.ts
1044
+ var GPS_PI = 3.1415926535898;
1045
+ var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
1046
+ var SEC_PER_WEEK = 7 * 86400;
1047
+
1048
+ // src/navbits/cnav.ts
1049
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
1050
+ var SEC_PER_WEEK2 = 7 * 86400;
1051
+ var CNAV_A_REF = 26559710;
1052
+ var CNAV_OMEGA_DOT_REF = -26e-10;
1053
+
1041
1054
  // src/rinex/nav.ts
1042
1055
  var DATA_LINES = {
1043
1056
  G: 7,
@@ -1061,19 +1074,19 @@ var R4_MSG_LINES = {
1061
1074
  SBAS: 4,
1062
1075
  FDMA: 5,
1063
1076
  // RINEX 4 adds orbit-4 (status flags) vs 3 in RINEX 3
1064
- // EPH types we skip (different field layout)
1077
+ // EPH type parsed into RinexCnavEphemeris for G/J (skipped otherwise)
1065
1078
  CNAV: 9,
1079
+ // EPH types we skip (different field layout)
1066
1080
  CNV1: 10,
1067
1081
  CNV2: 10,
1068
1082
  CNV3: 9,
1069
1083
  L1NV: 8,
1070
1084
  L1OC: 9,
1071
- L3OC: 9,
1072
- // Non-EPH records
1073
- STO: 2,
1074
- EOP: 3,
1075
- ION: 3
1085
+ L3OC: 9
1076
1086
  };
1087
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
1088
+ var SEC_PER_WEEK3 = 7 * 86400;
1089
+ var QZSS_CNAV_A_REF = 42164200;
1077
1090
  var SUPPORTED_EPH_MSGS = /* @__PURE__ */ new Set([
1078
1091
  "LNAV",
1079
1092
  "INAV",
@@ -1137,6 +1150,20 @@ function parseDataLine(line, colOffset = 4) {
1137
1150
  }
1138
1151
  return values;
1139
1152
  }
1153
+ function parseDataLineSlots(line) {
1154
+ const values = [];
1155
+ for (let i = 0; i < 4; i++) {
1156
+ const start = 4 + i * 19;
1157
+ const s = start < line.length ? line.substring(start, start + 19).trim() : "";
1158
+ if (s.length === 0) {
1159
+ values.push(null);
1160
+ continue;
1161
+ }
1162
+ const v = parseFloat19(s);
1163
+ values.push(Number.isFinite(v) ? v : null);
1164
+ }
1165
+ return values;
1166
+ }
1140
1167
  function buildKeplerEphemeris(sys, prn, date, epochVals, data) {
1141
1168
  const d = [];
1142
1169
  for (const line of data) {
@@ -1199,6 +1226,68 @@ function buildStateVectorEphemeris(sys, prn, date, epochVals, data) {
1199
1226
  zAcc: d[10] ?? 0
1200
1227
  };
1201
1228
  }
1229
+ function buildCnavEphemeris(sys, prn, date, epochVals, d) {
1230
+ const num = (r, c) => d[r]?.[c] ?? 0;
1231
+ const opt = (r, c) => d[r]?.[c] ?? null;
1232
+ const t = (date.getTime() - GPS_EPOCH_MS3) / 1e3;
1233
+ const week = Math.floor(t / SEC_PER_WEEK3);
1234
+ const toc = t - week * SEC_PER_WEEK3;
1235
+ const sqrtA = num(1, 3);
1236
+ const a = sqrtA * sqrtA;
1237
+ const aRef = sys === "J" ? QZSS_CNAV_A_REF : CNAV_A_REF;
1238
+ const omegaDot = num(3, 3);
1239
+ const wnOp = d[7]?.[1];
1240
+ return {
1241
+ system: sys,
1242
+ prn,
1243
+ week,
1244
+ health: num(5, 1),
1245
+ uraEd: num(5, 0),
1246
+ uraNed0: num(4, 2),
1247
+ uraNed1: num(4, 3),
1248
+ uraNed2: num(5, 3),
1249
+ top: num(2, 0),
1250
+ toe: toc,
1251
+ toeDate: date,
1252
+ a,
1253
+ deltaA: a - aRef,
1254
+ aDot: num(0, 0),
1255
+ deltaN0: num(0, 2),
1256
+ deltaN0Dot: num(4, 1),
1257
+ m0: num(0, 3),
1258
+ e: num(1, 1),
1259
+ omega: num(3, 2),
1260
+ omega0: num(2, 2),
1261
+ i0: num(3, 0),
1262
+ omegaDot,
1263
+ deltaOmegaDot: omegaDot - CNAV_OMEGA_DOT_REF * GPS_PI,
1264
+ i0Dot: num(4, 0),
1265
+ cis: num(2, 3),
1266
+ cic: num(2, 1),
1267
+ crs: num(0, 1),
1268
+ crc: num(3, 1),
1269
+ cus: num(1, 2),
1270
+ cuc: num(1, 0),
1271
+ toc,
1272
+ tocDate: date,
1273
+ af0: epochVals[0] ?? 0,
1274
+ af1: epochVals[1] ?? 0,
1275
+ af2: epochVals[2] ?? 0,
1276
+ clockMsgType: 0,
1277
+ // not recorded in RINEX
1278
+ tgd: opt(5, 2),
1279
+ iscL1ca: opt(6, 0),
1280
+ iscL2c: opt(6, 1),
1281
+ iscL5i5: opt(6, 2),
1282
+ iscL5q5: opt(6, 3),
1283
+ ...wnOp != null && { wnOp },
1284
+ integrityFlag: false,
1285
+ // not recorded in RINEX
1286
+ l2cPhasing: false,
1287
+ // not recorded in RINEX
1288
+ tow: num(7, 0)
1289
+ };
1290
+ }
1202
1291
  function parseNavFile(text) {
1203
1292
  const lines = text.split("\n");
1204
1293
  const header = {
@@ -1208,6 +1297,7 @@ function parseNavFile(text) {
1208
1297
  ionoCorrections: {}
1209
1298
  };
1210
1299
  const ephemerides = [];
1300
+ const cnav = [];
1211
1301
  let inHeader = true;
1212
1302
  let i = 0;
1213
1303
  while (i < lines.length) {
@@ -1268,8 +1358,15 @@ function parseNavFile(text) {
1268
1358
  if (isV4 && line.charAt(0) === ">") {
1269
1359
  const parts = line.substring(2).trim().split(/\s+/);
1270
1360
  const recType = parts[0] ?? "";
1361
+ const svSys = (parts[1] ?? "").charAt(0);
1271
1362
  const msgType = parts[2] ?? recType;
1272
- if (recType !== "EPH" || !SUPPORTED_EPH_MSGS.has(msgType)) {
1363
+ if (recType !== "EPH") {
1364
+ const totalLines = recType === "STO" ? 2 : recType === "EOP" ? 3 : recType === "ION" ? svSys === "E" ? 2 : 3 : 2;
1365
+ i += totalLines + 1;
1366
+ continue;
1367
+ }
1368
+ const isCnav = msgType === "CNAV" && (svSys === "G" || svSys === "J");
1369
+ if (!SUPPORTED_EPH_MSGS.has(msgType) && !isCnav) {
1273
1370
  const totalLines = R4_MSG_LINES[msgType] ?? 2;
1274
1371
  i += totalLines + 1;
1275
1372
  continue;
@@ -1281,6 +1378,21 @@ function parseNavFile(text) {
1281
1378
  const sys2 = epochLine.charAt(0);
1282
1379
  const parsed = parseNavEpochV3(epochLine);
1283
1380
  const prn2 = `${sys2}${parsed.prn.substring(1)}`;
1381
+ if (isCnav) {
1382
+ const slotLines = [];
1383
+ for (let j = 0; j < numDataLines2; j++) {
1384
+ i++;
1385
+ if (i >= lines.length) break;
1386
+ slotLines.push(parseDataLineSlots(lines[i]));
1387
+ }
1388
+ if (slotLines.length === numDataLines2 && (sys2 === "G" || sys2 === "J")) {
1389
+ cnav.push(
1390
+ buildCnavEphemeris(sys2, prn2, parsed.date, parsed.values, slotLines)
1391
+ );
1392
+ }
1393
+ i++;
1394
+ continue;
1395
+ }
1284
1396
  const dataLines2 = [];
1285
1397
  for (let j = 0; j < numDataLines2; j++) {
1286
1398
  i++;
@@ -1354,7 +1466,7 @@ function parseNavFile(text) {
1354
1466
  }
1355
1467
  i++;
1356
1468
  }
1357
- return { header, ephemerides };
1469
+ return cnav.length > 0 ? { header, ephemerides, cnav } : { header, ephemerides };
1358
1470
  }
1359
1471
 
1360
1472
  // src/rinex/nav-writer.ts
@@ -1362,9 +1474,13 @@ function fmtD(val) {
1362
1474
  if (val === 0) return " 0.000000000000E+00";
1363
1475
  const sign = val < 0 ? "-" : " ";
1364
1476
  const abs = Math.abs(val);
1365
- const exp = Math.floor(Math.log10(abs));
1477
+ let exp = Math.floor(Math.log10(abs));
1366
1478
  const mantissa = abs / 10 ** exp;
1367
- const mStr = mantissa.toFixed(12);
1479
+ let mStr = mantissa.toFixed(12);
1480
+ if (mStr.length > 14) {
1481
+ exp += 1;
1482
+ mStr = (mantissa / 10).toFixed(12);
1483
+ }
1368
1484
  const expSign = exp >= 0 ? "+" : "-";
1369
1485
  const expStr = String(Math.abs(exp)).padStart(2, "0");
1370
1486
  return `${sign}${mStr}E${expSign}${expStr}`;
@@ -1439,7 +1555,7 @@ function writeKeplerRecord(eph) {
1439
1555
  function writeGlonassRecord(eph) {
1440
1556
  const lines = [];
1441
1557
  lines.push(
1442
- `${padL(eph.prn, 3)} ${fmtEpoch(eph.tocDate)}${fmtD(-eph.tauN)}${fmtD(eph.gammaN)}${fmtD(eph.messageFrameTime)}`
1558
+ `${padL(eph.prn, 3)} ${fmtEpoch(eph.tocDate)}${fmtD(eph.tauN)}${fmtD(eph.gammaN)}${fmtD(eph.messageFrameTime)}`
1443
1559
  );
1444
1560
  lines.push(orbitLine([eph.x, eph.xDot, eph.xAcc, eph.health]));
1445
1561
  lines.push(orbitLine([eph.y, eph.yDot, eph.yAcc, eph.freqNum]));
@@ -1459,6 +1575,181 @@ function writeRinexNav(nav) {
1459
1575
  return header + records.join("\n") + "\n";
1460
1576
  }
1461
1577
 
1578
+ // src/rinex/nav-writer-v4.ts
1579
+ var BLANK19 = " ".repeat(19);
1580
+ function fmtEpoch4(d) {
1581
+ const p2 = (v) => String(v).padStart(2, "0");
1582
+ return `${d.getUTCFullYear()} ${p2(d.getUTCMonth() + 1)} ${p2(d.getUTCDate())} ${p2(d.getUTCHours())} ${p2(d.getUTCMinutes())} ${p2(d.getUTCSeconds())}`;
1583
+ }
1584
+ function orbitLine4(values) {
1585
+ const line = " " + values.map((v) => v == null ? BLANK19 : fmtD(v)).join("");
1586
+ return line.replace(/ +$/, "");
1587
+ }
1588
+ function epochLine4(prn, date, vals) {
1589
+ return `${padL(prn, 3)} ${fmtEpoch4(date)}${vals.map(fmtD).join("")}`;
1590
+ }
1591
+ var BDS_GEO = /* @__PURE__ */ new Set([
1592
+ "C01",
1593
+ "C02",
1594
+ "C03",
1595
+ "C04",
1596
+ "C05",
1597
+ "C59",
1598
+ "C60",
1599
+ "C61",
1600
+ "C62",
1601
+ "C63"
1602
+ ]);
1603
+ function msgLabel(eph) {
1604
+ switch (eph.system) {
1605
+ case "E":
1606
+ return "INAV";
1607
+ case "C":
1608
+ return BDS_GEO.has(eph.prn) ? "D2" : "D1";
1609
+ case "R":
1610
+ return "FDMA";
1611
+ case "S":
1612
+ return "SBAS";
1613
+ default:
1614
+ return "LNAV";
1615
+ }
1616
+ }
1617
+ function isKepler2(eph) {
1618
+ return "af0" in eph;
1619
+ }
1620
+ function keplerRecord(eph) {
1621
+ return [
1622
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
1623
+ orbitLine4([eph.iode, eph.crs, eph.deltaN, eph.m0]),
1624
+ orbitLine4([eph.cuc, eph.e, eph.cus, eph.sqrtA]),
1625
+ orbitLine4([eph.toe, eph.cic, eph.omega0, eph.cis]),
1626
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
1627
+ orbitLine4([eph.idot, 0, eph.week, 0]),
1628
+ orbitLine4([0, eph.svHealth, eph.tgd, 0]),
1629
+ orbitLine4([eph.toe, 0, 0, 0])
1630
+ ].join("\n");
1631
+ }
1632
+ function stateVectorRecord(eph) {
1633
+ const lines = [
1634
+ epochLine4(eph.prn, eph.tocDate, [
1635
+ eph.tauN,
1636
+ eph.gammaN,
1637
+ eph.messageFrameTime
1638
+ ]),
1639
+ orbitLine4([eph.x, eph.xDot, eph.xAcc, eph.health]),
1640
+ orbitLine4([eph.y, eph.yDot, eph.yAcc, eph.freqNum]),
1641
+ orbitLine4([eph.z, eph.zDot, eph.zAcc, 0])
1642
+ ];
1643
+ if (eph.system === "R") {
1644
+ lines.push(orbitLine4([null, 999999999999e-3, 15, null]));
1645
+ }
1646
+ return lines.join("\n");
1647
+ }
1648
+ function resolveWnOp(week, wnOp) {
1649
+ if (wnOp == null) return week;
1650
+ return week - ((week - wnOp) % 256 + 256) % 256;
1651
+ }
1652
+ function cnavRecord(eph) {
1653
+ return [
1654
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
1655
+ orbitLine4([eph.aDot, eph.crs, eph.deltaN0, eph.m0]),
1656
+ orbitLine4([eph.cuc, eph.e, eph.cus, Math.sqrt(eph.a)]),
1657
+ orbitLine4([eph.top, eph.cic, eph.omega0, eph.cis]),
1658
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
1659
+ orbitLine4([eph.i0Dot, eph.deltaN0Dot, eph.uraNed0, eph.uraNed1]),
1660
+ orbitLine4([eph.uraEd, eph.health, eph.tgd, eph.uraNed2]),
1661
+ orbitLine4([eph.iscL1ca, eph.iscL2c, eph.iscL5i5, eph.iscL5q5]),
1662
+ orbitLine4([eph.tow, resolveWnOp(eph.week, eph.wnOp)])
1663
+ ].join("\n");
1664
+ }
1665
+ var KLOBUCHAR_SOURCES = [
1666
+ ["GPSA", "GPSB", "G", "LNAV"],
1667
+ ["QZSA", "QZSB", "J", "LNAV"],
1668
+ ["BDSA", "BDSB", "C", "D1D2"],
1669
+ ["IRNA", "IRNB", "I", "LNAV"]
1670
+ ];
1671
+ function ionRecords(header, epoch) {
1672
+ const records = [];
1673
+ const iono = header.ionoCorrections;
1674
+ for (const [alphaKey, betaKey, sys, msg] of KLOBUCHAR_SOURCES) {
1675
+ const alpha = iono[alphaKey];
1676
+ if (!alpha) continue;
1677
+ const beta = iono[betaKey] ?? [0, 0, 0, 0];
1678
+ records.push(
1679
+ [
1680
+ `> ION ${sys} ${msg}`,
1681
+ ` ${fmtEpoch4(epoch)}${[alpha[0] ?? 0, alpha[1] ?? 0, alpha[2] ?? 0].map(fmtD).join("")}`,
1682
+ orbitLine4([alpha[3] ?? 0, beta[0] ?? 0, beta[1] ?? 0, beta[2] ?? 0]),
1683
+ orbitLine4([beta[3] ?? 0, null])
1684
+ ].join("\n")
1685
+ );
1686
+ }
1687
+ const gal = iono["GAL"];
1688
+ if (gal) {
1689
+ records.push(
1690
+ [
1691
+ "> ION E IFNV",
1692
+ ` ${fmtEpoch4(epoch)}${[gal[0] ?? 0, gal[1] ?? 0, gal[2] ?? 0].map(fmtD).join("")}`,
1693
+ orbitLine4([gal[3] ?? 0])
1694
+ ].join("\n")
1695
+ );
1696
+ }
1697
+ return records;
1698
+ }
1699
+ function writeHeader4(header) {
1700
+ const lines = [];
1701
+ lines.push(
1702
+ hdrLine(
1703
+ padL(" 4.01", 9) + " " + padL("NAVIGATION DATA", 20) + padL("M", 20),
1704
+ "RINEX VERSION / TYPE"
1705
+ )
1706
+ );
1707
+ const now = /* @__PURE__ */ new Date();
1708
+ 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";
1709
+ lines.push(
1710
+ hdrLine(
1711
+ padL("GNSSCalc", 20) + padL("", 20) + padL(dateStr, 20),
1712
+ "PGM / RUN BY / DATE"
1713
+ )
1714
+ );
1715
+ if (header.leapSeconds != null) {
1716
+ lines.push(hdrLine(padR(String(header.leapSeconds), 6), "LEAP SECONDS"));
1717
+ }
1718
+ lines.push(hdrLine("", "END OF HEADER"));
1719
+ return lines.join("\n") + "\n";
1720
+ }
1721
+ function writeRinexNav4(nav) {
1722
+ const records = [];
1723
+ for (const eph of nav.ephemerides) {
1724
+ const label = msgLabel(eph);
1725
+ records.push({
1726
+ prn: eph.prn,
1727
+ time: eph.tocDate.getTime(),
1728
+ label,
1729
+ body: `> EPH ${padL(eph.prn, 3)} ${label}
1730
+ ` + (isKepler2(eph) ? keplerRecord(eph) : stateVectorRecord(eph))
1731
+ });
1732
+ }
1733
+ for (const eph of nav.cnav ?? []) {
1734
+ records.push({
1735
+ prn: eph.prn,
1736
+ time: eph.tocDate.getTime(),
1737
+ label: "CNAV",
1738
+ body: `> EPH ${padL(eph.prn, 3)} CNAV
1739
+ ` + cnavRecord(eph)
1740
+ });
1741
+ }
1742
+ records.sort(
1743
+ (a, b) => a.prn.localeCompare(b.prn) || a.time - b.time || a.label.localeCompare(b.label)
1744
+ );
1745
+ const bodies = records.map((r) => r.body);
1746
+ if (records.length > 0) {
1747
+ const earliest = new Date(Math.min(...records.map((r) => r.time)));
1748
+ bodies.push(...ionRecords(nav.header, earliest));
1749
+ }
1750
+ return writeHeader4(nav.header) + (bodies.length > 0 ? bodies.join("\n") + "\n" : "");
1751
+ }
1752
+
1462
1753
  // src/rinex/ionex.ts
1463
1754
  function gridRange(l1, l2, dl) {
1464
1755
  const out = [];
@@ -2041,6 +2332,7 @@ function writeRinex4ObsBlob(header, epochs, obsTypes) {
2041
2332
  createGzipLineSink,
2042
2333
  crxDecompress,
2043
2334
  crxRepair,
2335
+ fmtD,
2044
2336
  fmtF,
2045
2337
  hdrLine,
2046
2338
  padL,
@@ -2058,5 +2350,6 @@ function writeRinex4ObsBlob(header, epochs, obsTypes) {
2058
2350
  writeRinex2ObsBlob,
2059
2351
  writeRinex4ObsBlob,
2060
2352
  writeRinexNav,
2353
+ writeRinexNav4,
2061
2354
  writeRinexObsBlob
2062
2355
  });
package/dist/rinex.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { R as RinexHeader } from './parser-JPjjFgeP.cjs';
2
2
  export { E as EpochSummary, P as ParseOptions, a as RinexResult, b as RinexStats, S as SYSTEM_ORDER, c as SatObsCallback, p as parseRinexStream, s as systemCmp, d as systemName } from './parser-JPjjFgeP.cjs';
3
- import { a as NavResult } from './nav-BAI1a9vK.cjs';
4
- export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, p as parseNavFile } from './nav-BAI1a9vK.cjs';
3
+ import { a as NavResult, N as NavHeader, E as Ephemeris, R as RinexCnavEphemeris } from './nav-DImXUdbp.cjs';
4
+ export { G as GlonassEphemeris, K as KeplerEphemeris, p as parseNavFile } from './nav-DImXUdbp.cjs';
5
5
 
6
6
  /**
7
7
  * Compact RINEX (Hatanaka / CRX) decompression utilities.
@@ -106,6 +106,16 @@ declare class WarningAccumulator {
106
106
  * Produces a standards-compliant RINEX 3.04 nav file from parsed NavResult data.
107
107
  */
108
108
 
109
+ /**
110
+ * Format a number in RINEX "D19.12" scientific notation.
111
+ *
112
+ * 19 characters wide, 12 decimal digits, explicit exponent sign.
113
+ * Examples:
114
+ * 0.0 → " 0.000000000000E+00"
115
+ * -1.5e-8 → "-1.500000000000E-08"
116
+ * 5153.71875→ " 5.153718750000E+03"
117
+ */
118
+ declare function fmtD(val: number): string;
109
119
  /**
110
120
  * Write a RINEX 3.04 mixed navigation file from parsed navigation data.
111
121
  *
@@ -113,6 +123,38 @@ declare class WarningAccumulator {
113
123
  */
114
124
  declare function writeRinexNav(nav: NavResult): string;
115
125
 
126
+ /**
127
+ * RINEX 4.01 mixed navigation file writer.
128
+ *
129
+ * Produces `> EPH` data records for the classic ephemeris types
130
+ * (GPS/QZSS/NavIC LNAV, Galileo INAV, BeiDou D1/D2, GLONASS FDMA,
131
+ * SBAS), `> EPH ... CNAV` records for GPS/QZSS CNAV ephemerides, and
132
+ * `> ION` records for the header's Klobuchar/NeQuick-G coefficients,
133
+ * per the RINEX 4.01 record tables (A9-A18, A21, A26, A32, A33).
134
+ */
135
+
136
+ /** Input for {@link writeRinexNav4}: a NavResult, optionally with CNAV. */
137
+ interface Nav4Input {
138
+ header: NavHeader;
139
+ ephemerides: Ephemeris[];
140
+ cnav?: RinexCnavEphemeris[];
141
+ }
142
+ /**
143
+ * Write a RINEX 4.01 mixed navigation file.
144
+ *
145
+ * Emits `> EPH` records for every classic ephemeris (message labels
146
+ * per constellation: LNAV, INAV, D1/D2, FDMA, SBAS) and every CNAV
147
+ * ephemeris (`CNAV`), sorted by PRN, epoch, then label; followed by
148
+ * `> ION` records when the header carries Klobuchar (GPSA/GPSB,
149
+ * QZSA/QZSB, BDSA/BDSB, IRNA/IRNB) or NeQuick-G (GAL) coefficients.
150
+ * ION records need a transmit-time epoch, which RINEX 3-style headers
151
+ * do not store — the earliest record epoch is used, so a file with no
152
+ * ephemerides gets no ION records. There is no source for system
153
+ * time offsets in NavHeader, so no `> STO` records are produced; leap
154
+ * seconds go in the LEAP SECONDS header line.
155
+ */
156
+ declare function writeRinexNav4(nav: Nav4Input): string;
157
+
116
158
  /**
117
159
  * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
118
160
  * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
@@ -263,4 +305,4 @@ declare function createGzipLineSink(): {
263
305
  */
264
306
  declare function stationHeaderLines(header: RinexHeader): string[];
265
307
 
266
- export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, type IonexGrid, NavResult, RinexHeader, type RinexWarning, type RinexWarnings, type Sp3File, type Sp3Sample, WarningAccumulator, type WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexObsBlob };
308
+ export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, Ephemeris, type IonexGrid, type Nav4Input, NavHeader, NavResult, RinexCnavEphemeris, RinexHeader, type RinexWarning, type RinexWarnings, type Sp3File, type Sp3Sample, WarningAccumulator, type WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob };
package/dist/rinex.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { R as RinexHeader } from './parser-JPjjFgeP.js';
2
2
  export { E as EpochSummary, P as ParseOptions, a as RinexResult, b as RinexStats, S as SYSTEM_ORDER, c as SatObsCallback, p as parseRinexStream, s as systemCmp, d as systemName } from './parser-JPjjFgeP.js';
3
- import { a as NavResult } from './nav-BAI1a9vK.js';
4
- export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, p as parseNavFile } from './nav-BAI1a9vK.js';
3
+ import { a as NavResult, N as NavHeader, E as Ephemeris, R as RinexCnavEphemeris } from './nav-DImXUdbp.js';
4
+ export { G as GlonassEphemeris, K as KeplerEphemeris, p as parseNavFile } from './nav-DImXUdbp.js';
5
5
 
6
6
  /**
7
7
  * Compact RINEX (Hatanaka / CRX) decompression utilities.
@@ -106,6 +106,16 @@ declare class WarningAccumulator {
106
106
  * Produces a standards-compliant RINEX 3.04 nav file from parsed NavResult data.
107
107
  */
108
108
 
109
+ /**
110
+ * Format a number in RINEX "D19.12" scientific notation.
111
+ *
112
+ * 19 characters wide, 12 decimal digits, explicit exponent sign.
113
+ * Examples:
114
+ * 0.0 → " 0.000000000000E+00"
115
+ * -1.5e-8 → "-1.500000000000E-08"
116
+ * 5153.71875→ " 5.153718750000E+03"
117
+ */
118
+ declare function fmtD(val: number): string;
109
119
  /**
110
120
  * Write a RINEX 3.04 mixed navigation file from parsed navigation data.
111
121
  *
@@ -113,6 +123,38 @@ declare class WarningAccumulator {
113
123
  */
114
124
  declare function writeRinexNav(nav: NavResult): string;
115
125
 
126
+ /**
127
+ * RINEX 4.01 mixed navigation file writer.
128
+ *
129
+ * Produces `> EPH` data records for the classic ephemeris types
130
+ * (GPS/QZSS/NavIC LNAV, Galileo INAV, BeiDou D1/D2, GLONASS FDMA,
131
+ * SBAS), `> EPH ... CNAV` records for GPS/QZSS CNAV ephemerides, and
132
+ * `> ION` records for the header's Klobuchar/NeQuick-G coefficients,
133
+ * per the RINEX 4.01 record tables (A9-A18, A21, A26, A32, A33).
134
+ */
135
+
136
+ /** Input for {@link writeRinexNav4}: a NavResult, optionally with CNAV. */
137
+ interface Nav4Input {
138
+ header: NavHeader;
139
+ ephemerides: Ephemeris[];
140
+ cnav?: RinexCnavEphemeris[];
141
+ }
142
+ /**
143
+ * Write a RINEX 4.01 mixed navigation file.
144
+ *
145
+ * Emits `> EPH` records for every classic ephemeris (message labels
146
+ * per constellation: LNAV, INAV, D1/D2, FDMA, SBAS) and every CNAV
147
+ * ephemeris (`CNAV`), sorted by PRN, epoch, then label; followed by
148
+ * `> ION` records when the header carries Klobuchar (GPSA/GPSB,
149
+ * QZSA/QZSB, BDSA/BDSB, IRNA/IRNB) or NeQuick-G (GAL) coefficients.
150
+ * ION records need a transmit-time epoch, which RINEX 3-style headers
151
+ * do not store — the earliest record epoch is used, so a file with no
152
+ * ephemerides gets no ION records. There is no source for system
153
+ * time offsets in NavHeader, so no `> STO` records are produced; leap
154
+ * seconds go in the LEAP SECONDS header line.
155
+ */
156
+ declare function writeRinexNav4(nav: Nav4Input): string;
157
+
116
158
  /**
117
159
  * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
118
160
  * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
@@ -263,4 +305,4 @@ declare function createGzipLineSink(): {
263
305
  */
264
306
  declare function stationHeaderLines(header: RinexHeader): string[];
265
307
 
266
- export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, type IonexGrid, NavResult, RinexHeader, type RinexWarning, type RinexWarnings, type Sp3File, type Sp3Sample, WarningAccumulator, type WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexObsBlob };
308
+ export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, Ephemeris, type IonexGrid, type Nav4Input, NavHeader, NavResult, RinexCnavEphemeris, RinexHeader, type RinexWarning, type RinexWarnings, type Sp3File, type Sp3Sample, WarningAccumulator, type WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob };
package/dist/rinex.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  EMPTY_WARNINGS,
3
3
  WarningAccumulator,
4
4
  createGzipLineSink,
5
+ fmtD,
5
6
  fmtF,
6
7
  hdrLine,
7
8
  padL,
@@ -15,8 +16,11 @@ import {
15
16
  writeRinex2ObsBlob,
16
17
  writeRinex4ObsBlob,
17
18
  writeRinexNav,
19
+ writeRinexNav4,
18
20
  writeRinexObsBlob
19
- } from "./chunk-HXF7XVW7.js";
21
+ } from "./chunk-K73SERD5.js";
22
+ import "./chunk-6NCMACDO.js";
23
+ import "./chunk-IQ5OIMQD.js";
20
24
  import {
21
25
  SYSTEM_ORDER,
22
26
  crxDecompress,
@@ -34,6 +38,7 @@ export {
34
38
  createGzipLineSink,
35
39
  crxDecompress,
36
40
  crxRepair,
41
+ fmtD,
37
42
  fmtF,
38
43
  hdrLine,
39
44
  padL,
@@ -51,5 +56,6 @@ export {
51
56
  writeRinex2ObsBlob,
52
57
  writeRinex4ObsBlob,
53
58
  writeRinexNav,
59
+ writeRinexNav4,
54
60
  writeRinexObsBlob
55
61
  };
package/dist/sbf.d.cts CHANGED
@@ -1,5 +1,4 @@
1
- import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
2
- import { C as CnavEphemeris } from './cnav-CRns0ECn.cjs';
1
+ import { E as Ephemeris, C as CnavEphemeris } from './nav-DImXUdbp.cjs';
3
2
 
4
3
  /**
5
4
  * Septentrio SBF framing shared by the measurement and navigation
package/dist/sbf.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { E as Ephemeris } from './nav-BAI1a9vK.js';
2
- import { C as CnavEphemeris } from './cnav-CRns0ECn.js';
1
+ import { E as Ephemeris, C as CnavEphemeris } from './nav-DImXUdbp.js';
3
2
 
4
3
  /**
5
4
  * Septentrio SBF framing shared by the measurement and navigation
package/dist/sbf.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  parseSbfMeas,
7
7
  parseSbfNav,
8
8
  scanSbfFrames
9
- } from "./chunk-CG7SF45F.js";
10
- import "./chunk-Y5IWUPJQ.js";
9
+ } from "./chunk-5S5HMAKW.js";
10
+ import "./chunk-6NCMACDO.js";
11
11
  import "./chunk-IQ5OIMQD.js";
12
12
  import "./chunk-HVXYFUCB.js";
13
13
  import "./chunk-LEEU5OIO.js";
package/dist/ubx.d.cts CHANGED
@@ -1,5 +1,4 @@
1
- import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
2
- import { C as CnavEphemeris } from './cnav-CRns0ECn.cjs';
1
+ import { E as Ephemeris, C as CnavEphemeris } from './nav-DImXUdbp.cjs';
3
2
 
4
3
  /**
5
4
  * u-blox UBX framing shared by the measurement and navigation decoders:
package/dist/ubx.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { E as Ephemeris } from './nav-BAI1a9vK.js';
2
- import { C as CnavEphemeris } from './cnav-CRns0ECn.js';
1
+ import { E as Ephemeris, C as CnavEphemeris } from './nav-DImXUdbp.js';
3
2
 
4
3
  /**
5
4
  * u-blox UBX framing shared by the measurement and navigation decoders:
package/dist/ubx.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  parseUbxNav,
5
5
  parseUbxRawx,
6
6
  ubxFrames
7
- } from "./chunk-FEGLXNWA.js";
8
- import "./chunk-Y5IWUPJQ.js";
7
+ } from "./chunk-JW2ICDR7.js";
8
+ import "./chunk-6NCMACDO.js";
9
9
  import "./chunk-IQ5OIMQD.js";
10
10
  export {
11
11
  parseUbxCnav,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
5
5
  "type": "module",
6
6
  "sideEffects": false,