gnss-js 1.24.0 → 1.25.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.
@@ -43,6 +43,51 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
43
43
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
44
44
  }
45
45
 
46
+ // src/positioning/gim.ts
47
+ var R_E = 6371e3;
48
+ var H_ION = 45e4;
49
+ var F_L1 = 157542e4;
50
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
51
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
52
+ const m = grid.maps[mapIdx];
53
+ const { lats, lons } = grid;
54
+ const w = lons.length;
55
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
56
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
57
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
58
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
59
+ const di = Math.max(0, Math.min(1, fi - i0));
60
+ const dj = Math.max(0, Math.min(1, fj - j0));
61
+ return m[i0 * w + j0] * (1 - di) * (1 - dj) + m[i0 * w + j0 + 1] * (1 - di) * dj + m[(i0 + 1) * w + j0] * di * (1 - dj) + m[(i0 + 1) * w + j0 + 1] * di * dj;
62
+ }
63
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
64
+ const e = grid.epochs;
65
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
66
+ let i = 0;
67
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
68
+ const span = e[i + 1] - e[i];
69
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
70
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
71
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
72
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
73
+ return a * (1 - f) + b * f;
74
+ }
75
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
76
+ if (elRad <= 0) return null;
77
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
78
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
79
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
80
+ const latI = Math.asin(
81
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
82
+ );
83
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
84
+ const latDeg = latI * 180 / Math.PI;
85
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
86
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
87
+ if (vtec === null) return null;
88
+ return IONO_L1_M_PER_TECU * vtec * mapping;
89
+ }
90
+
46
91
  // src/positioning/lambda.ts
47
92
  var LOOPMAX = 1e4;
48
93
  var ROUND = (x) => Math.floor(x + 0.5);
@@ -1084,10 +1129,17 @@ var PRIMARY_FREQ_HZ = {
1084
1129
  C: 1561098e3,
1085
1130
  R: 1602e6
1086
1131
  };
1087
- var F_L1 = 157542e4;
1088
- function tropoDelay2(elevationRad) {
1089
- const sinEl = Math.sin(elevationRad);
1090
- return 2.47 / (sinEl + 0.0121);
1132
+ var F_L12 = 157542e4;
1133
+ function tropoDelay2(elevationRad, latRad, heightM) {
1134
+ if (elevationRad <= 0) return 0;
1135
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
1136
+ const humi = 0.7;
1137
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
1138
+ const temp = 15 - 65e-4 * h + 273.16;
1139
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
1140
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
1141
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
1142
+ return (zhd + zwd) / Math.sin(elevationRad);
1091
1143
  }
1092
1144
  function sagnac2(pos, travelTimeS) {
1093
1145
  const theta = OMEGA_E * travelTimeS;
@@ -1120,7 +1172,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1120
1172
  maxIterations = 15,
1121
1173
  convergenceM = 1e-4,
1122
1174
  tgd = true,
1123
- iono
1175
+ iono,
1176
+ gim
1124
1177
  } = opts;
1125
1178
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
1126
1179
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -1147,7 +1200,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1147
1200
  let rows = 0;
1148
1201
  for (const k of Object.keys(residuals2)) delete residuals2[k];
1149
1202
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
1150
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
1203
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
1151
1204
  for (const prn of prns) {
1152
1205
  const psr = pseudoranges.get(prn);
1153
1206
  const eph = ephemerides.get(prn);
@@ -1177,11 +1230,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1177
1230
  const sinEl = Math.max(Math.sin(elev), 0.1);
1178
1231
  weight = sinEl * sinEl;
1179
1232
  }
1180
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
1233
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
1181
1234
  let ionoM = 0;
1182
- if (iono && positionSane) {
1183
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
1184
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
1235
+ if ((gim || iono) && positionSane) {
1236
+ let l1M = null;
1237
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
1238
+ if (l1M === null && iono) {
1239
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
1240
+ }
1241
+ if (l1M !== null) {
1242
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
1243
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
1244
+ }
1185
1245
  }
1186
1246
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
1187
1247
  const v = psr - predicted;
@@ -1263,6 +1323,9 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1263
1323
 
1264
1324
  export {
1265
1325
  klobucharDelay,
1326
+ IONO_L1_M_PER_TECU,
1327
+ gimVerticalTec,
1328
+ gimSlantIonoDelayL1,
1266
1329
  lambdaSearch,
1267
1330
  lambdaReduction,
1268
1331
  toRtkEpoch,
package/dist/index.cjs CHANGED
@@ -67,6 +67,7 @@ __export(src_exports, {
67
67
  GLO_F2_BASE: () => GLO_F2_BASE,
68
68
  GLO_F2_STEP: () => GLO_F2_STEP,
69
69
  GLO_F3: () => GLO_F3,
70
+ IONO_L1_M_PER_TECU: () => IONO_L1_M_PER_TECU,
70
71
  IonoAccumulator: () => IonoAccumulator,
71
72
  MILLISECONDS_GPS_TAI: () => MILLISECONDS_GPS_TAI,
72
73
  MILLISECONDS_IN_DAY: () => MILLISECONDS_IN_DAY,
@@ -202,6 +203,8 @@ __export(src_exports, {
202
203
  getUtcDate: () => getUtcDate,
203
204
  getWeekNumber: () => getWeekNumber,
204
205
  getWeekOfYear: () => getWeekOfYear,
206
+ gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
207
+ gimVerticalTec: () => gimVerticalTec,
205
208
  gloFreq: () => gloFreq,
206
209
  glonassAlmanacEpochMs: () => glonassAlmanacEpochMs,
207
210
  glonassAlmanacPosition: () => glonassAlmanacPosition,
@@ -9682,6 +9685,51 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
9682
9685
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
9683
9686
  }
9684
9687
 
9688
+ // src/positioning/gim.ts
9689
+ var R_E = 6371e3;
9690
+ var H_ION = 45e4;
9691
+ var F_L1 = 157542e4;
9692
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
9693
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
9694
+ const m = grid.maps[mapIdx];
9695
+ const { lats, lons } = grid;
9696
+ const w = lons.length;
9697
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
9698
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
9699
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
9700
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
9701
+ const di = Math.max(0, Math.min(1, fi - i0));
9702
+ const dj = Math.max(0, Math.min(1, fj - j0));
9703
+ return m[i0 * w + j0] * (1 - di) * (1 - dj) + m[i0 * w + j0 + 1] * (1 - di) * dj + m[(i0 + 1) * w + j0] * di * (1 - dj) + m[(i0 + 1) * w + j0 + 1] * di * dj;
9704
+ }
9705
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
9706
+ const e = grid.epochs;
9707
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
9708
+ let i = 0;
9709
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
9710
+ const span = e[i + 1] - e[i];
9711
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
9712
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
9713
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
9714
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
9715
+ return a * (1 - f) + b * f;
9716
+ }
9717
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
9718
+ if (elRad <= 0) return null;
9719
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
9720
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
9721
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
9722
+ const latI = Math.asin(
9723
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
9724
+ );
9725
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
9726
+ const latDeg = latI * 180 / Math.PI;
9727
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
9728
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
9729
+ if (vtec === null) return null;
9730
+ return IONO_L1_M_PER_TECU * vtec * mapping;
9731
+ }
9732
+
9685
9733
  // src/positioning/lambda.ts
9686
9734
  var LOOPMAX = 1e4;
9687
9735
  var ROUND = (x) => Math.floor(x + 0.5);
@@ -10723,10 +10771,17 @@ var PRIMARY_FREQ_HZ = {
10723
10771
  C: 1561098e3,
10724
10772
  R: 1602e6
10725
10773
  };
10726
- var F_L1 = 157542e4;
10727
- function tropoDelay2(elevationRad) {
10728
- const sinEl = Math.sin(elevationRad);
10729
- return 2.47 / (sinEl + 0.0121);
10774
+ var F_L12 = 157542e4;
10775
+ function tropoDelay2(elevationRad, latRad, heightM) {
10776
+ if (elevationRad <= 0) return 0;
10777
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
10778
+ const humi = 0.7;
10779
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
10780
+ const temp = 15 - 65e-4 * h + 273.16;
10781
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
10782
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
10783
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
10784
+ return (zhd + zwd) / Math.sin(elevationRad);
10730
10785
  }
10731
10786
  function sagnac2(pos, travelTimeS) {
10732
10787
  const theta = OMEGA_E * travelTimeS;
@@ -10759,7 +10814,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
10759
10814
  maxIterations = 15,
10760
10815
  convergenceM = 1e-4,
10761
10816
  tgd = true,
10762
- iono
10817
+ iono,
10818
+ gim
10763
10819
  } = opts;
10764
10820
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
10765
10821
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -10786,7 +10842,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
10786
10842
  let rows = 0;
10787
10843
  for (const k of Object.keys(residuals2)) delete residuals2[k];
10788
10844
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
10789
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
10845
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
10790
10846
  for (const prn of prns) {
10791
10847
  const psr = pseudoranges.get(prn);
10792
10848
  const eph = ephemerides.get(prn);
@@ -10816,11 +10872,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
10816
10872
  const sinEl = Math.max(Math.sin(elev), 0.1);
10817
10873
  weight = sinEl * sinEl;
10818
10874
  }
10819
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
10875
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
10820
10876
  let ionoM = 0;
10821
- if (iono && positionSane) {
10822
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
10823
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
10877
+ if ((gim || iono) && positionSane) {
10878
+ let l1M = null;
10879
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
10880
+ if (l1M === null && iono) {
10881
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
10882
+ }
10883
+ if (l1M !== null) {
10884
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
10885
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
10886
+ }
10824
10887
  }
10825
10888
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
10826
10889
  const v = psr - predicted;
@@ -13207,6 +13270,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
13207
13270
  GLO_F2_BASE,
13208
13271
  GLO_F2_STEP,
13209
13272
  GLO_F3,
13273
+ IONO_L1_M_PER_TECU,
13210
13274
  IonoAccumulator,
13211
13275
  MILLISECONDS_GPS_TAI,
13212
13276
  MILLISECONDS_IN_DAY,
@@ -13342,6 +13406,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
13342
13406
  getUtcDate,
13343
13407
  getWeekNumber,
13344
13408
  getWeekOfYear,
13409
+ gimSlantIonoDelayL1,
13410
+ gimVerticalTec,
13345
13411
  gloFreq,
13346
13412
  glonassAlmanacEpochMs,
13347
13413
  glonassAlmanacPosition,
package/dist/index.d.cts CHANGED
@@ -4,9 +4,10 @@ export { A as ARC_GAP_FACTOR, B as BAND_LABELS, a as BDS_SATELLITES, b as BdsSat
4
4
  export { Scale, getBdsTime, getDateFromBdsTime, getDateFromDayOfWeek, getDateFromDayOfYear, getDateFromGalTime, getDateFromGloN, getDateFromGpsData, getDateFromGpsTime, getDateFromHourCode, getDateFromJulianDate, getDateFromMJD, getDateFromMJD2000, getDateFromRINEX, getDateFromTai, getDateFromTimeOfDay, getDateFromTt, getDateFromUnixTime, getDateFromUtc, getDayOfWeek, getDayOfYear, getGalTime, getGloN4, getGloNA, getGpsLeap, getGpsTime, getHourCode, getHoursFromTimeDifference, getJulianDate, getLeap, getMJD, getMJD2000, getMinutesFromTimeDifference, getNtpTime, getRINEX, getSecondsFromTimeDifference, getTaiDate, getTimeDifference, getTimeDifferenceFromDays, getTimeDifferenceFromHours, getTimeDifferenceFromMinutes, getTimeDifferenceFromObject, getTimeDifferenceFromSeconds, getTimeOfDay, getTimeOfWeek, getTotalDaysFromTimeDifference, getTtDate, getUnixTime, getUtcDate, getWeekNumber, getWeekOfYear } from './time.cjs';
5
5
  export { deg2dms, deg2rad, euclidean3D, geodeticToGeohash, geodeticToMaidenhead, geodeticToUtm, greatCircleMidpoint, horizonDistance, rad2deg, rhumbLine, vincenty } from './coordinates.cjs';
6
6
  export { c as clampUnit, e as ecefToGeodetic, g as geodeticToEcef, a as getAer, b as getEnuDifference } from './ecef-CF0uAysr.cjs';
7
- export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, IonexGrid, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.cjs';
7
+ export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.cjs';
8
8
  export { E as EpochSummary, P as ParseOptions, R as RinexHeader, 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';
9
9
  export { C as CnavEphemeris, E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, R as RinexCnavEphemeris, p as parseNavFile } from './nav-DImXUdbp.cjs';
10
+ export { I as IonexGrid, p as parseIonex } from './ionex-DaW5x9nq.cjs';
10
11
  export { B as BitReader, E as EphemerisInfo, R as Rtcm3DebugHandler, a as Rtcm3Decoder, b as Rtcm3Frame, d as decodeEphemeris, r as readString, c as reportDecodeError, s as setRtcm3DebugHandler } from './ephemeris-Ohl6NAfw.cjs';
11
12
  export { MessageTypeStats, MsmEpoch, MsmSatObs, MsmSignal, RTCM3_MESSAGE_NAMES, SatCn0, SignalCn0, StationMeta, StreamStats, createStationMeta, createStreamStats, decodeMsmFull, msmEpochToDate, resetGloFreqCache, rtcm3Constellation, setGloFreqNumber, updateStationMeta, updateStreamStats } from './rtcm3.cjs';
12
13
  export { UbxCnavResult, UbxFrame, UbxIonoUtcResult, UbxMeasurement, UbxNavOptions, UbxNavResult, UbxParseResult, UbxRawNavOptions, UbxRawNavResult, UbxRawxEpoch, parseUbxCnav, parseUbxIonoUtc, parseUbxNav, parseUbxRawNav, parseUbxRawx, ubxFrames } from './ubx.cjs';
@@ -14,7 +15,7 @@ export { HasMessage, SbfBdsNavResult, SbfCnavEphemeris, SbfCnavResult, SbfGalEph
14
15
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelRange } from './novatel.cjs';
15
16
  export { AllPositionsData, AlmanacPosition, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.cjs';
16
17
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
17
- export { DgnssOptions, DgnssSolution, EphemerisSource, KlobucharCoeffs, LambdaResult, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.cjs';
18
+ export { DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, gimSlantIonoDelayL1, gimVerticalTec, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.cjs';
18
19
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
19
20
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.cjs';
20
21
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';
package/dist/index.d.ts CHANGED
@@ -4,9 +4,10 @@ export { A as ARC_GAP_FACTOR, B as BAND_LABELS, a as BDS_SATELLITES, b as BdsSat
4
4
  export { Scale, getBdsTime, getDateFromBdsTime, getDateFromDayOfWeek, getDateFromDayOfYear, getDateFromGalTime, getDateFromGloN, getDateFromGpsData, getDateFromGpsTime, getDateFromHourCode, getDateFromJulianDate, getDateFromMJD, getDateFromMJD2000, getDateFromRINEX, getDateFromTai, getDateFromTimeOfDay, getDateFromTt, getDateFromUnixTime, getDateFromUtc, getDayOfWeek, getDayOfYear, getGalTime, getGloN4, getGloNA, getGpsLeap, getGpsTime, getHourCode, getHoursFromTimeDifference, getJulianDate, getLeap, getMJD, getMJD2000, getMinutesFromTimeDifference, getNtpTime, getRINEX, getSecondsFromTimeDifference, getTaiDate, getTimeDifference, getTimeDifferenceFromDays, getTimeDifferenceFromHours, getTimeDifferenceFromMinutes, getTimeDifferenceFromObject, getTimeDifferenceFromSeconds, getTimeOfDay, getTimeOfWeek, getTotalDaysFromTimeDifference, getTtDate, getUnixTime, getUtcDate, getWeekNumber, getWeekOfYear } from './time.js';
5
5
  export { deg2dms, deg2rad, euclidean3D, geodeticToGeohash, geodeticToMaidenhead, geodeticToUtm, greatCircleMidpoint, horizonDistance, rad2deg, rhumbLine, vincenty } from './coordinates.js';
6
6
  export { c as clampUnit, e as ecefToGeodetic, g as geodeticToEcef, a as getAer, b as getEnuDifference } from './ecef-CF0uAysr.js';
7
- export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, IonexGrid, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseIonex, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.js';
7
+ export { CompactEpoch, CrxField, DiffState, EMPTY_WARNINGS, Nav4Input, RinexWarning, RinexWarnings, Sp3File, Sp3Sample, WarningAccumulator, WarningSeverity, createGzipLineSink, crxDecompress, crxRepair, fmtD, fmtF, hdrLine, padL, padR, parseCrxDataLine, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob } from './rinex.js';
8
8
  export { E as EpochSummary, P as ParseOptions, R as RinexHeader, 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';
9
9
  export { C as CnavEphemeris, E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, R as RinexCnavEphemeris, p as parseNavFile } from './nav-DImXUdbp.js';
10
+ export { I as IonexGrid, p as parseIonex } from './ionex-DaW5x9nq.js';
10
11
  export { B as BitReader, E as EphemerisInfo, R as Rtcm3DebugHandler, a as Rtcm3Decoder, b as Rtcm3Frame, d as decodeEphemeris, r as readString, c as reportDecodeError, s as setRtcm3DebugHandler } from './ephemeris-Ohl6NAfw.js';
11
12
  export { MessageTypeStats, MsmEpoch, MsmSatObs, MsmSignal, RTCM3_MESSAGE_NAMES, SatCn0, SignalCn0, StationMeta, StreamStats, createStationMeta, createStreamStats, decodeMsmFull, msmEpochToDate, resetGloFreqCache, rtcm3Constellation, setGloFreqNumber, updateStationMeta, updateStreamStats } from './rtcm3.js';
12
13
  export { UbxCnavResult, UbxFrame, UbxIonoUtcResult, UbxMeasurement, UbxNavOptions, UbxNavResult, UbxParseResult, UbxRawNavOptions, UbxRawNavResult, UbxRawxEpoch, parseUbxCnav, parseUbxIonoUtc, parseUbxNav, parseUbxRawNav, parseUbxRawx, ubxFrames } from './ubx.js';
@@ -14,7 +15,7 @@ export { HasMessage, SbfBdsNavResult, SbfCnavEphemeris, SbfCnavResult, SbfGalEph
14
15
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelRange } from './novatel.js';
15
16
  export { AllPositionsData, AlmanacPosition, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.js';
16
17
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
17
- export { DgnssOptions, DgnssSolution, EphemerisSource, KlobucharCoeffs, LambdaResult, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.js';
18
+ export { DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SppOptions, SppSolution, gimSlantIonoDelayL1, gimVerticalTec, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch } from './positioning.js';
18
19
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
19
20
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.js';
20
21
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.js';
package/dist/index.js CHANGED
@@ -128,7 +128,10 @@ import {
128
128
  transformFrame
129
129
  } from "./chunk-4KOXMHUW.js";
130
130
  import {
131
+ IONO_L1_M_PER_TECU,
131
132
  RtkFloatEngine,
133
+ gimSlantIonoDelayL1,
134
+ gimVerticalTec,
132
135
  ionoFree,
133
136
  klobucharDelay,
134
137
  lambdaReduction,
@@ -137,7 +140,7 @@ import {
137
140
  solveDgnss,
138
141
  solveSpp,
139
142
  toRtkEpoch
140
- } from "./chunk-T2ZMNRCY.js";
143
+ } from "./chunk-4XGKCVE3.js";
141
144
  import {
142
145
  almanacEpochMs,
143
146
  almanacSatPosition,
@@ -353,6 +356,7 @@ export {
353
356
  GLO_F2_BASE,
354
357
  GLO_F2_STEP,
355
358
  GLO_F3,
359
+ IONO_L1_M_PER_TECU,
356
360
  IonoAccumulator,
357
361
  MILLISECONDS_GPS_TAI,
358
362
  MILLISECONDS_IN_DAY,
@@ -488,6 +492,8 @@ export {
488
492
  getUtcDate,
489
493
  getWeekNumber,
490
494
  getWeekOfYear,
495
+ gimSlantIonoDelayL1,
496
+ gimVerticalTec,
491
497
  gloFreq,
492
498
  glonassAlmanacEpochMs,
493
499
  glonassAlmanacPosition,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
3
+ * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
4
+ *
5
+ * Returns the TEC maps in TECU on a regular lat/lon grid, one map per
6
+ * epoch. RMS maps and the DCB auxiliary block are skipped.
7
+ */
8
+ interface IonexGrid {
9
+ /** Map epochs (Unix ms, UTC). */
10
+ epochs: number[];
11
+ /** Grid latitudes (degrees), in file order (typically 87.5 → −87.5). */
12
+ lats: number[];
13
+ /** Grid longitudes (degrees), in file order (typically −180 → 180). */
14
+ lons: number[];
15
+ /**
16
+ * TEC maps in TECU: maps[epochIdx][latIdx * lons.length + lonIdx].
17
+ * NaN where the file marks no value (9999).
18
+ */
19
+ maps: Float32Array[];
20
+ }
21
+ declare function parseIonex(text: string): IonexGrid;
22
+
23
+ export { type IonexGrid as I, parseIonex as p };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
3
+ * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
4
+ *
5
+ * Returns the TEC maps in TECU on a regular lat/lon grid, one map per
6
+ * epoch. RMS maps and the DCB auxiliary block are skipped.
7
+ */
8
+ interface IonexGrid {
9
+ /** Map epochs (Unix ms, UTC). */
10
+ epochs: number[];
11
+ /** Grid latitudes (degrees), in file order (typically 87.5 → −87.5). */
12
+ lats: number[];
13
+ /** Grid longitudes (degrees), in file order (typically −180 → 180). */
14
+ lons: number[];
15
+ /**
16
+ * TEC maps in TECU: maps[epochIdx][latIdx * lons.length + lonIdx].
17
+ * NaN where the file marks no value (9999).
18
+ */
19
+ maps: Float32Array[];
20
+ }
21
+ declare function parseIonex(text: string): IonexGrid;
22
+
23
+ export { type IonexGrid as I, parseIonex as p };
@@ -20,7 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/positioning/index.ts
21
21
  var positioning_exports = {};
22
22
  __export(positioning_exports, {
23
+ IONO_L1_M_PER_TECU: () => IONO_L1_M_PER_TECU,
23
24
  RtkFloatEngine: () => RtkFloatEngine,
25
+ gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
26
+ gimVerticalTec: () => gimVerticalTec,
24
27
  ionoFree: () => ionoFree,
25
28
  klobucharDelay: () => klobucharDelay,
26
29
  lambdaReduction: () => lambdaReduction,
@@ -464,6 +467,51 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
464
467
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
465
468
  }
466
469
 
470
+ // src/positioning/gim.ts
471
+ var R_E = 6371e3;
472
+ var H_ION = 45e4;
473
+ var F_L1 = 157542e4;
474
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
475
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
476
+ const m = grid.maps[mapIdx];
477
+ const { lats, lons } = grid;
478
+ const w = lons.length;
479
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
480
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
481
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
482
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
483
+ const di = Math.max(0, Math.min(1, fi - i0));
484
+ const dj = Math.max(0, Math.min(1, fj - j0));
485
+ return m[i0 * w + j0] * (1 - di) * (1 - dj) + m[i0 * w + j0 + 1] * (1 - di) * dj + m[(i0 + 1) * w + j0] * di * (1 - dj) + m[(i0 + 1) * w + j0 + 1] * di * dj;
486
+ }
487
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
488
+ const e = grid.epochs;
489
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
490
+ let i = 0;
491
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
492
+ const span = e[i + 1] - e[i];
493
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
494
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
495
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
496
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
497
+ return a * (1 - f) + b * f;
498
+ }
499
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
500
+ if (elRad <= 0) return null;
501
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
502
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
503
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
504
+ const latI = Math.asin(
505
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
506
+ );
507
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
508
+ const latDeg = latI * 180 / Math.PI;
509
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
510
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
511
+ if (vtec === null) return null;
512
+ return IONO_L1_M_PER_TECU * vtec * mapping;
513
+ }
514
+
467
515
  // src/positioning/lambda.ts
468
516
  var LOOPMAX = 1e4;
469
517
  var ROUND = (x) => Math.floor(x + 0.5);
@@ -1505,10 +1553,17 @@ var PRIMARY_FREQ_HZ = {
1505
1553
  C: 1561098e3,
1506
1554
  R: 1602e6
1507
1555
  };
1508
- var F_L1 = 157542e4;
1509
- function tropoDelay2(elevationRad) {
1510
- const sinEl = Math.sin(elevationRad);
1511
- return 2.47 / (sinEl + 0.0121);
1556
+ var F_L12 = 157542e4;
1557
+ function tropoDelay2(elevationRad, latRad, heightM) {
1558
+ if (elevationRad <= 0) return 0;
1559
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
1560
+ const humi = 0.7;
1561
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
1562
+ const temp = 15 - 65e-4 * h + 273.16;
1563
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
1564
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
1565
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
1566
+ return (zhd + zwd) / Math.sin(elevationRad);
1512
1567
  }
1513
1568
  function sagnac2(pos, travelTimeS) {
1514
1569
  const theta = OMEGA_E * travelTimeS;
@@ -1541,7 +1596,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1541
1596
  maxIterations = 15,
1542
1597
  convergenceM = 1e-4,
1543
1598
  tgd = true,
1544
- iono
1599
+ iono,
1600
+ gim
1545
1601
  } = opts;
1546
1602
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
1547
1603
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -1568,7 +1624,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1568
1624
  let rows = 0;
1569
1625
  for (const k of Object.keys(residuals2)) delete residuals2[k];
1570
1626
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
1571
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
1627
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
1572
1628
  for (const prn of prns) {
1573
1629
  const psr = pseudoranges.get(prn);
1574
1630
  const eph = ephemerides.get(prn);
@@ -1598,11 +1654,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1598
1654
  const sinEl = Math.max(Math.sin(elev), 0.1);
1599
1655
  weight = sinEl * sinEl;
1600
1656
  }
1601
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
1657
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
1602
1658
  let ionoM = 0;
1603
- if (iono && positionSane) {
1604
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
1605
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
1659
+ if ((gim || iono) && positionSane) {
1660
+ let l1M = null;
1661
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
1662
+ if (l1M === null && iono) {
1663
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
1664
+ }
1665
+ if (l1M !== null) {
1666
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
1667
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
1668
+ }
1606
1669
  }
1607
1670
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
1608
1671
  const v = psr - predicted;
@@ -1683,7 +1746,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
1683
1746
  }
1684
1747
  // Annotate the CommonJS export names for ESM import in node:
1685
1748
  0 && (module.exports = {
1749
+ IONO_L1_M_PER_TECU,
1686
1750
  RtkFloatEngine,
1751
+ gimSlantIonoDelayL1,
1752
+ gimVerticalTec,
1687
1753
  ionoFree,
1688
1754
  klobucharDelay,
1689
1755
  lambdaReduction,
@@ -1,5 +1,6 @@
1
1
  import { E as Ephemeris } from './nav-DImXUdbp.cjs';
2
2
  import { DopValues } from './orbit.cjs';
3
+ import { I as IonexGrid } from './ionex-DaW5x9nq.cjs';
3
4
  import './ephemeris-Ohl6NAfw.cjs';
4
5
  import './ecef-CF0uAysr.cjs';
5
6
  import './nav-BYmYoh9A.cjs';
@@ -34,6 +35,52 @@ interface KlobucharCoeffs {
34
35
  */
35
36
  declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad: number, azRad: number, elRad: number, gpsTowSec: number): number;
36
37
 
38
+ /**
39
+ * Slant ionospheric delay from a Global Ionosphere Map (IONEX/GIM).
40
+ *
41
+ * A GIM gives vertical TEC on an hourly lat/lon grid (see
42
+ * {@link parseIonex}). For single-frequency positioning this is a much
43
+ * stronger correction than the broadcast Klobuchar model — GIMs capture
44
+ * ~80–90% of the true ionosphere versus Klobuchar's ~50% — so feeding
45
+ * `solveSpp` a GIM (the `gim` option) collapses most of the residual
46
+ * height bias a single-frequency solution otherwise carries.
47
+ *
48
+ * The evaluation is the standard thin-shell recipe: pierce the shell at
49
+ * {@link H_ION}, interpolate vertical TEC there (bilinear in space,
50
+ * linear in time), map to slant with the obliquity factor, and convert
51
+ * TEC → L1 group delay. The result is the delay on GPS L1; `solveSpp`
52
+ * scales it to each system's primary frequency by (f_L1/f)².
53
+ */
54
+
55
+ /**
56
+ * L1 group delay per unit slant TEC: 40.3 · 10¹⁶ / f_L1² (m per TECU).
57
+ * 40.3 m·Hz²/(el/m²), one TECU = 10¹⁶ el/m². ≈ 0.1624 m/TECU.
58
+ */
59
+ declare const IONO_L1_M_PER_TECU: number;
60
+ /**
61
+ * Space- and time-interpolated vertical TEC (TECU) at `timeMs`, or null
62
+ * outside the map's time span or where the grid marks no value.
63
+ *
64
+ * IGS GIMs are global, so spatial coverage is total; a pierce point is
65
+ * bilinearly interpolated and clamped to the nearest edge cell rather
66
+ * than rejected. Null therefore signals a time gap or a no-value cell,
67
+ * not an out-of-area position.
68
+ */
69
+ declare function gimVerticalTec(grid: IonexGrid, timeMs: number, latDeg: number, lonDeg: number): number | null;
70
+ /**
71
+ * Slant ionospheric group delay on GPS L1 (m) from a GIM, or null when
72
+ * the pierce point falls outside the map's coverage in space or time.
73
+ *
74
+ * @param grid Parsed IONEX grid ({@link parseIonex}).
75
+ * @param latRad Receiver geodetic latitude (radians).
76
+ * @param lonRad Receiver geodetic longitude (radians).
77
+ * @param azRad Satellite azimuth (radians).
78
+ * @param elRad Satellite elevation (radians).
79
+ * @param timeMs Epoch (ms). GIM epochs are UTC; a GPS-scale time differs
80
+ * by leap seconds (~18 s), negligible against hourly maps.
81
+ */
82
+ declare function gimSlantIonoDelayL1(grid: IonexGrid, latRad: number, lonRad: number, azRad: number, elRad: number, timeMs: number): number | null;
83
+
37
84
  /**
38
85
  * RTK positioning, stage 1: epoch-wise double-differenced (DD)
39
86
  * processing of a base/rover receiver pair.
@@ -441,7 +488,7 @@ declare function lambdaReduction(Q: Float64Array, n: number): Float64Array | nul
441
488
  * constellation (absorbing inter-system time offsets), satellite clock
442
489
  * polynomial + relativistic correction, broadcast group delay
443
490
  * (TGD/BGD), Sagnac (Earth-rotation) correction, elevation
444
- * masking/weighting, and a simple tropospheric model. Ionospheric
491
+ * masking/weighting, and a Saastamoinen tropospheric model. Ionospheric
445
492
  * delay can be modelled from the broadcast Klobuchar coefficients
446
493
  * (`iono` option) for single-frequency measurements; for metre-level
447
494
  * results prefer the iono-free combination (ionoFree helper) with
@@ -451,7 +498,7 @@ declare function lambdaReduction(Q: Float64Array, n: number): Float64Array | nul
451
498
  interface SppOptions {
452
499
  /** Elevation mask in degrees (applied after the first pass). Default 10. */
453
500
  elevationMaskDeg?: number;
454
- /** Apply the simple tropospheric model. Default true. */
501
+ /** Apply the Saastamoinen tropospheric model. Default true. */
455
502
  troposphere?: boolean;
456
503
  /** Maximum Gauss-Newton iterations. Default 15. */
457
504
  maxIterations?: number;
@@ -465,6 +512,15 @@ interface SppOptions {
465
512
  * iono-free combinations.
466
513
  */
467
514
  iono?: KlobucharCoeffs;
515
+ /**
516
+ * Global Ionosphere Map (IONEX/GIM, from `parseIonex`). When given it
517
+ * takes precedence over `iono`: the slant delay is read from the map
518
+ * (~80–90% of the true ionosphere vs Klobuchar's ~50%), falling back
519
+ * to `iono` — if also supplied — only where the map has no value for
520
+ * an epoch/cell (a time gap; global maps always cover space). Omit for
521
+ * iono-free input.
522
+ */
523
+ gim?: IonexGrid;
468
524
  /**
469
525
  * Apply the broadcast group-delay correction (GPS TGD, Galileo
470
526
  * BGD E5a/E1, BeiDou TGD1) to the satellite clock. Correct for
@@ -513,4 +569,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
513
569
  */
514
570
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
515
571
 
516
- export { type DgnssOptions, type DgnssSolution, type EphemerisSource, type KlobucharCoeffs, type LambdaResult, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
572
+ export { type DgnssOptions, type DgnssSolution, type EphemerisSource, IONO_L1_M_PER_TECU, type KlobucharCoeffs, type LambdaResult, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, gimSlantIonoDelayL1, gimVerticalTec, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
@@ -1,5 +1,6 @@
1
1
  import { E as Ephemeris } from './nav-DImXUdbp.js';
2
2
  import { DopValues } from './orbit.js';
3
+ import { I as IonexGrid } from './ionex-DaW5x9nq.js';
3
4
  import './ephemeris-Ohl6NAfw.js';
4
5
  import './ecef-CF0uAysr.js';
5
6
  import './nav-CN8rxL4h.js';
@@ -34,6 +35,52 @@ interface KlobucharCoeffs {
34
35
  */
35
36
  declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad: number, azRad: number, elRad: number, gpsTowSec: number): number;
36
37
 
38
+ /**
39
+ * Slant ionospheric delay from a Global Ionosphere Map (IONEX/GIM).
40
+ *
41
+ * A GIM gives vertical TEC on an hourly lat/lon grid (see
42
+ * {@link parseIonex}). For single-frequency positioning this is a much
43
+ * stronger correction than the broadcast Klobuchar model — GIMs capture
44
+ * ~80–90% of the true ionosphere versus Klobuchar's ~50% — so feeding
45
+ * `solveSpp` a GIM (the `gim` option) collapses most of the residual
46
+ * height bias a single-frequency solution otherwise carries.
47
+ *
48
+ * The evaluation is the standard thin-shell recipe: pierce the shell at
49
+ * {@link H_ION}, interpolate vertical TEC there (bilinear in space,
50
+ * linear in time), map to slant with the obliquity factor, and convert
51
+ * TEC → L1 group delay. The result is the delay on GPS L1; `solveSpp`
52
+ * scales it to each system's primary frequency by (f_L1/f)².
53
+ */
54
+
55
+ /**
56
+ * L1 group delay per unit slant TEC: 40.3 · 10¹⁶ / f_L1² (m per TECU).
57
+ * 40.3 m·Hz²/(el/m²), one TECU = 10¹⁶ el/m². ≈ 0.1624 m/TECU.
58
+ */
59
+ declare const IONO_L1_M_PER_TECU: number;
60
+ /**
61
+ * Space- and time-interpolated vertical TEC (TECU) at `timeMs`, or null
62
+ * outside the map's time span or where the grid marks no value.
63
+ *
64
+ * IGS GIMs are global, so spatial coverage is total; a pierce point is
65
+ * bilinearly interpolated and clamped to the nearest edge cell rather
66
+ * than rejected. Null therefore signals a time gap or a no-value cell,
67
+ * not an out-of-area position.
68
+ */
69
+ declare function gimVerticalTec(grid: IonexGrid, timeMs: number, latDeg: number, lonDeg: number): number | null;
70
+ /**
71
+ * Slant ionospheric group delay on GPS L1 (m) from a GIM, or null when
72
+ * the pierce point falls outside the map's coverage in space or time.
73
+ *
74
+ * @param grid Parsed IONEX grid ({@link parseIonex}).
75
+ * @param latRad Receiver geodetic latitude (radians).
76
+ * @param lonRad Receiver geodetic longitude (radians).
77
+ * @param azRad Satellite azimuth (radians).
78
+ * @param elRad Satellite elevation (radians).
79
+ * @param timeMs Epoch (ms). GIM epochs are UTC; a GPS-scale time differs
80
+ * by leap seconds (~18 s), negligible against hourly maps.
81
+ */
82
+ declare function gimSlantIonoDelayL1(grid: IonexGrid, latRad: number, lonRad: number, azRad: number, elRad: number, timeMs: number): number | null;
83
+
37
84
  /**
38
85
  * RTK positioning, stage 1: epoch-wise double-differenced (DD)
39
86
  * processing of a base/rover receiver pair.
@@ -441,7 +488,7 @@ declare function lambdaReduction(Q: Float64Array, n: number): Float64Array | nul
441
488
  * constellation (absorbing inter-system time offsets), satellite clock
442
489
  * polynomial + relativistic correction, broadcast group delay
443
490
  * (TGD/BGD), Sagnac (Earth-rotation) correction, elevation
444
- * masking/weighting, and a simple tropospheric model. Ionospheric
491
+ * masking/weighting, and a Saastamoinen tropospheric model. Ionospheric
445
492
  * delay can be modelled from the broadcast Klobuchar coefficients
446
493
  * (`iono` option) for single-frequency measurements; for metre-level
447
494
  * results prefer the iono-free combination (ionoFree helper) with
@@ -451,7 +498,7 @@ declare function lambdaReduction(Q: Float64Array, n: number): Float64Array | nul
451
498
  interface SppOptions {
452
499
  /** Elevation mask in degrees (applied after the first pass). Default 10. */
453
500
  elevationMaskDeg?: number;
454
- /** Apply the simple tropospheric model. Default true. */
501
+ /** Apply the Saastamoinen tropospheric model. Default true. */
455
502
  troposphere?: boolean;
456
503
  /** Maximum Gauss-Newton iterations. Default 15. */
457
504
  maxIterations?: number;
@@ -465,6 +512,15 @@ interface SppOptions {
465
512
  * iono-free combinations.
466
513
  */
467
514
  iono?: KlobucharCoeffs;
515
+ /**
516
+ * Global Ionosphere Map (IONEX/GIM, from `parseIonex`). When given it
517
+ * takes precedence over `iono`: the slant delay is read from the map
518
+ * (~80–90% of the true ionosphere vs Klobuchar's ~50%), falling back
519
+ * to `iono` — if also supplied — only where the map has no value for
520
+ * an epoch/cell (a time gap; global maps always cover space). Omit for
521
+ * iono-free input.
522
+ */
523
+ gim?: IonexGrid;
468
524
  /**
469
525
  * Apply the broadcast group-delay correction (GPS TGD, Galileo
470
526
  * BGD E5a/E1, BeiDou TGD1) to the satellite clock. Correct for
@@ -513,4 +569,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
513
569
  */
514
570
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
515
571
 
516
- export { type DgnssOptions, type DgnssSolution, type EphemerisSource, type KlobucharCoeffs, type LambdaResult, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
572
+ export { type DgnssOptions, type DgnssSolution, type EphemerisSource, IONO_L1_M_PER_TECU, type KlobucharCoeffs, type LambdaResult, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, gimSlantIonoDelayL1, gimVerticalTec, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
@@ -1,5 +1,8 @@
1
1
  import {
2
+ IONO_L1_M_PER_TECU,
2
3
  RtkFloatEngine,
4
+ gimSlantIonoDelayL1,
5
+ gimVerticalTec,
3
6
  ionoFree,
4
7
  klobucharDelay,
5
8
  lambdaReduction,
@@ -8,7 +11,7 @@ import {
8
11
  solveDgnss,
9
12
  solveSpp,
10
13
  toRtkEpoch
11
- } from "./chunk-T2ZMNRCY.js";
14
+ } from "./chunk-4XGKCVE3.js";
12
15
  import "./chunk-MHGNKSSH.js";
13
16
  import "./chunk-37QNKGTC.js";
14
17
  import "./chunk-6FAL6P4G.js";
@@ -16,7 +19,10 @@ import "./chunk-HVXYFUCB.js";
16
19
  import "./chunk-LEEU5OIO.js";
17
20
  import "./chunk-FIEWO4J4.js";
18
21
  export {
22
+ IONO_L1_M_PER_TECU,
19
23
  RtkFloatEngine,
24
+ gimSlantIonoDelayL1,
25
+ gimVerticalTec,
20
26
  ionoFree,
21
27
  klobucharDelay,
22
28
  lambdaReduction,
package/dist/rinex.d.cts CHANGED
@@ -2,6 +2,7 @@ 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
3
  import { a as NavResult, N as NavHeader, E as Ephemeris, R as RinexCnavEphemeris } from './nav-DImXUdbp.cjs';
4
4
  export { G as GlonassEphemeris, K as KeplerEphemeris, p as parseNavFile } from './nav-DImXUdbp.cjs';
5
+ export { I as IonexGrid, p as parseIonex } from './ionex-DaW5x9nq.cjs';
5
6
 
6
7
  /**
7
8
  * Compact RINEX (Hatanaka / CRX) decompression utilities.
@@ -155,28 +156,6 @@ interface Nav4Input {
155
156
  */
156
157
  declare function writeRinexNav4(nav: Nav4Input): string;
157
158
 
158
- /**
159
- * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
160
- * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
161
- *
162
- * Returns the TEC maps in TECU on a regular lat/lon grid, one map per
163
- * epoch. RMS maps and the DCB auxiliary block are skipped.
164
- */
165
- interface IonexGrid {
166
- /** Map epochs (Unix ms, UTC). */
167
- epochs: number[];
168
- /** Grid latitudes (degrees), in file order (typically 87.5 → −87.5). */
169
- lats: number[];
170
- /** Grid longitudes (degrees), in file order (typically −180 → 180). */
171
- lons: number[];
172
- /**
173
- * TEC maps in TECU: maps[epochIdx][latIdx * lons.length + lonIdx].
174
- * NaN where the file marks no value (9999).
175
- */
176
- maps: Float32Array[];
177
- }
178
- declare function parseIonex(text: string): IonexGrid;
179
-
180
159
  /**
181
160
  * SP3-c/-d precise orbit and clock parser, with Lagrange interpolation
182
161
  * for evaluating positions between the tabulated epochs.
@@ -305,4 +284,4 @@ declare function createGzipLineSink(): {
305
284
  */
306
285
  declare function stationHeaderLines(header: RinexHeader): string[];
307
286
 
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 };
287
+ export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, Ephemeris, 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, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob };
package/dist/rinex.d.ts CHANGED
@@ -2,6 +2,7 @@ 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
3
  import { a as NavResult, N as NavHeader, E as Ephemeris, R as RinexCnavEphemeris } from './nav-DImXUdbp.js';
4
4
  export { G as GlonassEphemeris, K as KeplerEphemeris, p as parseNavFile } from './nav-DImXUdbp.js';
5
+ export { I as IonexGrid, p as parseIonex } from './ionex-DaW5x9nq.js';
5
6
 
6
7
  /**
7
8
  * Compact RINEX (Hatanaka / CRX) decompression utilities.
@@ -155,28 +156,6 @@ interface Nav4Input {
155
156
  */
156
157
  declare function writeRinexNav4(nav: Nav4Input): string;
157
158
 
158
- /**
159
- * IONEX 1.0 parser — global ionosphere maps (GIMs) as published by
160
- * the IGS analysis centres (e.g. ESA0OPSRAP_*_GIM.INX).
161
- *
162
- * Returns the TEC maps in TECU on a regular lat/lon grid, one map per
163
- * epoch. RMS maps and the DCB auxiliary block are skipped.
164
- */
165
- interface IonexGrid {
166
- /** Map epochs (Unix ms, UTC). */
167
- epochs: number[];
168
- /** Grid latitudes (degrees), in file order (typically 87.5 → −87.5). */
169
- lats: number[];
170
- /** Grid longitudes (degrees), in file order (typically −180 → 180). */
171
- lons: number[];
172
- /**
173
- * TEC maps in TECU: maps[epochIdx][latIdx * lons.length + lonIdx].
174
- * NaN where the file marks no value (9999).
175
- */
176
- maps: Float32Array[];
177
- }
178
- declare function parseIonex(text: string): IonexGrid;
179
-
180
159
  /**
181
160
  * SP3-c/-d precise orbit and clock parser, with Lagrange interpolation
182
161
  * for evaluating positions between the tabulated epochs.
@@ -305,4 +284,4 @@ declare function createGzipLineSink(): {
305
284
  */
306
285
  declare function stationHeaderLines(header: RinexHeader): string[];
307
286
 
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 };
287
+ export { type CompactEpoch, type CrxField, type DiffState, EMPTY_WARNINGS, Ephemeris, 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, parseSp3, sp3Position, stationHeaderLines, writeModernObsBlob, writeRinex2ObsBlob, writeRinex4ObsBlob, writeRinexNav, writeRinexNav4, writeRinexObsBlob };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.24.0",
3
+ "version": "1.25.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,