gnss-js 2.8.1 → 2.9.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.
@@ -37,6 +37,42 @@ function invertMatrix(A) {
37
37
  return M.map((row) => row.slice(n));
38
38
  }
39
39
 
40
+ // src/orbit/validity.ts
41
+ var H = 36e5;
42
+ var MIN = 6e4;
43
+ var EPHEMERIS_VALIDITY_MS = {
44
+ G: 2 * H,
45
+ J: 2 * H,
46
+ E: 2.5 * H,
47
+ C: 90 * MIN,
48
+ R: 15 * MIN,
49
+ I: 2 * H,
50
+ S: 6 * MIN
51
+ };
52
+ function tocDistanceMs(eph, tGpsMs) {
53
+ const t = eph.system === "R" || eph.system === "S" ? tGpsMs - getGpsLeap(new Date(tGpsMs)) * 1e3 : tGpsMs;
54
+ return Math.abs(eph.tocDate.getTime() - t);
55
+ }
56
+ function isEphemerisValid(eph, tGpsMs) {
57
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
58
+ return w !== void 0 && tocDistanceMs(eph, tGpsMs) <= w;
59
+ }
60
+ function nearestValidEphemeris(ephs, tGpsMs) {
61
+ if (!ephs) return null;
62
+ let best = null;
63
+ let bestDt = Infinity;
64
+ for (const eph of ephs) {
65
+ const dt = tocDistanceMs(eph, tGpsMs);
66
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
67
+ if (w === void 0 || dt > w) continue;
68
+ if (dt < bestDt) {
69
+ bestDt = dt;
70
+ best = eph;
71
+ }
72
+ }
73
+ return best;
74
+ }
75
+
40
76
  // src/orbit/almanac.ts
41
77
  var GM_GPS = 3986005e8;
42
78
  var GM_GAL = 3986004418e5;
@@ -409,24 +445,30 @@ function dopClock(prn) {
409
445
  return sys === "J" || sys === "S" ? "G" : sys;
410
446
  }
411
447
  function computeDop(satAzEls) {
412
- const n = satAzEls.length;
413
- const clocks = [...new Set(satAzEls.map((s) => dopClock(s.prn)))];
448
+ const perClock = /* @__PURE__ */ new Map();
449
+ for (const s of satAzEls) {
450
+ const c = dopClock(s.prn);
451
+ perClock.set(c, (perClock.get(c) ?? 0) + 1);
452
+ }
453
+ const sats = perClock.size > 1 ? satAzEls.filter((s) => perClock.get(dopClock(s.prn)) > 1) : satAzEls;
454
+ const n = sats.length;
455
+ const clocks = [...new Set(sats.map((s) => dopClock(s.prn)))];
414
456
  const m = 3 + clocks.length;
415
457
  if (n < Math.max(4, m)) return null;
416
- const H = [];
417
- for (const { az, el, prn } of satAzEls) {
458
+ const H2 = [];
459
+ for (const { az, el, prn } of sats) {
418
460
  const cosEl = Math.cos(el);
419
461
  const row = [cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el)];
420
462
  const clock = dopClock(prn);
421
463
  for (const c of clocks) row.push(c === clock ? 1 : 0);
422
- H.push(row);
464
+ H2.push(row);
423
465
  }
424
466
  const HtH = Array.from({ length: m }, () => new Array(m).fill(0));
425
467
  for (let i = 0; i < m; i++) {
426
468
  for (let j = 0; j < m; j++) {
427
469
  let sum = 0;
428
470
  for (let k = 0; k < n; k++) {
429
- sum += H[k][i] * H[k][j];
471
+ sum += H2[k][i] * H2[k][j];
430
472
  }
431
473
  HtH[i][j] = sum;
432
474
  }
@@ -527,18 +569,7 @@ function computeAllPositions(ephemerides, times, rxPos) {
527
569
  return { prns, times, positions };
528
570
  }
529
571
  function selectBest(ephs, timeMs) {
530
- let best = null;
531
- let bestDt = Infinity;
532
- for (const eph of ephs) {
533
- const dt = Math.abs(timeMs - eph.tocDate.getTime());
534
- const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
535
- if (dt > maxAge) continue;
536
- if (dt < bestDt) {
537
- bestDt = dt;
538
- best = eph;
539
- }
540
- }
541
- return best;
572
+ return nearestValidEphemeris(ephs, timeMs);
542
573
  }
543
574
  var GPS_EPOCH_MS2 = START_GPS_TIME.getTime();
544
575
  var MS_PER_WEEK = 7 * 864e5;
@@ -780,6 +811,9 @@ function computeVisibilityFromPositions(all, elevationMaskDeg = 10) {
780
811
 
781
812
  export {
782
813
  invertMatrix,
814
+ EPHEMERIS_VALIDITY_MS,
815
+ isEphemerisValid,
816
+ nearestValidEphemeris,
783
817
  almanacEpochMs,
784
818
  almanacSatPosition,
785
819
  glonassAlmanacEpochMs,
@@ -7,7 +7,7 @@ import {
7
7
  ecefToAzEl,
8
8
  invertMatrix,
9
9
  selectEphemeris
10
- } from "./chunk-T5M3LVBJ.js";
10
+ } from "./chunk-7T5DLVZL.js";
11
11
  import {
12
12
  ecefToGeodetic,
13
13
  getAer,
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(src_exports, {
36
36
  DELTA_GLO_L2: () => DELTA_GLO_L2,
37
37
  DUAL_FREQ_PAIRS: () => DUAL_FREQ_PAIRS,
38
38
  EMPTY_WARNINGS: () => EMPTY_WARNINGS,
39
+ EPHEMERIS_VALIDITY_MS: () => EPHEMERIS_VALIDITY_MS,
39
40
  FREQ: () => FREQ,
40
41
  FREQ_BDS_B1A: () => FREQ_BDS_B1A,
41
42
  FREQ_BDS_B1C: () => FREQ_BDS_B1C,
@@ -252,6 +253,7 @@ __export(src_exports, {
252
253
  hdrLine: () => hdrLine,
253
254
  horizonDistance: () => horizonDistance,
254
255
  ionoFree: () => ionoFree,
256
+ isEphemerisValid: () => isEphemerisValid,
255
257
  isSbasL1Preamble: () => isSbasL1Preamble,
256
258
  isSbasL5Correction: () => isSbasL5Correction,
257
259
  isSsrMessage: () => isSsrMessage,
@@ -264,6 +266,7 @@ __export(src_exports, {
264
266
  meridianArc: () => meridianArc,
265
267
  moonEcef: () => moonEcef,
266
268
  navTimesFromEph: () => navTimesFromEph,
269
+ nearestValidEphemeris: () => nearestValidEphemeris,
267
270
  niellMapping: () => niellMapping,
268
271
  nlWavelength: () => nlWavelength,
269
272
  nmeaCoordToDecimal: () => nmeaCoordToDecimal,
@@ -12312,6 +12315,42 @@ function invertMatrix(A) {
12312
12315
  return M.map((row) => row.slice(n));
12313
12316
  }
12314
12317
 
12318
+ // src/orbit/validity.ts
12319
+ var H = 36e5;
12320
+ var MIN = 6e4;
12321
+ var EPHEMERIS_VALIDITY_MS = {
12322
+ G: 2 * H,
12323
+ J: 2 * H,
12324
+ E: 2.5 * H,
12325
+ C: 90 * MIN,
12326
+ R: 15 * MIN,
12327
+ I: 2 * H,
12328
+ S: 6 * MIN
12329
+ };
12330
+ function tocDistanceMs(eph, tGpsMs) {
12331
+ const t = eph.system === "R" || eph.system === "S" ? tGpsMs - getGpsLeap(new Date(tGpsMs)) * 1e3 : tGpsMs;
12332
+ return Math.abs(eph.tocDate.getTime() - t);
12333
+ }
12334
+ function isEphemerisValid(eph, tGpsMs) {
12335
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
12336
+ return w !== void 0 && tocDistanceMs(eph, tGpsMs) <= w;
12337
+ }
12338
+ function nearestValidEphemeris(ephs, tGpsMs) {
12339
+ if (!ephs) return null;
12340
+ let best = null;
12341
+ let bestDt = Infinity;
12342
+ for (const eph of ephs) {
12343
+ const dt = tocDistanceMs(eph, tGpsMs);
12344
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
12345
+ if (w === void 0 || dt > w) continue;
12346
+ if (dt < bestDt) {
12347
+ bestDt = dt;
12348
+ best = eph;
12349
+ }
12350
+ }
12351
+ return best;
12352
+ }
12353
+
12315
12354
  // src/orbit/almanac.ts
12316
12355
  var GM_GPS = 3986005e8;
12317
12356
  var GM_GAL = 3986004418e5;
@@ -12684,12 +12723,18 @@ function dopClock(prn) {
12684
12723
  return sys === "J" || sys === "S" ? "G" : sys;
12685
12724
  }
12686
12725
  function computeDop(satAzEls) {
12687
- const n = satAzEls.length;
12688
- const clocks = [...new Set(satAzEls.map((s) => dopClock(s.prn)))];
12726
+ const perClock = /* @__PURE__ */ new Map();
12727
+ for (const s of satAzEls) {
12728
+ const c = dopClock(s.prn);
12729
+ perClock.set(c, (perClock.get(c) ?? 0) + 1);
12730
+ }
12731
+ const sats = perClock.size > 1 ? satAzEls.filter((s) => perClock.get(dopClock(s.prn)) > 1) : satAzEls;
12732
+ const n = sats.length;
12733
+ const clocks = [...new Set(sats.map((s) => dopClock(s.prn)))];
12689
12734
  const m = 3 + clocks.length;
12690
12735
  if (n < Math.max(4, m)) return null;
12691
12736
  const H3 = [];
12692
- for (const { az, el, prn } of satAzEls) {
12737
+ for (const { az, el, prn } of sats) {
12693
12738
  const cosEl = Math.cos(el);
12694
12739
  const row = [cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el)];
12695
12740
  const clock = dopClock(prn);
@@ -12802,18 +12847,7 @@ function computeAllPositions(ephemerides, times, rxPos) {
12802
12847
  return { prns, times, positions };
12803
12848
  }
12804
12849
  function selectBest(ephs, timeMs) {
12805
- let best = null;
12806
- let bestDt = Infinity;
12807
- for (const eph of ephs) {
12808
- const dt = Math.abs(timeMs - eph.tocDate.getTime());
12809
- const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
12810
- if (dt > maxAge) continue;
12811
- if (dt < bestDt) {
12812
- bestDt = dt;
12813
- best = eph;
12814
- }
12815
- }
12816
- return best;
12850
+ return nearestValidEphemeris(ephs, timeMs);
12817
12851
  }
12818
12852
  var GPS_EPOCH_MS24 = START_GPS_TIME.getTime();
12819
12853
  var MS_PER_WEEK13 = 7 * 864e5;
@@ -13054,14 +13088,14 @@ function computeVisibilityFromPositions(all, elevationMaskDeg = 10) {
13054
13088
  }
13055
13089
 
13056
13090
  // src/frames/index.ts
13057
- var H = (t, d, r, tDot, dDot, rDot, epoch) => ({ t, d, r, tDot, dDot, rDot, epoch });
13091
+ var H2 = (t, d, r, tDot, dDot, rDot, epoch) => ({ t, d, r, tDot, dDot, rDot, epoch });
13058
13092
  var Z = [0, 0, 0];
13059
13093
  var FROM_ITRF2020 = {
13060
- ITRF2014: H([-1.4, -0.9, 1.4], -0.42, Z, [0, -0.1, 0.2], 0, Z, 2015),
13061
- ITRF2008: H([0.2, 1, 3.3], -0.29, Z, [0, -0.1, 0.1], 0.03, Z, 2015),
13062
- ITRF2005: H([2.7, 0.1, -1.4], 0.65, Z, [0.3, -0.1, 0.1], 0.03, Z, 2015),
13063
- ITRF2000: H([-0.2, 0.8, -34.2], 2.25, Z, [0.1, 0, -1.7], 0.11, Z, 2015),
13064
- ITRF97: H(
13094
+ ITRF2014: H2([-1.4, -0.9, 1.4], -0.42, Z, [0, -0.1, 0.2], 0, Z, 2015),
13095
+ ITRF2008: H2([0.2, 1, 3.3], -0.29, Z, [0, -0.1, 0.1], 0.03, Z, 2015),
13096
+ ITRF2005: H2([2.7, 0.1, -1.4], 0.65, Z, [0.3, -0.1, 0.1], 0.03, Z, 2015),
13097
+ ITRF2000: H2([-0.2, 0.8, -34.2], 2.25, Z, [0.1, 0, -1.7], 0.11, Z, 2015),
13098
+ ITRF97: H2(
13065
13099
  [6.5, -3.9, -77.9],
13066
13100
  3.98,
13067
13101
  [0, 0, 0.36],
@@ -13070,7 +13104,7 @@ var FROM_ITRF2020 = {
13070
13104
  [0, 0, 0.02],
13071
13105
  2015
13072
13106
  ),
13073
- ITRF96: H(
13107
+ ITRF96: H2(
13074
13108
  [6.5, -3.9, -77.9],
13075
13109
  3.98,
13076
13110
  [0, 0, 0.36],
@@ -13079,7 +13113,7 @@ var FROM_ITRF2020 = {
13079
13113
  [0, 0, 0.02],
13080
13114
  2015
13081
13115
  ),
13082
- ITRF94: H(
13116
+ ITRF94: H2(
13083
13117
  [6.5, -3.9, -77.9],
13084
13118
  3.98,
13085
13119
  [0, 0, 0.36],
@@ -13088,7 +13122,7 @@ var FROM_ITRF2020 = {
13088
13122
  [0, 0, 0.02],
13089
13123
  2015
13090
13124
  ),
13091
- ITRF93: H(
13125
+ ITRF93: H2(
13092
13126
  [-65.8, 1.9, -71.3],
13093
13127
  4.47,
13094
13128
  [-3.36, -4.33, 0.75],
@@ -13097,7 +13131,7 @@ var FROM_ITRF2020 = {
13097
13131
  [-0.11, -0.19, 0.07],
13098
13132
  2015
13099
13133
  ),
13100
- ITRF92: H(
13134
+ ITRF92: H2(
13101
13135
  [14.5, -1.9, -85.9],
13102
13136
  3.27,
13103
13137
  [0, 0, 0.36],
@@ -13106,7 +13140,7 @@ var FROM_ITRF2020 = {
13106
13140
  [0, 0, 0.02],
13107
13141
  2015
13108
13142
  ),
13109
- ITRF91: H(
13143
+ ITRF91: H2(
13110
13144
  [26.5, 12.1, -91.9],
13111
13145
  4.67,
13112
13146
  [0, 0, 0.36],
@@ -13115,7 +13149,7 @@ var FROM_ITRF2020 = {
13115
13149
  [0, 0, 0.02],
13116
13150
  2015
13117
13151
  ),
13118
- ITRF90: H(
13152
+ ITRF90: H2(
13119
13153
  [24.5, 8.1, -107.9],
13120
13154
  4.97,
13121
13155
  [0, 0, 0.36],
@@ -13124,7 +13158,7 @@ var FROM_ITRF2020 = {
13124
13158
  [0, 0, 0.02],
13125
13159
  2015
13126
13160
  ),
13127
- ITRF89: H(
13161
+ ITRF89: H2(
13128
13162
  [29.5, 32.1, -145.9],
13129
13163
  8.37,
13130
13164
  [0, 0, 0.36],
@@ -13133,7 +13167,7 @@ var FROM_ITRF2020 = {
13133
13167
  [0, 0, 0.02],
13134
13168
  2015
13135
13169
  ),
13136
- ITRF88: H(
13170
+ ITRF88: H2(
13137
13171
  [24.5, -3.9, -169.9],
13138
13172
  11.47,
13139
13173
  [0.1, 0, 0.36],
@@ -13143,10 +13177,10 @@ var FROM_ITRF2020 = {
13143
13177
  2015
13144
13178
  ),
13145
13179
  /** ITRF2020 → ETRF2020, EPSG:10572 (epoch-1989 variant) */
13146
- ETRF2020: H(Z, 0, Z, Z, 0, [0.086, 0.519, -0.753], 1989)
13180
+ ETRF2020: H2(Z, 0, Z, Z, 0, [0.086, 0.519, -0.753], 1989)
13147
13181
  };
13148
- var ITRF2014_TO_ETRF2014 = H(Z, 0, Z, Z, 0, [0.085, 0.531, -0.77], 1989);
13149
- var ITRF2014_TO_ETRF2000 = H(
13182
+ var ITRF2014_TO_ETRF2014 = H2(Z, 0, Z, Z, 0, [0.085, 0.531, -0.77], 1989);
13183
+ var ITRF2014_TO_ETRF2000 = H2(
13150
13184
  [54.7, 52.2, -74.1],
13151
13185
  2.12,
13152
13186
  [1.701, 10.29, -16.632],
@@ -13155,7 +13189,7 @@ var ITRF2014_TO_ETRF2000 = H(
13155
13189
  [0.081, 0.49, -0.792],
13156
13190
  2010
13157
13191
  );
13158
- var ITRF2014_TO_NAD83_2011 = H(
13192
+ var ITRF2014_TO_NAD83_2011 = H2(
13159
13193
  [1005.3, -1909.21, -541.57],
13160
13194
  0.36891,
13161
13195
  [-26.78138, 0.42027, -10.93206],
@@ -15831,7 +15865,7 @@ function moonEcef(timeMs) {
15831
15865
  ];
15832
15866
  return eciToEcef(eci, gmst(timeMs));
15833
15867
  }
15834
- var H2 = 0.6078;
15868
+ var H22 = 0.6078;
15835
15869
  var L2 = 0.0847;
15836
15870
  var RE = 6378137;
15837
15871
  var GM_SUN_RATIO = 332946.0482;
@@ -15856,7 +15890,7 @@ function solidEarthTide(rcv, sun, moon) {
15856
15890
  ];
15857
15891
  const dot2 = rHat[0] * jHat[0] + rHat[1] * jHat[1] + rHat[2] * jHat[2];
15858
15892
  const factor = ratio * RE * RE * RE * RE / (rj * rj * rj);
15859
- const radialScale = H2 * (1.5 * dot2 * dot2 - 0.5);
15893
+ const radialScale = H22 * (1.5 * dot2 * dot2 - 0.5);
15860
15894
  const tanScale = 3 * L2 * dot2;
15861
15895
  for (let k = 0; k < 3; k++) {
15862
15896
  disp[k] += factor * (radialScale * rHat[k] + tanScale * (jHat[k] - dot2 * rHat[k]));
@@ -19516,6 +19550,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19516
19550
  DELTA_GLO_L2,
19517
19551
  DUAL_FREQ_PAIRS,
19518
19552
  EMPTY_WARNINGS,
19553
+ EPHEMERIS_VALIDITY_MS,
19519
19554
  FREQ,
19520
19555
  FREQ_BDS_B1A,
19521
19556
  FREQ_BDS_B1C,
@@ -19732,6 +19767,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19732
19767
  hdrLine,
19733
19768
  horizonDistance,
19734
19769
  ionoFree,
19770
+ isEphemerisValid,
19735
19771
  isSbasL1Preamble,
19736
19772
  isSbasL5Correction,
19737
19773
  isSsrMessage,
@@ -19744,6 +19780,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19744
19780
  meridianArc,
19745
19781
  moonEcef,
19746
19782
  navTimesFromEph,
19783
+ nearestValidEphemeris,
19747
19784
  niellMapping,
19748
19785
  nlWavelength,
19749
19786
  nmeaCoordToDecimal,
package/dist/index.d.cts CHANGED
@@ -17,7 +17,7 @@ export { DfmcCensus, DfmcMessageCb, GeoL5Result, SBAS_L5_MT_NAMES, SBAS_L5_PREAM
17
17
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, NovatelPvtResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelPvt, parseNovatelRange } from './novatel.cjs';
18
18
  export { TrimbleEpoch, TrimbleFrame, TrimbleMeasurement, TrimbleNavResult, TrimbleParseResult, parseTrimble, parseTrimbleNav, trimbleFrames } from './trimble.cjs';
19
19
  export { BinexEpoch, BinexMeasurement, BinexParseResult, BinexRecord, binexCrc16, binexCrc32, binexCsum8, binexRecords, decodeBinexEph, getBnxi, parseBinex } from './binex.cjs';
20
- 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';
20
+ export { AllPositionsData, AlmanacPosition, DopValues, EPHEMERIS_VALIDITY_MS, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, isEphemerisValid, keplerPosition, maskRadForAzimuth, navTimesFromEph, nearestValidEphemeris, rangeRate, selectEphemeris } from './orbit.cjs';
21
21
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
22
22
  export { AntennaOffset, AppliedOrbitClock, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, Degradation, DgnssOptions, DgnssSolution, EphemerisSource, FixedPositionResult, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, NetworkFcbResult, NetworkFcbSummary, NlArc, NlFcbResult, OrbitClockCorrection, PppAntennaModel, PppArc, PppCorrectionConfig, PppCorrections, PppEngine, PppEphemerisSource, PppEpoch, PppEpochContext, PppEpochResult, PppFixState, PppOptions, PppSatObs, PppSolution, RawObservation, ReceiverBias, ReceiverClockSample, RtkAntennaConfig, RtkAntennaModel, RtkAntennaOffset, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, RtkObsEpoch, RtkObsInput, RtkPostProcessOptions, RtkPostProcessResult, RtkReceiverAntenna, RtkTrackPoint, SatGeom, SatSample, SbasCorrectionSource, SbasIgp, SbasIonoDelay, SbasProcessor, SbasSatCorrection, Sp3EphemerisSource, SppOptions, SppSolution, TropoMapping, WlArc, WlFcbOptions, WlFcbResult, WlObs, ZeroBaselineEngine, ZeroBaselineOptions, antexFreqOfGroup, applyOrbitClock, buildPppAntenna, buildRtkAntenna, createPppCorrections, estimateNarrowlaneFcb, estimateNetworkFcbs, estimateWidelaneFcb, extractWidelaneArcs, fixPppPosition, gimSlantIonoDelayL1, gimVerticalTec, gmst, hasToOrbitClock, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, postProcessRtk, rcvAntennaRangeM, resolvePppAmbiguities, satClockCorrection, sbasIonoDeg, sbasLongTermDeg, sbasProtectionLevels, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength } from './positioning.cjs';
23
23
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { DfmcCensus, DfmcMessageCb, GeoL5Result, SBAS_L5_MT_NAMES, SBAS_L5_PREAM
17
17
  export { NovatelEpoch, NovatelMeasurement, NovatelNavResult, NovatelParseResult, NovatelPvtResult, OEM4_HLEN, Oem4Frame, crc32, oem4Frames, parseNovatelNav, parseNovatelPvt, parseNovatelRange } from './novatel.js';
18
18
  export { TrimbleEpoch, TrimbleFrame, TrimbleMeasurement, TrimbleNavResult, TrimbleParseResult, parseTrimble, parseTrimbleNav, trimbleFrames } from './trimble.js';
19
19
  export { BinexEpoch, BinexMeasurement, BinexParseResult, BinexRecord, binexCrc16, binexCrc32, binexCsum8, binexRecords, decodeBinexEph, getBnxi, parseBinex } from './binex.js';
20
- 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';
20
+ export { AllPositionsData, AlmanacPosition, DopValues, EPHEMERIS_VALIDITY_MS, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, isEphemerisValid, keplerPosition, maskRadForAzimuth, navTimesFromEph, nearestValidEphemeris, rangeRate, selectEphemeris } from './orbit.js';
21
21
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
22
22
  export { AntennaOffset, AppliedOrbitClock, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, Degradation, DgnssOptions, DgnssSolution, EphemerisSource, FixedPositionResult, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, NetworkFcbResult, NetworkFcbSummary, NlArc, NlFcbResult, OrbitClockCorrection, PppAntennaModel, PppArc, PppCorrectionConfig, PppCorrections, PppEngine, PppEphemerisSource, PppEpoch, PppEpochContext, PppEpochResult, PppFixState, PppOptions, PppSatObs, PppSolution, RawObservation, ReceiverBias, ReceiverClockSample, RtkAntennaConfig, RtkAntennaModel, RtkAntennaOffset, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, RtkObsEpoch, RtkObsInput, RtkPostProcessOptions, RtkPostProcessResult, RtkReceiverAntenna, RtkTrackPoint, SatGeom, SatSample, SbasCorrectionSource, SbasIgp, SbasIonoDelay, SbasProcessor, SbasSatCorrection, Sp3EphemerisSource, SppOptions, SppSolution, TropoMapping, WlArc, WlFcbOptions, WlFcbResult, WlObs, ZeroBaselineEngine, ZeroBaselineOptions, antexFreqOfGroup, applyOrbitClock, buildPppAntenna, buildRtkAntenna, createPppCorrections, estimateNarrowlaneFcb, estimateNetworkFcbs, estimateWidelaneFcb, extractWidelaneArcs, fixPppPosition, gimSlantIonoDelayL1, gimVerticalTec, gmst, hasToOrbitClock, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, postProcessRtk, rcvAntennaRangeM, resolvePppAmbiguities, satClockCorrection, sbasIonoDeg, sbasLongTermDeg, sbasProtectionLevels, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength } from './positioning.js';
23
23
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
package/dist/index.js CHANGED
@@ -213,9 +213,10 @@ import {
213
213
  sunEcef,
214
214
  toRtkEpoch,
215
215
  wlWavelength
216
- } from "./chunk-F6KOFOXN.js";
216
+ } from "./chunk-NOGA5KD7.js";
217
217
  import "./chunk-WQX354KD.js";
218
218
  import {
219
+ EPHEMERIS_VALIDITY_MS,
219
220
  almanacEpochMs,
220
221
  almanacSatPosition,
221
222
  computeAllPositions,
@@ -230,12 +231,14 @@ import {
230
231
  glonassAlmanacEpochMs,
231
232
  glonassAlmanacPosition,
232
233
  glonassPosition,
234
+ isEphemerisValid,
233
235
  keplerPosition,
234
236
  maskRadForAzimuth,
235
237
  navTimesFromEph,
238
+ nearestValidEphemeris,
236
239
  rangeRate,
237
240
  selectEphemeris
238
- } from "./chunk-T5M3LVBJ.js";
241
+ } from "./chunk-7T5DLVZL.js";
239
242
  import {
240
243
  getBdsTime,
241
244
  getDateFromBdsTime,
@@ -415,6 +418,7 @@ export {
415
418
  DELTA_GLO_L2,
416
419
  DUAL_FREQ_PAIRS,
417
420
  EMPTY_WARNINGS,
421
+ EPHEMERIS_VALIDITY_MS,
418
422
  FREQ,
419
423
  FREQ_BDS_B1A,
420
424
  FREQ_BDS_B1C,
@@ -631,6 +635,7 @@ export {
631
635
  hdrLine,
632
636
  horizonDistance,
633
637
  ionoFree,
638
+ isEphemerisValid,
634
639
  isSbasL1Preamble,
635
640
  isSbasL5Correction,
636
641
  isSsrMessage,
@@ -643,6 +648,7 @@ export {
643
648
  meridianArc,
644
649
  moonEcef,
645
650
  navTimesFromEph,
651
+ nearestValidEphemeris,
646
652
  niellMapping,
647
653
  nlWavelength,
648
654
  nmeaCoordToDecimal,
package/dist/orbit.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/orbit/index.ts
21
21
  var orbit_exports = {};
22
22
  __export(orbit_exports, {
23
+ EPHEMERIS_VALIDITY_MS: () => EPHEMERIS_VALIDITY_MS,
23
24
  almanacEpochMs: () => almanacEpochMs,
24
25
  almanacSatPosition: () => almanacSatPosition,
25
26
  computeAllPositions: () => computeAllPositions,
@@ -36,9 +37,11 @@ __export(orbit_exports, {
36
37
  glonassAlmanacEpochMs: () => glonassAlmanacEpochMs,
37
38
  glonassAlmanacPosition: () => glonassAlmanacPosition,
38
39
  glonassPosition: () => glonassPosition,
40
+ isEphemerisValid: () => isEphemerisValid,
39
41
  keplerPosition: () => keplerPosition,
40
42
  maskRadForAzimuth: () => maskRadForAzimuth,
41
43
  navTimesFromEph: () => navTimesFromEph,
44
+ nearestValidEphemeris: () => nearestValidEphemeris,
42
45
  rangeRate: () => rangeRate,
43
46
  selectEphemeris: () => selectEphemeris
44
47
  });
@@ -198,6 +201,42 @@ function invertMatrix(A) {
198
201
  return M.map((row) => row.slice(n));
199
202
  }
200
203
 
204
+ // src/orbit/validity.ts
205
+ var H = 36e5;
206
+ var MIN = 6e4;
207
+ var EPHEMERIS_VALIDITY_MS = {
208
+ G: 2 * H,
209
+ J: 2 * H,
210
+ E: 2.5 * H,
211
+ C: 90 * MIN,
212
+ R: 15 * MIN,
213
+ I: 2 * H,
214
+ S: 6 * MIN
215
+ };
216
+ function tocDistanceMs(eph, tGpsMs) {
217
+ const t = eph.system === "R" || eph.system === "S" ? tGpsMs - getGpsLeap(new Date(tGpsMs)) * 1e3 : tGpsMs;
218
+ return Math.abs(eph.tocDate.getTime() - t);
219
+ }
220
+ function isEphemerisValid(eph, tGpsMs) {
221
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
222
+ return w !== void 0 && tocDistanceMs(eph, tGpsMs) <= w;
223
+ }
224
+ function nearestValidEphemeris(ephs, tGpsMs) {
225
+ if (!ephs) return null;
226
+ let best = null;
227
+ let bestDt = Infinity;
228
+ for (const eph of ephs) {
229
+ const dt = tocDistanceMs(eph, tGpsMs);
230
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
231
+ if (w === void 0 || dt > w) continue;
232
+ if (dt < bestDt) {
233
+ bestDt = dt;
234
+ best = eph;
235
+ }
236
+ }
237
+ return best;
238
+ }
239
+
201
240
  // src/orbit/almanac.ts
202
241
  var GM_GPS = 3986005e8;
203
242
  var GM_GAL = 3986004418e5;
@@ -570,24 +609,30 @@ function dopClock(prn) {
570
609
  return sys === "J" || sys === "S" ? "G" : sys;
571
610
  }
572
611
  function computeDop(satAzEls) {
573
- const n = satAzEls.length;
574
- const clocks = [...new Set(satAzEls.map((s) => dopClock(s.prn)))];
612
+ const perClock = /* @__PURE__ */ new Map();
613
+ for (const s of satAzEls) {
614
+ const c = dopClock(s.prn);
615
+ perClock.set(c, (perClock.get(c) ?? 0) + 1);
616
+ }
617
+ const sats = perClock.size > 1 ? satAzEls.filter((s) => perClock.get(dopClock(s.prn)) > 1) : satAzEls;
618
+ const n = sats.length;
619
+ const clocks = [...new Set(sats.map((s) => dopClock(s.prn)))];
575
620
  const m = 3 + clocks.length;
576
621
  if (n < Math.max(4, m)) return null;
577
- const H = [];
578
- for (const { az, el, prn } of satAzEls) {
622
+ const H2 = [];
623
+ for (const { az, el, prn } of sats) {
579
624
  const cosEl = Math.cos(el);
580
625
  const row = [cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el)];
581
626
  const clock = dopClock(prn);
582
627
  for (const c of clocks) row.push(c === clock ? 1 : 0);
583
- H.push(row);
628
+ H2.push(row);
584
629
  }
585
630
  const HtH = Array.from({ length: m }, () => new Array(m).fill(0));
586
631
  for (let i = 0; i < m; i++) {
587
632
  for (let j = 0; j < m; j++) {
588
633
  let sum = 0;
589
634
  for (let k = 0; k < n; k++) {
590
- sum += H[k][i] * H[k][j];
635
+ sum += H2[k][i] * H2[k][j];
591
636
  }
592
637
  HtH[i][j] = sum;
593
638
  }
@@ -688,18 +733,7 @@ function computeAllPositions(ephemerides, times, rxPos) {
688
733
  return { prns, times, positions };
689
734
  }
690
735
  function selectBest(ephs, timeMs) {
691
- let best = null;
692
- let bestDt = Infinity;
693
- for (const eph of ephs) {
694
- const dt = Math.abs(timeMs - eph.tocDate.getTime());
695
- const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
696
- if (dt > maxAge) continue;
697
- if (dt < bestDt) {
698
- bestDt = dt;
699
- best = eph;
700
- }
701
- }
702
- return best;
736
+ return nearestValidEphemeris(ephs, timeMs);
703
737
  }
704
738
  var GPS_EPOCH_MS2 = START_GPS_TIME.getTime();
705
739
  var MS_PER_WEEK = 7 * 864e5;
@@ -940,6 +974,7 @@ function computeVisibilityFromPositions(all, elevationMaskDeg = 10) {
940
974
  }
941
975
  // Annotate the CommonJS export names for ESM import in node:
942
976
  0 && (module.exports = {
977
+ EPHEMERIS_VALIDITY_MS,
943
978
  almanacEpochMs,
944
979
  almanacSatPosition,
945
980
  computeAllPositions,
@@ -956,9 +991,11 @@ function computeVisibilityFromPositions(all, elevationMaskDeg = 10) {
956
991
  glonassAlmanacEpochMs,
957
992
  glonassAlmanacPosition,
958
993
  glonassPosition,
994
+ isEphemerisValid,
959
995
  keplerPosition,
960
996
  maskRadForAzimuth,
961
997
  navTimesFromEph,
998
+ nearestValidEphemeris,
962
999
  rangeRate,
963
1000
  selectEphemeris
964
1001
  });
package/dist/orbit.d.cts CHANGED
@@ -91,6 +91,40 @@ declare function glonassAlmanacEpochMs(alm: SbfGlonassAlmanac): number;
91
91
  */
92
92
  declare function glonassAlmanacPosition(alm: SbfGlonassAlmanac, timeMs: number): AlmanacPosition;
93
93
 
94
+ /**
95
+ * Broadcast-ephemeris validity — whether an ephemeris may position its
96
+ * satellite at a given epoch, and the nearest one that may.
97
+ *
98
+ * Each system's half-window |t − toc| follows its broadcast design, never
99
+ * wider than the documented validity:
100
+ * - GPS / QZSS: the 4-h curve fit centred on toe → ±2 h (IS-GPS-200,
101
+ * IS-QZSS; RTKLIB MAXDTOE).
102
+ * - Galileo: 2.5 h, inside the 4-h nominal validity (RTKLIB MAXDTOE_GAL).
103
+ * - BeiDou: 90 min — ephemerides are refreshed hourly.
104
+ * - GLONASS: the immediate data cover a 30-min interval centred on tb →
105
+ * ±15 min.
106
+ * - NavIC: ±2 h (2-h update cycle).
107
+ * - SBAS GEO (MT9 state vector): 6 min (RTKLIB MAXDTOE_SBS) — a GEO
108
+ * extrapolated from an hours-old state vector is not an orbit.
109
+ * Any other system is never valid.
110
+ *
111
+ * Time: `tGpsMs` is GPS-scale epoch ms, like everywhere in this module.
112
+ * GLONASS and SBAS tocs are on the UTC axis (see {@link computeSatPosition}),
113
+ * so for those t is moved to UTC first — on a ±15-min window the 18-s
114
+ * leap-second offset matters.
115
+ */
116
+
117
+ /** Validity half-window |t − toc| per system letter, in ms. */
118
+ declare const EPHEMERIS_VALIDITY_MS: Readonly<Record<string, number>>;
119
+ /** May `eph` position its satellite at GPS-scale epoch `tGpsMs`? */
120
+ declare function isEphemerisValid(eph: Ephemeris, tGpsMs: number): boolean;
121
+ /**
122
+ * The ephemeris in `ephs` whose toc is nearest to `tGpsMs` among those valid
123
+ * at `tGpsMs`, or null when none is. `ephs` may mix satellites; filter by PRN
124
+ * first if it does.
125
+ */
126
+ declare function nearestValidEphemeris(ephs: readonly Ephemeris[] | undefined, tGpsMs: number): Ephemeris | null;
127
+
94
128
  /**
95
129
  * Satellite orbit computation from broadcast ephemerides.
96
130
  * Supports GPS, Galileo, BeiDou (Keplerian) and GLONASS (Runge-Kutta).
@@ -164,8 +198,11 @@ declare function ecefToAzEl(rxX: number, rxY: number, rxZ: number, satX: number,
164
198
  * constellation time scale, as a multi-GNSS receiver solves: every extra
165
199
  * time scale costs one observation. QZSS and SBAS share the GPS clock —
166
200
  * QZSST and SBAS network time are steered to GPST (RTKLIB pntpos does the
167
- * same) — so a lone SBAS GEO doesn't inflate TDOP/GDOP. Untagged
168
- * satellites share a single clock (the classic single-system model).
201
+ * same) — so a lone SBAS GEO doesn't inflate TDOP/GDOP. A time scale
202
+ * with a single satellite in view is left out: that satellite only fixes
203
+ * its own clock, adding nothing to PDOP/HDOP/VDOP and an unrecoverable ~1
204
+ * to TDOP². Untagged satellites share a single clock (the classic
205
+ * single-system model).
169
206
  *
170
207
  * @param satAzEls Satellite azimuth/elevation pairs (radians), optionally
171
208
  * tagged with their PRN.
@@ -178,11 +215,12 @@ declare function computeDop(satAzEls: {
178
215
  prn?: string;
179
216
  }[]): DopValues | null;
180
217
  /**
181
- * Find the best ephemeris for a given PRN at a given time.
182
- * Selects the one with the closest epoch (toe/toc) to the target time.
218
+ * Find the best ephemeris for a given PRN at a given time: the one with the
219
+ * closest epoch (toe/toc) among those within their broadcast validity at
220
+ * that time ({@link isEphemerisValid}); null when none is valid.
183
221
  * @param ephemerides Array of candidate ephemerides
184
222
  * @param prn Satellite PRN identifier (e.g. "G01")
185
- * @param timeMs Target time in Unix milliseconds
223
+ * @param timeMs Target time, GPS-scale epoch milliseconds
186
224
  */
187
225
  declare function selectEphemeris(ephemerides: Ephemeris[], prn: string, timeMs: number): Ephemeris | null;
188
226
  /**
@@ -300,4 +338,4 @@ declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, num
300
338
  */
301
339
  declare function computeVisibilityFromPositions(all: AllPositionsData, elevationMaskDeg?: number | number[]): VisibilityResult;
302
340
 
303
- export { type AllPositionsData, type AlmanacPosition, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris };
341
+ export { type AllPositionsData, type AlmanacPosition, type DopValues, EPHEMERIS_VALIDITY_MS, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, isEphemerisValid, keplerPosition, maskRadForAzimuth, navTimesFromEph, nearestValidEphemeris, rangeRate, selectEphemeris };
package/dist/orbit.d.ts CHANGED
@@ -91,6 +91,40 @@ declare function glonassAlmanacEpochMs(alm: SbfGlonassAlmanac): number;
91
91
  */
92
92
  declare function glonassAlmanacPosition(alm: SbfGlonassAlmanac, timeMs: number): AlmanacPosition;
93
93
 
94
+ /**
95
+ * Broadcast-ephemeris validity — whether an ephemeris may position its
96
+ * satellite at a given epoch, and the nearest one that may.
97
+ *
98
+ * Each system's half-window |t − toc| follows its broadcast design, never
99
+ * wider than the documented validity:
100
+ * - GPS / QZSS: the 4-h curve fit centred on toe → ±2 h (IS-GPS-200,
101
+ * IS-QZSS; RTKLIB MAXDTOE).
102
+ * - Galileo: 2.5 h, inside the 4-h nominal validity (RTKLIB MAXDTOE_GAL).
103
+ * - BeiDou: 90 min — ephemerides are refreshed hourly.
104
+ * - GLONASS: the immediate data cover a 30-min interval centred on tb →
105
+ * ±15 min.
106
+ * - NavIC: ±2 h (2-h update cycle).
107
+ * - SBAS GEO (MT9 state vector): 6 min (RTKLIB MAXDTOE_SBS) — a GEO
108
+ * extrapolated from an hours-old state vector is not an orbit.
109
+ * Any other system is never valid.
110
+ *
111
+ * Time: `tGpsMs` is GPS-scale epoch ms, like everywhere in this module.
112
+ * GLONASS and SBAS tocs are on the UTC axis (see {@link computeSatPosition}),
113
+ * so for those t is moved to UTC first — on a ±15-min window the 18-s
114
+ * leap-second offset matters.
115
+ */
116
+
117
+ /** Validity half-window |t − toc| per system letter, in ms. */
118
+ declare const EPHEMERIS_VALIDITY_MS: Readonly<Record<string, number>>;
119
+ /** May `eph` position its satellite at GPS-scale epoch `tGpsMs`? */
120
+ declare function isEphemerisValid(eph: Ephemeris, tGpsMs: number): boolean;
121
+ /**
122
+ * The ephemeris in `ephs` whose toc is nearest to `tGpsMs` among those valid
123
+ * at `tGpsMs`, or null when none is. `ephs` may mix satellites; filter by PRN
124
+ * first if it does.
125
+ */
126
+ declare function nearestValidEphemeris(ephs: readonly Ephemeris[] | undefined, tGpsMs: number): Ephemeris | null;
127
+
94
128
  /**
95
129
  * Satellite orbit computation from broadcast ephemerides.
96
130
  * Supports GPS, Galileo, BeiDou (Keplerian) and GLONASS (Runge-Kutta).
@@ -164,8 +198,11 @@ declare function ecefToAzEl(rxX: number, rxY: number, rxZ: number, satX: number,
164
198
  * constellation time scale, as a multi-GNSS receiver solves: every extra
165
199
  * time scale costs one observation. QZSS and SBAS share the GPS clock —
166
200
  * QZSST and SBAS network time are steered to GPST (RTKLIB pntpos does the
167
- * same) — so a lone SBAS GEO doesn't inflate TDOP/GDOP. Untagged
168
- * satellites share a single clock (the classic single-system model).
201
+ * same) — so a lone SBAS GEO doesn't inflate TDOP/GDOP. A time scale
202
+ * with a single satellite in view is left out: that satellite only fixes
203
+ * its own clock, adding nothing to PDOP/HDOP/VDOP and an unrecoverable ~1
204
+ * to TDOP². Untagged satellites share a single clock (the classic
205
+ * single-system model).
169
206
  *
170
207
  * @param satAzEls Satellite azimuth/elevation pairs (radians), optionally
171
208
  * tagged with their PRN.
@@ -178,11 +215,12 @@ declare function computeDop(satAzEls: {
178
215
  prn?: string;
179
216
  }[]): DopValues | null;
180
217
  /**
181
- * Find the best ephemeris for a given PRN at a given time.
182
- * Selects the one with the closest epoch (toe/toc) to the target time.
218
+ * Find the best ephemeris for a given PRN at a given time: the one with the
219
+ * closest epoch (toe/toc) among those within their broadcast validity at
220
+ * that time ({@link isEphemerisValid}); null when none is valid.
183
221
  * @param ephemerides Array of candidate ephemerides
184
222
  * @param prn Satellite PRN identifier (e.g. "G01")
185
- * @param timeMs Target time in Unix milliseconds
223
+ * @param timeMs Target time, GPS-scale epoch milliseconds
186
224
  */
187
225
  declare function selectEphemeris(ephemerides: Ephemeris[], prn: string, timeMs: number): Ephemeris | null;
188
226
  /**
@@ -300,4 +338,4 @@ declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, num
300
338
  */
301
339
  declare function computeVisibilityFromPositions(all: AllPositionsData, elevationMaskDeg?: number | number[]): VisibilityResult;
302
340
 
303
- export { type AllPositionsData, type AlmanacPosition, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris };
341
+ export { type AllPositionsData, type AlmanacPosition, type DopValues, EPHEMERIS_VALIDITY_MS, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, isEphemerisValid, keplerPosition, maskRadForAzimuth, navTimesFromEph, nearestValidEphemeris, rangeRate, selectEphemeris };
package/dist/orbit.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ EPHEMERIS_VALIDITY_MS,
2
3
  almanacEpochMs,
3
4
  almanacSatPosition,
4
5
  computeAllPositions,
@@ -13,12 +14,14 @@ import {
13
14
  glonassAlmanacEpochMs,
14
15
  glonassAlmanacPosition,
15
16
  glonassPosition,
17
+ isEphemerisValid,
16
18
  keplerPosition,
17
19
  maskRadForAzimuth,
18
20
  navTimesFromEph,
21
+ nearestValidEphemeris,
19
22
  rangeRate,
20
23
  selectEphemeris
21
- } from "./chunk-T5M3LVBJ.js";
24
+ } from "./chunk-7T5DLVZL.js";
22
25
  import "./chunk-HVXYFUCB.js";
23
26
  import {
24
27
  ecefToGeodetic,
@@ -28,6 +31,7 @@ import "./chunk-LEEU5OIO.js";
28
31
  import "./chunk-6FAL6P4G.js";
29
32
  import "./chunk-FIEWO4J4.js";
30
33
  export {
34
+ EPHEMERIS_VALIDITY_MS,
31
35
  almanacEpochMs,
32
36
  almanacSatPosition,
33
37
  computeAllPositions,
@@ -44,9 +48,11 @@ export {
44
48
  glonassAlmanacEpochMs,
45
49
  glonassAlmanacPosition,
46
50
  glonassPosition,
51
+ isEphemerisValid,
47
52
  keplerPosition,
48
53
  maskRadForAzimuth,
49
54
  navTimesFromEph,
55
+ nearestValidEphemeris,
50
56
  rangeRate,
51
57
  selectEphemeris
52
58
  };
@@ -254,6 +254,38 @@ function invertMatrix(A) {
254
254
  return M.map((row) => row.slice(n));
255
255
  }
256
256
 
257
+ // src/orbit/validity.ts
258
+ var H = 36e5;
259
+ var MIN = 6e4;
260
+ var EPHEMERIS_VALIDITY_MS = {
261
+ G: 2 * H,
262
+ J: 2 * H,
263
+ E: 2.5 * H,
264
+ C: 90 * MIN,
265
+ R: 15 * MIN,
266
+ I: 2 * H,
267
+ S: 6 * MIN
268
+ };
269
+ function tocDistanceMs(eph, tGpsMs) {
270
+ const t = eph.system === "R" || eph.system === "S" ? tGpsMs - getGpsLeap(new Date(tGpsMs)) * 1e3 : tGpsMs;
271
+ return Math.abs(eph.tocDate.getTime() - t);
272
+ }
273
+ function nearestValidEphemeris(ephs, tGpsMs) {
274
+ if (!ephs) return null;
275
+ let best = null;
276
+ let bestDt = Infinity;
277
+ for (const eph of ephs) {
278
+ const dt = tocDistanceMs(eph, tGpsMs);
279
+ const w = EPHEMERIS_VALIDITY_MS[eph.system];
280
+ if (w === void 0 || dt > w) continue;
281
+ if (dt < bestDt) {
282
+ bestDt = dt;
283
+ best = eph;
284
+ }
285
+ }
286
+ return best;
287
+ }
288
+
257
289
  // src/orbit/index.ts
258
290
  var GM_GPS = 3986005e8;
259
291
  var GM_GAL = 3986004418e5;
@@ -442,24 +474,30 @@ function dopClock(prn) {
442
474
  return sys === "J" || sys === "S" ? "G" : sys;
443
475
  }
444
476
  function computeDop(satAzEls) {
445
- const n = satAzEls.length;
446
- const clocks = [...new Set(satAzEls.map((s) => dopClock(s.prn)))];
477
+ const perClock = /* @__PURE__ */ new Map();
478
+ for (const s of satAzEls) {
479
+ const c = dopClock(s.prn);
480
+ perClock.set(c, (perClock.get(c) ?? 0) + 1);
481
+ }
482
+ const sats = perClock.size > 1 ? satAzEls.filter((s) => perClock.get(dopClock(s.prn)) > 1) : satAzEls;
483
+ const n = sats.length;
484
+ const clocks = [...new Set(sats.map((s) => dopClock(s.prn)))];
447
485
  const m = 3 + clocks.length;
448
486
  if (n < Math.max(4, m)) return null;
449
- const H = [];
450
- for (const { az, el, prn } of satAzEls) {
487
+ const H3 = [];
488
+ for (const { az, el, prn } of sats) {
451
489
  const cosEl = Math.cos(el);
452
490
  const row = [cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el)];
453
491
  const clock = dopClock(prn);
454
492
  for (const c of clocks) row.push(c === clock ? 1 : 0);
455
- H.push(row);
493
+ H3.push(row);
456
494
  }
457
495
  const HtH = Array.from({ length: m }, () => new Array(m).fill(0));
458
496
  for (let i = 0; i < m; i++) {
459
497
  for (let j = 0; j < m; j++) {
460
498
  let sum = 0;
461
499
  for (let k = 0; k < n; k++) {
462
- sum += H[k][i] * H[k][j];
500
+ sum += H3[k][i] * H3[k][j];
463
501
  }
464
502
  HtH[i][j] = sum;
465
503
  }
@@ -495,18 +533,7 @@ function computeSatPosition(eph, timeMs) {
495
533
  return keplerPosition(eph, tow);
496
534
  }
497
535
  function selectBest(ephs, timeMs) {
498
- let best = null;
499
- let bestDt = Infinity;
500
- for (const eph of ephs) {
501
- const dt = Math.abs(timeMs - eph.tocDate.getTime());
502
- const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
503
- if (dt > maxAge) continue;
504
- if (dt < bestDt) {
505
- bestDt = dt;
506
- best = eph;
507
- }
508
- }
509
- return best;
536
+ return nearestValidEphemeris(ephs, timeMs);
510
537
  }
511
538
  var GPS_EPOCH_MS = START_GPS_TIME.getTime();
512
539
  var MS_PER_WEEK = 7 * 864e5;
@@ -1302,7 +1329,7 @@ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
1302
1329
  let cov = null;
1303
1330
  for (let iter = 0; iter < maxIterations; iter++) {
1304
1331
  iterations = iter + 1;
1305
- const H = [];
1332
+ const H3 = [];
1306
1333
  const v = [];
1307
1334
  const terms = /* @__PURE__ */ new Map();
1308
1335
  const termOf = (g) => {
@@ -1318,11 +1345,11 @@ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
1318
1345
  const tr = termOf(row.ref);
1319
1346
  const pred = ts.rho - row.g.rhoB + ts.dTropo - (tr.rho - row.ref.rhoB + tr.dTropo);
1320
1347
  v.push(row.z - pred);
1321
- H.push([ts.u[0] - tr.u[0], ts.u[1] - tr.u[1], ts.u[2] - tr.u[2]]);
1348
+ H3.push([ts.u[0] - tr.u[0], ts.u[1] - tr.u[1], ts.u[2] - tr.u[2]]);
1322
1349
  }
1323
- const Ht = transpose(H);
1350
+ const Ht = transpose(H3);
1324
1351
  const HtW = matMul(Ht, W);
1325
- const N = matMul(HtW, H);
1352
+ const N = matMul(HtW, H3);
1326
1353
  const b = matMul(
1327
1354
  HtW,
1328
1355
  v.map((s) => [s])
@@ -1338,7 +1365,7 @@ function solveDgnss(rover, base, basePos, ephemerides, timeMs, opts = {}) {
1338
1365
  z += dx[2][0];
1339
1366
  cov = Ninv;
1340
1367
  rows.forEach((row, i) => {
1341
- residuals[row.g.prn] = v[i] - (H[i][0] * dx[0][0] + H[i][1] * dx[1][0] + H[i][2] * dx[2][0]);
1368
+ residuals[row.g.prn] = v[i] - (H3[i][0] * dx[0][0] + H3[i][1] * dx[1][0] + H3[i][2] * dx[2][0]);
1342
1369
  });
1343
1370
  if (Math.hypot(dx[0][0], dx[1][0], dx[2][0]) < convergenceM) {
1344
1371
  converged = true;
@@ -1732,19 +1759,19 @@ var RtkFloatEngine = class {
1732
1759
  const PPrior = this.P;
1733
1760
  let xi = [...this.x];
1734
1761
  let K = [];
1735
- let H = [];
1762
+ let H3 = [];
1736
1763
  for (let it = 0; it < Math.max(o.updateIterations, 1); it++) {
1737
- H = [];
1764
+ H3 = [];
1738
1765
  const v = [];
1739
1766
  for (const row of rows) {
1740
1767
  const { pred, h } = predict(row, xi);
1741
1768
  let corr = 0;
1742
1769
  for (let j = 0; j < n; j++) corr += h[j] * (xPrior[j] - xi[j]);
1743
1770
  v.push(row.z - pred - corr);
1744
- H.push(h);
1771
+ H3.push(h);
1745
1772
  }
1746
- const Ht = transpose(H);
1747
- const S = matMul(matMul(H, PPrior), Ht);
1773
+ const Ht = transpose(H3);
1774
+ const S = matMul(matMul(H3, PPrior), Ht);
1748
1775
  for (let i = 0; i < rows.length; i++)
1749
1776
  for (let j = 0; j < rows.length; j++) S[i][j] += R[i][j];
1750
1777
  const Sinv = matInv(S);
@@ -1760,7 +1787,7 @@ var RtkFloatEngine = class {
1760
1787
  xi = xPrior.map((s, i) => s + dx[i][0]);
1761
1788
  }
1762
1789
  this.x = xi;
1763
- const KH = matMul(K, H);
1790
+ const KH = matMul(K, H3);
1764
1791
  const IKH = zeros(n, n);
1765
1792
  for (let i = 0; i < n; i++)
1766
1793
  for (let j = 0; j < n; j++) IKH[i][j] = (i === j ? 1 : 0) - KH[i][j];
@@ -40,9 +40,9 @@ import {
40
40
  sunEcef,
41
41
  toRtkEpoch,
42
42
  wlWavelength
43
- } from "./chunk-F6KOFOXN.js";
43
+ } from "./chunk-NOGA5KD7.js";
44
44
  import "./chunk-WQX354KD.js";
45
- import "./chunk-T5M3LVBJ.js";
45
+ import "./chunk-7T5DLVZL.js";
46
46
  import "./chunk-HVXYFUCB.js";
47
47
  import "./chunk-CVCTRIWU.js";
48
48
  import "./chunk-37QNKGTC.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "2.8.1",
3
+ "version": "2.9.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,