gnss-js 2.8.2 → 2.10.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.
@@ -1566,6 +1566,21 @@ function writeRinexObsBlob(header, epochs, obsTypes) {
1566
1566
  comment: "Merged from multiple RINEX files"
1567
1567
  });
1568
1568
  }
1569
+ function modalIntervalS(epochs) {
1570
+ const counts = /* @__PURE__ */ new Map();
1571
+ for (let i = 1; i < epochs.length; i++) {
1572
+ const d = Math.round(epochs[i].time - epochs[i - 1].time);
1573
+ if (d > 0) counts.set(d, (counts.get(d) ?? 0) + 1);
1574
+ }
1575
+ let best = null;
1576
+ let bestN = 0;
1577
+ for (const [d, n] of counts)
1578
+ if (n > bestN || n === bestN && best !== null && d < best) {
1579
+ best = d;
1580
+ bestN = n;
1581
+ }
1582
+ return best === null ? null : best / 1e3;
1583
+ }
1569
1584
  async function writeModernObsBlob(header, epochs, obsTypes, opts) {
1570
1585
  if (epochs.length === 0) return new Blob([], { type: "application/gzip" });
1571
1586
  const sink = createGzipLineSink();
@@ -1612,11 +1627,9 @@ async function writeModernObsBlob(header, epochs, obsTypes, opts) {
1612
1627
  "TIME OF LAST OBS"
1613
1628
  )
1614
1629
  );
1615
- if (epochs.length >= 2) {
1616
- const interval = (epochs[1].time - epochs[0].time) / 1e3;
1617
- if (interval > 0 && interval < 3600) {
1618
- sink.push(hdrLine(fmtF(interval, 10, 3), "INTERVAL"));
1619
- }
1630
+ const interval = modalIntervalS(epochs);
1631
+ if (interval !== null && interval < 3600) {
1632
+ sink.push(hdrLine(fmtF(interval, 10, 3), "INTERVAL"));
1620
1633
  }
1621
1634
  if (header.glonassSlots && Object.keys(header.glonassSlots).length > 0) {
1622
1635
  const entries = Object.entries(header.glonassSlots).sort(
@@ -1888,6 +1901,7 @@ export {
1888
1901
  createGzipLineSink,
1889
1902
  stationHeaderLines,
1890
1903
  writeRinexObsBlob,
1904
+ modalIntervalS,
1891
1905
  writeModernObsBlob,
1892
1906
  writeRinex2ObsBlob,
1893
1907
  writeRinex4ObsBlob
@@ -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;
@@ -419,20 +455,20 @@ function computeDop(satAzEls) {
419
455
  const clocks = [...new Set(sats.map((s) => dopClock(s.prn)))];
420
456
  const m = 3 + clocks.length;
421
457
  if (n < Math.max(4, m)) return null;
422
- const H = [];
458
+ const H2 = [];
423
459
  for (const { az, el, prn } of sats) {
424
460
  const cosEl = Math.cos(el);
425
461
  const row = [cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el)];
426
462
  const clock = dopClock(prn);
427
463
  for (const c of clocks) row.push(c === clock ? 1 : 0);
428
- H.push(row);
464
+ H2.push(row);
429
465
  }
430
466
  const HtH = Array.from({ length: m }, () => new Array(m).fill(0));
431
467
  for (let i = 0; i < m; i++) {
432
468
  for (let j = 0; j < m; j++) {
433
469
  let sum = 0;
434
470
  for (let k = 0; k < n; k++) {
435
- sum += H[k][i] * H[k][j];
471
+ sum += H2[k][i] * H2[k][j];
436
472
  }
437
473
  HtH[i][j] = sum;
438
474
  }
@@ -533,18 +569,7 @@ function computeAllPositions(ephemerides, times, rxPos) {
533
569
  return { prns, times, positions };
534
570
  }
535
571
  function selectBest(ephs, timeMs) {
536
- let best = null;
537
- let bestDt = Infinity;
538
- for (const eph of ephs) {
539
- const dt = Math.abs(timeMs - eph.tocDate.getTime());
540
- const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
541
- if (dt > maxAge) continue;
542
- if (dt < bestDt) {
543
- bestDt = dt;
544
- best = eph;
545
- }
546
- }
547
- return best;
572
+ return nearestValidEphemeris(ephs, timeMs);
548
573
  }
549
574
  var GPS_EPOCH_MS2 = START_GPS_TIME.getTime();
550
575
  var MS_PER_WEEK = 7 * 864e5;
@@ -786,6 +811,9 @@ function computeVisibilityFromPositions(all, elevationMaskDeg = 10) {
786
811
 
787
812
  export {
788
813
  invertMatrix,
814
+ EPHEMERIS_VALIDITY_MS,
815
+ isEphemerisValid,
816
+ nearestValidEphemeris,
789
817
  almanacEpochMs,
790
818
  almanacSatPosition,
791
819
  glonassAlmanacEpochMs,
@@ -7,7 +7,7 @@ import {
7
7
  ecefToAzEl,
8
8
  invertMatrix,
9
9
  selectEphemeris
10
- } from "./chunk-5OWRCF56.js";
10
+ } from "./chunk-7T5DLVZL.js";
11
11
  import {
12
12
  ecefToGeodetic,
13
13
  getAer,
@@ -1621,6 +1621,66 @@ var ZeroBaselineEngine = class {
1621
1621
  }
1622
1622
  };
1623
1623
 
1624
+ // src/positioning/msm-resolve.ts
1625
+ var LIGHT_MS = C_LIGHT / 1e3;
1626
+ var MAX_FRACTION_MISFIT = 0.25;
1627
+ function predictedRange(eph, timeMs, rx) {
1628
+ let tau = 0.075;
1629
+ for (let i = 0; i < 2; i++) {
1630
+ const tEmit = timeMs - tau * 1e3;
1631
+ const p = computeSatPosition(eph, tEmit);
1632
+ if (!Number.isFinite(p.x)) return null;
1633
+ const rho = Math.hypot(p.x - rx[0], p.y - rx[1], p.z - rx[2]);
1634
+ tau = rho / C_LIGHT;
1635
+ if (i === 1) return rho - C_LIGHT * satClockCorrection(eph, tEmit);
1636
+ }
1637
+ return null;
1638
+ }
1639
+ function moduloRange(sat) {
1640
+ for (const s of sat.signals)
1641
+ if (s.pseudorange !== void 0) return s.pseudorange;
1642
+ for (const s of sat.signals)
1643
+ if (s.phase !== void 0 && s.wavelength > 0)
1644
+ return s.phase * s.wavelength;
1645
+ return null;
1646
+ }
1647
+ function resolveMsmRanges(epoch, opts) {
1648
+ if (!epoch.rangeModulo1ms) return epoch;
1649
+ const cand = [];
1650
+ for (const sat of epoch.observations) {
1651
+ const eph = opts.ephFor(sat.prn);
1652
+ const mod = moduloRange(sat);
1653
+ if (!eph || mod === null) continue;
1654
+ const pred = predictedRange(eph, opts.timeMs, opts.rxEcef);
1655
+ if (pred === null) continue;
1656
+ cand.push({ sat, k: (pred - mod) / LIGHT_MS });
1657
+ }
1658
+ const { rangeModulo1ms: _drop, ...rest } = epoch;
1659
+ if (cand.length === 0) return { ...rest, observations: [] };
1660
+ let sx = 0;
1661
+ let sy = 0;
1662
+ for (const c of cand) {
1663
+ sx += Math.cos(2 * Math.PI * c.k);
1664
+ sy += Math.sin(2 * Math.PI * c.k);
1665
+ }
1666
+ const b = Math.atan2(sy, sx) / (2 * Math.PI);
1667
+ const observations = [];
1668
+ for (const { sat, k } of cand) {
1669
+ const n = Math.round(k - b);
1670
+ if (Math.abs(k - b - n) > MAX_FRACTION_MISFIT) continue;
1671
+ const add = n * LIGHT_MS;
1672
+ observations.push({
1673
+ ...sat,
1674
+ signals: sat.signals.map((s) => ({
1675
+ ...s,
1676
+ ...s.pseudorange !== void 0 ? { pseudorange: s.pseudorange + add } : {},
1677
+ ...s.phase !== void 0 && s.wavelength > 0 ? { phase: s.phase + add / s.wavelength } : {}
1678
+ }))
1679
+ });
1680
+ }
1681
+ return { ...rest, observations };
1682
+ }
1683
+
1624
1684
  // src/positioning/sbas.ts
1625
1685
  var P2_11 = 2 ** -11;
1626
1686
  var P2_31 = 2 ** -31;
@@ -4011,6 +4071,7 @@ export {
4011
4071
  RtkFloatEngine,
4012
4072
  postProcessRtk,
4013
4073
  ZeroBaselineEngine,
4074
+ resolveMsmRanges,
4014
4075
  sbasLongTermDeg,
4015
4076
  sbasIonoDeg,
4016
4077
  SbasProcessor,
@@ -1138,12 +1138,14 @@ function satPrn(sys, satIdx1) {
1138
1138
  const num = sys === "S" ? satIdx1 + 119 : satIdx1;
1139
1139
  return `${prefix}${String(num).padStart(2, "0")}`;
1140
1140
  }
1141
- function decodeMsmFull(frame) {
1141
+ function decodeMsmFull(frame, opts = {}) {
1142
1142
  const type = frame.messageType;
1143
1143
  const sys = msmSystem(type);
1144
1144
  if (!sys) return null;
1145
1145
  const variant = type % 10;
1146
- if (variant < 4 || variant > 7) return null;
1146
+ if (variant < 1 || variant > 7) return null;
1147
+ const compact = variant <= 3;
1148
+ if (compact && !opts.compactMsm) return null;
1147
1149
  const payload = frame.payload;
1148
1150
  if (!payload || payload.length < 10) return null;
1149
1151
  try {
@@ -1188,7 +1190,9 @@ function decodeMsmFull(frame) {
1188
1190
  const rrmod = new Float64Array(numSat);
1189
1191
  const extsat = new Int8Array(numSat);
1190
1192
  const rdop = new Float64Array(numSat);
1191
- if (variant === 4 || variant === 6) {
1193
+ if (compact) {
1194
+ for (let j = 0; j < numSat; j++) rrmod[j] = bits.readU(10) / 1024;
1195
+ } else if (variant === 4 || variant === 6) {
1192
1196
  for (let j = 0; j < numSat; j++) rrint[j] = bits.readU(8);
1193
1197
  for (let j = 0; j < numSat; j++) rrmod[j] = bits.readU(10) / 1024;
1194
1198
  } else {
@@ -1214,7 +1218,18 @@ function decodeMsmFull(frame) {
1214
1218
  psr.fill(-1e30);
1215
1219
  cp.fill(-1e30);
1216
1220
  dop.fill(-1e30);
1217
- if (variant === 4) {
1221
+ if (variant === 1) {
1222
+ for (let i = 0; i < activeCells; i++) psr[i] = bits.readS(15) / 2 ** 24;
1223
+ } else if (variant === 2) {
1224
+ for (let i = 0; i < activeCells; i++) cp[i] = bits.readS(22) / 2 ** 29;
1225
+ for (let i = 0; i < activeCells; i++) ll[i] = bits.readU(4);
1226
+ for (let i = 0; i < activeCells; i++) hc[i] = bits.readU(1);
1227
+ } else if (variant === 3) {
1228
+ for (let i = 0; i < activeCells; i++) psr[i] = bits.readS(15) / 2 ** 24;
1229
+ for (let i = 0; i < activeCells; i++) cp[i] = bits.readS(22) / 2 ** 29;
1230
+ for (let i = 0; i < activeCells; i++) ll[i] = bits.readU(4);
1231
+ for (let i = 0; i < activeCells; i++) hc[i] = bits.readU(1);
1232
+ } else if (variant === 4) {
1218
1233
  for (let i = 0; i < activeCells; i++) psr[i] = bits.readS(15) / 2 ** 24;
1219
1234
  for (let i = 0; i < activeCells; i++) cp[i] = bits.readS(22) / 2 ** 29;
1220
1235
  for (let i = 0; i < activeCells; i++) ll[i] = bits.readU(4);
@@ -1290,8 +1305,10 @@ function decodeMsmFull(frame) {
1290
1305
  if (cnrVal > 0) {
1291
1306
  signal.cn0 = cnrVal;
1292
1307
  }
1293
- signal.lockTime = lockTimeSec(type, ll[cellIdx]);
1294
- signal.halfCycle = hc[cellIdx] === 1;
1308
+ if (variant !== 1) {
1309
+ signal.lockTime = lockTimeSec(type, ll[cellIdx]);
1310
+ signal.halfCycle = hc[cellIdx] === 1;
1311
+ }
1295
1312
  signals.push(signal);
1296
1313
  cellIdx++;
1297
1314
  }
@@ -1303,7 +1320,8 @@ function decodeMsmFull(frame) {
1303
1320
  messageType: type,
1304
1321
  epochMs,
1305
1322
  system: sys,
1306
- observations
1323
+ observations,
1324
+ ...compact ? { rangeModulo1ms: true } : {}
1307
1325
  };
1308
1326
  } catch (err) {
1309
1327
  reportDecodeError("msm", err);
@@ -1457,8 +1475,8 @@ function decodeLegacyObs(frame) {
1457
1475
  }
1458
1476
  return { messageType: type, epochMs, system, observations };
1459
1477
  }
1460
- function decodeObs(frame) {
1461
- return decodeMsmFull(frame) ?? decodeLegacyObs(frame);
1478
+ function decodeObs(frame, opts = {}) {
1479
+ return decodeMsmFull(frame, opts) ?? decodeLegacyObs(frame);
1462
1480
  }
1463
1481
 
1464
1482
  // src/rtcm3/ssr.ts