gnss-js 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -75,6 +75,8 @@ __export(src_exports, {
75
75
  MILLISECONDS_IN_WEEK: () => MILLISECONDS_IN_WEEK,
76
76
  MILLISECONDS_TT_TAI: () => MILLISECONDS_TT_TAI,
77
77
  MultipathAccumulator: () => MultipathAccumulator,
78
+ OMEGA_E: () => OMEGA_E,
79
+ REFERENCE_FRAMES: () => REFERENCE_FRAMES,
78
80
  RINEX_CODES: () => RINEX_CODES,
79
81
  RTCM3_MESSAGE_NAMES: () => RTCM3_MESSAGE_NAMES,
80
82
  Rtcm3Decoder: () => Rtcm3Decoder,
@@ -101,6 +103,7 @@ __export(src_exports, {
101
103
  WGS84_SEMI_MINOR_AXIS: () => WGS84_SEMI_MINOR_AXIS,
102
104
  WarningAccumulator: () => WarningAccumulator,
103
105
  analyzeQuality: () => analyzeQuality,
106
+ applyHelmert: () => applyHelmert,
104
107
  buildGloChannelMap: () => buildGloChannelMap,
105
108
  buildObsIndices: () => buildObsIndices,
106
109
  clampUnit: () => clampUnit,
@@ -116,12 +119,14 @@ __export(src_exports, {
116
119
  createStreamStats: () => createStreamStats,
117
120
  crxDecompress: () => crxDecompress,
118
121
  crxRepair: () => crxRepair,
122
+ dateToEpoch: () => dateToEpoch,
119
123
  decodeEphemeris: () => decodeEphemeris,
120
124
  decodeMsmFull: () => decodeMsmFull,
121
125
  deg2dms: () => deg2dms,
122
126
  deg2rad: () => deg2rad,
123
127
  ecefToAzEl: () => ecefToAzEl,
124
128
  ecefToGeodetic: () => ecefToGeodetic,
129
+ ephInfoToEphemeris: () => ephInfoToEphemeris,
125
130
  euclidean3D: () => euclidean3D,
126
131
  fetchSourcetable: () => fetchSourcetable,
127
132
  fmtF: () => fmtF,
@@ -189,6 +194,7 @@ __export(src_exports, {
189
194
  greatCircleMidpoint: () => greatCircleMidpoint,
190
195
  hdrLine: () => hdrLine,
191
196
  horizonDistance: () => horizonDistance,
197
+ ionoFree: () => ionoFree,
192
198
  keplerPosition: () => keplerPosition,
193
199
  msmEpochToDate: () => msmEpochToDate,
194
200
  navTimesFromEph: () => navTimesFromEph,
@@ -211,11 +217,14 @@ __export(src_exports, {
211
217
  resetGloFreqCache: () => resetGloFreqCache,
212
218
  rhumbLine: () => rhumbLine,
213
219
  rtcm3Constellation: () => rtcm3Constellation,
220
+ satClockCorrection: () => satClockCorrection,
214
221
  selectEphemeris: () => selectEphemeris,
215
222
  setGloFreqNumber: () => setGloFreqNumber,
216
223
  setRtcm3DebugHandler: () => setRtcm3DebugHandler,
224
+ solveSpp: () => solveSpp,
217
225
  systemCmp: () => systemCmp,
218
226
  systemName: () => systemName,
227
+ transformFrame: () => transformFrame,
219
228
  updateStationMeta: () => updateStationMeta,
220
229
  updateStreamStats: () => updateStreamStats,
221
230
  verifyChecksum: () => verifyChecksum,
@@ -300,6 +309,7 @@ var SYS_SHORT = {
300
309
  S: "SBS"
301
310
  };
302
311
  var C_LIGHT = 299792458;
312
+ var OMEGA_E = 72921151467e-15;
303
313
  var FREQ = {
304
314
  G: { "1": 157542e4, "2": 12276e5, "5": 117645e4 },
305
315
  // R bands 4/6 are the CDMA L1OC/L2OC center frequencies
@@ -3806,6 +3816,7 @@ var RTCM3_MESSAGE_NAMES = {
3806
3816
  1031: "GLONASS Network RTK Residual",
3807
3817
  1032: "Physical Reference Station",
3808
3818
  1033: "Receiver+Antenna Descriptor",
3819
+ 1041: "NavIC Ephemeris",
3809
3820
  1042: "BeiDou Ephemeris",
3810
3821
  1043: "SBAS Ephemeris",
3811
3822
  1044: "QZSS Ephemeris",
@@ -3870,6 +3881,8 @@ var RTCM3_MESSAGE_NAMES = {
3870
3881
  1064: "GLONASS SSR Clock",
3871
3882
  1065: "GLONASS SSR Code Bias",
3872
3883
  1066: "GLONASS SSR Orbit+Clock",
3884
+ // Biases
3885
+ 1230: "GLONASS Code-Phase Biases",
3873
3886
  // IGS SSR
3874
3887
  4076: "IGS SSR"
3875
3888
  };
@@ -3878,6 +3891,7 @@ function rtcm3Constellation(msgType) {
3878
3891
  if (msgType >= 1009 && msgType <= 1012) return "GLONASS";
3879
3892
  if (msgType === 1019) return "GPS";
3880
3893
  if (msgType === 1020) return "GLONASS";
3894
+ if (msgType === 1041) return "NavIC";
3881
3895
  if (msgType === 1042) return "BeiDou";
3882
3896
  if (msgType === 1043) return "SBAS";
3883
3897
  if (msgType === 1044) return "QZSS";
@@ -3889,6 +3903,7 @@ function rtcm3Constellation(msgType) {
3889
3903
  if (msgType >= 1111 && msgType <= 1117) return "QZSS";
3890
3904
  if (msgType >= 1121 && msgType <= 1127) return "BeiDou";
3891
3905
  if (msgType >= 1131 && msgType <= 1137) return "NavIC";
3906
+ if (msgType === 1230) return "GLONASS";
3892
3907
  return null;
3893
3908
  }
3894
3909
  function createStreamStats() {
@@ -3983,7 +3998,6 @@ var GM_GPS = 3986005e8;
3983
3998
  var GM_GAL = 3986004418e5;
3984
3999
  var GM_BDS = 3986004418e5;
3985
4000
  var GM_GLO = 39860044e7;
3986
- var OMEGA_E = 72921151467e-15;
3987
4001
  var BDS_GEO_MAX_INCLINATION_RAD = 0.1;
3988
4002
  var BDS_GEO_MIN_SEMIMAJOR_AXIS_M = 4e7;
3989
4003
  var AE_GLO = 6378136;
@@ -4129,17 +4143,17 @@ function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
4129
4143
  function computeDop(satAzEls) {
4130
4144
  const n = satAzEls.length;
4131
4145
  if (n < 4) return null;
4132
- const H = [];
4146
+ const H2 = [];
4133
4147
  for (const { az, el } of satAzEls) {
4134
4148
  const cosEl = Math.cos(el);
4135
- H.push([cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el), 1]);
4149
+ H2.push([cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el), 1]);
4136
4150
  }
4137
4151
  const HtH = Array.from({ length: 4 }, () => new Array(4).fill(0));
4138
4152
  for (let i = 0; i < 4; i++) {
4139
4153
  for (let j = 0; j < 4; j++) {
4140
4154
  let sum = 0;
4141
4155
  for (let k = 0; k < n; k++) {
4142
- sum += H[k][i] * H[k][j];
4156
+ sum += H2[k][i] * H2[k][j];
4143
4157
  }
4144
4158
  HtH[i][j] = sum;
4145
4159
  }
@@ -4192,7 +4206,8 @@ function selectEphemeris(ephemerides, prn, timeMs) {
4192
4206
  if (eph.prn !== prn) continue;
4193
4207
  const ephTime = eph.tocDate.getTime();
4194
4208
  const dt = Math.abs(timeMs - ephTime);
4195
- if (dt > 4 * 3600 * 1e3) continue;
4209
+ const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
4210
+ if (dt > maxAge) continue;
4196
4211
  if (dt < bestDt) {
4197
4212
  bestDt = dt;
4198
4213
  best = eph;
@@ -4300,13 +4315,13 @@ function ephInfoToEphemeris(info) {
4300
4315
  return null;
4301
4316
  if (info.vx === void 0 || info.vy === void 0 || info.vz === void 0)
4302
4317
  return null;
4303
- const now = new Date(info.lastReceived);
4318
+ const moscow = new Date(info.lastReceived + 3 * 3600 * 1e3);
4304
4319
  const utcMidnight = Date.UTC(
4305
- now.getUTCFullYear(),
4306
- now.getUTCMonth(),
4307
- now.getUTCDate()
4320
+ moscow.getUTCFullYear(),
4321
+ moscow.getUTCMonth(),
4322
+ moscow.getUTCDate()
4308
4323
  );
4309
- const tbSec = (info.tb ?? 0) * 900;
4324
+ const tbSec = (info.tb ?? 0) * 60;
4310
4325
  const moscowOffset = 3 * 3600;
4311
4326
  const tocMs = utcMidnight + (tbSec - moscowOffset) * 1e3;
4312
4327
  return {
@@ -4469,6 +4484,414 @@ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, el
4469
4484
  return { times, elevation, visibleCount, pdop, passes };
4470
4485
  }
4471
4486
 
4487
+ // src/frames/index.ts
4488
+ var H = (t, d, r, tDot, dDot, rDot, epoch) => ({ t, d, r, tDot, dDot, rDot, epoch });
4489
+ var Z = [0, 0, 0];
4490
+ var FROM_ITRF2020 = {
4491
+ ITRF2014: H([-1.4, -0.9, 1.4], -0.42, Z, [0, -0.1, 0.2], 0, Z, 2015),
4492
+ ITRF2008: H([0.2, 1, 3.3], -0.29, Z, [0, -0.1, 0.1], 0.03, Z, 2015),
4493
+ ITRF2005: H([2.7, 0.1, -1.4], 0.65, Z, [0.3, -0.1, 0.1], 0.03, Z, 2015),
4494
+ ITRF2000: H([-0.2, 0.8, -34.2], 2.25, Z, [0.1, 0, -1.7], 0.11, Z, 2015),
4495
+ ITRF97: H(
4496
+ [6.5, -3.9, -77.9],
4497
+ 3.98,
4498
+ [0, 0, 0.36],
4499
+ [0.1, -0.6, -3.1],
4500
+ 0.12,
4501
+ [0, 0, 0.02],
4502
+ 2015
4503
+ ),
4504
+ ITRF96: H(
4505
+ [6.5, -3.9, -77.9],
4506
+ 3.98,
4507
+ [0, 0, 0.36],
4508
+ [0.1, -0.6, -3.1],
4509
+ 0.12,
4510
+ [0, 0, 0.02],
4511
+ 2015
4512
+ ),
4513
+ ITRF94: H(
4514
+ [6.5, -3.9, -77.9],
4515
+ 3.98,
4516
+ [0, 0, 0.36],
4517
+ [0.1, -0.6, -3.1],
4518
+ 0.12,
4519
+ [0, 0, 0.02],
4520
+ 2015
4521
+ ),
4522
+ ITRF93: H(
4523
+ [-65.8, 1.9, -71.3],
4524
+ 4.47,
4525
+ [-3.36, -4.33, 0.75],
4526
+ [-2.8, -0.2, -2.3],
4527
+ 0.12,
4528
+ [-0.11, -0.19, 0.07],
4529
+ 2015
4530
+ ),
4531
+ ITRF92: H(
4532
+ [14.5, -1.9, -85.9],
4533
+ 3.27,
4534
+ [0, 0, 0.36],
4535
+ [0.1, -0.6, -3.1],
4536
+ 0.12,
4537
+ [0, 0, 0.02],
4538
+ 2015
4539
+ ),
4540
+ ITRF91: H(
4541
+ [26.5, 12.1, -91.9],
4542
+ 4.67,
4543
+ [0, 0, 0.36],
4544
+ [0.1, -0.6, -3.1],
4545
+ 0.12,
4546
+ [0, 0, 0.02],
4547
+ 2015
4548
+ ),
4549
+ ITRF90: H(
4550
+ [24.5, 8.1, -107.9],
4551
+ 4.97,
4552
+ [0, 0, 0.36],
4553
+ [0.1, -0.6, -3.1],
4554
+ 0.12,
4555
+ [0, 0, 0.02],
4556
+ 2015
4557
+ ),
4558
+ ITRF89: H(
4559
+ [29.5, 32.1, -145.9],
4560
+ 8.37,
4561
+ [0, 0, 0.36],
4562
+ [0.1, -0.6, -3.1],
4563
+ 0.12,
4564
+ [0, 0, 0.02],
4565
+ 2015
4566
+ ),
4567
+ ITRF88: H(
4568
+ [24.5, -3.9, -169.9],
4569
+ 11.47,
4570
+ [0.1, 0, 0.36],
4571
+ [0.1, -0.6, -3.1],
4572
+ 0.12,
4573
+ [0, 0, 0.02],
4574
+ 2015
4575
+ ),
4576
+ /** ITRF2020 → ETRF2020, EPSG:10572 (epoch-1989 variant) */
4577
+ ETRF2020: H(Z, 0, Z, Z, 0, [0.086, 0.519, -0.753], 1989)
4578
+ };
4579
+ var ITRF2014_TO_ETRF2014 = H(Z, 0, Z, Z, 0, [0.085, 0.531, -0.77], 1989);
4580
+ var ITRF2014_TO_ETRF2000 = H(
4581
+ [54.7, 52.2, -74.1],
4582
+ 2.12,
4583
+ [1.701, 10.29, -16.632],
4584
+ [0.1, 0.1, -1.9],
4585
+ 0.11,
4586
+ [0.081, 0.49, -0.792],
4587
+ 2010
4588
+ );
4589
+ var ITRF2014_TO_NAD83_2011 = H(
4590
+ [1005.3, -1909.21, -541.57],
4591
+ 0.36891,
4592
+ [-26.78138, 0.42027, -10.93206],
4593
+ [0.79, -0.6, -1.44],
4594
+ -0.07201,
4595
+ [-0.06667, 0.75744, 0.05133],
4596
+ 2010
4597
+ );
4598
+ var MAS_TO_RAD = Math.PI / (180 * 3600 * 1e3);
4599
+ function applyHelmert(xyz, p, epoch, inverse = false) {
4600
+ const dt = epoch - p.epoch;
4601
+ const sign = inverse ? -1 : 1;
4602
+ const tx = sign * (p.t[0] + p.tDot[0] * dt) / 1e3;
4603
+ const ty = sign * (p.t[1] + p.tDot[1] * dt) / 1e3;
4604
+ const tz = sign * (p.t[2] + p.tDot[2] * dt) / 1e3;
4605
+ const d = sign * (p.d + p.dDot * dt) * 1e-9;
4606
+ const rx = sign * (p.r[0] + p.rDot[0] * dt) * MAS_TO_RAD;
4607
+ const ry = sign * (p.r[1] + p.rDot[1] * dt) * MAS_TO_RAD;
4608
+ const rz = sign * (p.r[2] + p.rDot[2] * dt) * MAS_TO_RAD;
4609
+ const [x, y, z] = xyz;
4610
+ return [
4611
+ x + tx + d * x - rz * y + ry * z,
4612
+ y + ty + rz * x + d * y - rx * z,
4613
+ z + tz - ry * x + rx * y + d * z
4614
+ ];
4615
+ }
4616
+ var EDGES = [
4617
+ ...Object.entries(FROM_ITRF2020).map(([to, p]) => ({
4618
+ from: "ITRF2020",
4619
+ to,
4620
+ p
4621
+ })),
4622
+ { from: "ITRF2014", to: "ETRF2014", p: ITRF2014_TO_ETRF2014 },
4623
+ { from: "ITRF2014", to: "ETRF2000", p: ITRF2014_TO_ETRF2000 },
4624
+ { from: "ITRF2014", to: "NAD83(2011)", p: ITRF2014_TO_NAD83_2011 }
4625
+ ];
4626
+ var REFERENCE_FRAMES = [
4627
+ "WGS84",
4628
+ "ITRF2020",
4629
+ "ITRF2014",
4630
+ "ITRF2008",
4631
+ "ITRF2005",
4632
+ "ITRF2000",
4633
+ "ITRF97",
4634
+ "ITRF96",
4635
+ "ITRF94",
4636
+ "ITRF93",
4637
+ "ITRF92",
4638
+ "ITRF91",
4639
+ "ITRF90",
4640
+ "ITRF89",
4641
+ "ITRF88",
4642
+ "ETRF2020",
4643
+ "ETRF2014",
4644
+ "ETRF2000",
4645
+ "NAD83(2011)"
4646
+ ];
4647
+ function resolveAlias(f) {
4648
+ return f === "WGS84" ? "ITRF2020" : f;
4649
+ }
4650
+ function findPath(from, to) {
4651
+ if (from === to) return [];
4652
+ const queue = [from];
4653
+ const cameFrom = /* @__PURE__ */ new Map();
4654
+ const seen = /* @__PURE__ */ new Set([from]);
4655
+ while (queue.length > 0) {
4656
+ const cur = queue.shift();
4657
+ for (const edge of EDGES) {
4658
+ let next = null;
4659
+ let inverse = false;
4660
+ if (edge.from === cur) {
4661
+ next = edge.to;
4662
+ } else if (edge.to === cur) {
4663
+ next = edge.from;
4664
+ inverse = true;
4665
+ }
4666
+ if (next === null || seen.has(next)) continue;
4667
+ seen.add(next);
4668
+ cameFrom.set(next, { prev: cur, edge, inverse });
4669
+ if (next === to) {
4670
+ const path = [];
4671
+ let node = to;
4672
+ while (node !== from) {
4673
+ const step = cameFrom.get(node);
4674
+ path.unshift({ edge: step.edge, inverse: step.inverse });
4675
+ node = step.prev;
4676
+ }
4677
+ return path;
4678
+ }
4679
+ queue.push(next);
4680
+ }
4681
+ }
4682
+ return null;
4683
+ }
4684
+ function transformFrame(xyz, from, to, epoch) {
4685
+ const src = resolveAlias(from);
4686
+ const dst = resolveAlias(to);
4687
+ const path = findPath(src, dst);
4688
+ if (!path) {
4689
+ throw new Error(`No transformation path from ${from} to ${to}`);
4690
+ }
4691
+ let out = [xyz[0], xyz[1], xyz[2]];
4692
+ for (const { edge, inverse } of path) {
4693
+ out = applyHelmert(out, edge.p, epoch, inverse);
4694
+ }
4695
+ return out;
4696
+ }
4697
+ function dateToEpoch(date) {
4698
+ const year = date.getUTCFullYear();
4699
+ const start = Date.UTC(year, 0, 1);
4700
+ const end = Date.UTC(year + 1, 0, 1);
4701
+ return year + (date.getTime() - start) / (end - start);
4702
+ }
4703
+
4704
+ // src/positioning/index.ts
4705
+ var GM_GPS2 = 3986005e8;
4706
+ var F_REL = -4442807633e-19;
4707
+ function satClockCorrection(eph, tMs) {
4708
+ if (eph.system === "R" || eph.system === "S") {
4709
+ const dt2 = (tMs - eph.tocDate.getTime()) / 1e3;
4710
+ return eph.tauN + eph.gammaN * dt2;
4711
+ }
4712
+ const k = eph;
4713
+ const dt = (tMs - k.tocDate.getTime()) / 1e3;
4714
+ const poly = k.af0 + k.af1 * dt + k.af2 * dt * dt;
4715
+ const a = k.sqrtA * k.sqrtA;
4716
+ const n = Math.sqrt(GM_GPS2 / (a * a * a)) + k.deltaN;
4717
+ const tk = dt;
4718
+ const Mk = k.m0 + n * tk;
4719
+ let Ek = Mk;
4720
+ for (let i = 0; i < 8; i++) {
4721
+ Ek = Mk + k.e * Math.sin(Ek);
4722
+ }
4723
+ const rel = F_REL * k.e * k.sqrtA * Math.sin(Ek);
4724
+ return poly + rel;
4725
+ }
4726
+ function ionoFree(p1, p2, f1, f2) {
4727
+ const g = f1 * f1 / (f1 * f1 - f2 * f2);
4728
+ return g * p1 - (g - 1) * p2;
4729
+ }
4730
+ function tropoDelay(elevationRad) {
4731
+ const sinEl = Math.sin(elevationRad);
4732
+ return 2.47 / (sinEl + 0.0121);
4733
+ }
4734
+ function sagnac(pos, travelTimeS) {
4735
+ const theta = OMEGA_E * travelTimeS;
4736
+ const c = Math.cos(theta);
4737
+ const s = Math.sin(theta);
4738
+ return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
4739
+ }
4740
+ function solveLinear(A, b) {
4741
+ const n = b.length;
4742
+ const M = A.map((row, i) => [...row, b[i]]);
4743
+ for (let col = 0; col < n; col++) {
4744
+ let pivot = col;
4745
+ for (let r = col + 1; r < n; r++) {
4746
+ if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
4747
+ }
4748
+ if (Math.abs(M[pivot][col]) < 1e-12) return null;
4749
+ [M[col], M[pivot]] = [M[pivot], M[col]];
4750
+ for (let r = 0; r < n; r++) {
4751
+ if (r === col) continue;
4752
+ const f = M[r][col] / M[col][col];
4753
+ for (let c = col; c <= n; c++) M[r][c] -= f * M[col][c];
4754
+ }
4755
+ }
4756
+ return M.map((row, i) => row[n] / M[i][i]);
4757
+ }
4758
+ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
4759
+ const {
4760
+ elevationMaskDeg = 10,
4761
+ troposphere = true,
4762
+ maxIterations = 15,
4763
+ convergenceM = 1e-4,
4764
+ tgd = true
4765
+ } = opts;
4766
+ const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
4767
+ const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
4768
+ if (all.length < minSats(all)) return null;
4769
+ const gaussNewton = (prns) => {
4770
+ const systems = [...new Set(prns.map((p) => p[0]))].sort();
4771
+ const dim = 3 + systems.length;
4772
+ const sysIndex = new Map(systems.map((s, i) => [s, 3 + i]));
4773
+ let x2 = 0;
4774
+ let y2 = 0;
4775
+ let z2 = 0;
4776
+ const clock2 = new Map(systems.map((s) => [s, 0]));
4777
+ const residuals2 = {};
4778
+ let iterations2 = 0;
4779
+ let converged2 = false;
4780
+ for (let iter = 0; iter < maxIterations; iter++) {
4781
+ iterations2 = iter + 1;
4782
+ const HtH = Array.from(
4783
+ { length: dim },
4784
+ () => new Array(dim).fill(0)
4785
+ );
4786
+ const Htv = new Array(dim).fill(0);
4787
+ let rows = 0;
4788
+ for (const k of Object.keys(residuals2)) delete residuals2[k];
4789
+ for (const prn of prns) {
4790
+ const psr = pseudoranges.get(prn);
4791
+ const eph = ephemerides.get(prn);
4792
+ const sys = prn[0];
4793
+ const clk = clock2.get(sys);
4794
+ const tTx = timeMs - psr / C_LIGHT * 1e3;
4795
+ const dtsClock = satClockCorrection(eph, tTx);
4796
+ const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
4797
+ if (!Number.isFinite(sat.x)) continue;
4798
+ const isKepler2 = eph.system !== "R" && eph.system !== "S";
4799
+ const dts = dtsClock - (tgd && isKepler2 ? eph.tgd : 0);
4800
+ const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
4801
+ const [sx, sy, sz] = sagnac(sat, travel);
4802
+ const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
4803
+ const ux = (x2 - sx) / rho;
4804
+ const uy = (y2 - sy) / rho;
4805
+ const uz = (z2 - sz) / rho;
4806
+ let elev = Math.PI / 2;
4807
+ let weight = 1;
4808
+ const positionSane = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
4809
+ if (positionSane) {
4810
+ const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
4811
+ elev = azel.el;
4812
+ if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
4813
+ const sinEl = Math.max(Math.sin(elev), 0.1);
4814
+ weight = sinEl * sinEl;
4815
+ }
4816
+ const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
4817
+ const predicted = rho + clk - C_LIGHT * dts + tropo;
4818
+ const v = psr - predicted;
4819
+ const h = new Array(dim).fill(0);
4820
+ h[0] = ux;
4821
+ h[1] = uy;
4822
+ h[2] = uz;
4823
+ h[sysIndex.get(sys)] = 1;
4824
+ for (let i = 0; i < dim; i++) {
4825
+ for (let j = 0; j < dim; j++) HtH[i][j] += weight * h[i] * h[j];
4826
+ Htv[i] += weight * h[i] * v;
4827
+ }
4828
+ residuals2[prn] = v;
4829
+ rows++;
4830
+ }
4831
+ if (rows < dim) return null;
4832
+ const dx = solveLinear(HtH, Htv);
4833
+ if (!dx) return null;
4834
+ x2 += dx[0];
4835
+ y2 += dx[1];
4836
+ z2 += dx[2];
4837
+ for (const s of systems)
4838
+ clock2.set(s, clock2.get(s) + dx[sysIndex.get(s)]);
4839
+ if (Math.hypot(dx[0], dx[1], dx[2]) < convergenceM) {
4840
+ converged2 = true;
4841
+ break;
4842
+ }
4843
+ }
4844
+ return { x: x2, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
4845
+ };
4846
+ const REJECT_THRESHOLD_M = 50;
4847
+ let candidates = all;
4848
+ const rejected = [];
4849
+ let inner = null;
4850
+ for (let round = 0; round <= all.length; round++) {
4851
+ inner = gaussNewton(candidates);
4852
+ if (!inner) return null;
4853
+ const vals = Object.entries(inner.residuals);
4854
+ let worst = null;
4855
+ let worstAbs = REJECT_THRESHOLD_M;
4856
+ for (const [prn, v] of vals) {
4857
+ if (Math.abs(v) > worstAbs) {
4858
+ worstAbs = Math.abs(v);
4859
+ worst = prn;
4860
+ }
4861
+ }
4862
+ if (!worst) break;
4863
+ const remaining = candidates.filter((p) => p !== worst);
4864
+ if (remaining.length < minSats(remaining)) break;
4865
+ rejected.push(worst);
4866
+ candidates = remaining;
4867
+ }
4868
+ if (!inner) return null;
4869
+ const { x, y, z, clock, residuals, iterations, converged } = inner;
4870
+ const used = [];
4871
+ const azels = [];
4872
+ for (const prn of candidates) {
4873
+ if (!(prn in residuals)) continue;
4874
+ const eph = ephemerides.get(prn);
4875
+ const sat = computeSatPosition(eph, timeMs);
4876
+ if (!Number.isFinite(sat.x)) continue;
4877
+ const azel = ecefToAzEl(x, y, z, sat.x, sat.y, sat.z);
4878
+ if (azel.el >= elevationMaskDeg * Math.PI / 180) {
4879
+ used.push(prn);
4880
+ azels.push(azel);
4881
+ }
4882
+ }
4883
+ return {
4884
+ position: [x, y, z],
4885
+ clockBias: Object.fromEntries(clock),
4886
+ usedSatellites: used,
4887
+ rejectedSatellites: rejected,
4888
+ residuals,
4889
+ dop: computeDop(azels),
4890
+ iterations,
4891
+ converged
4892
+ };
4893
+ }
4894
+
4472
4895
  // src/ntrip/index.ts
4473
4896
  function parseStreamEntry(fields) {
4474
4897
  if (fields.length < 19) return null;
@@ -4620,6 +5043,14 @@ async function connectToMountpoint(proxyUrl, info, signal) {
4620
5043
 
4621
5044
  // src/analysis/multipath.ts
4622
5045
  var MIN_ARC_LENGTH = 10;
5046
+ var MP_JUMP_M = 1.25;
5047
+ var MP_JUMP_WINDOW = 5;
5048
+ var MP_EDIT_SIGMA = 3;
5049
+ function median(values) {
5050
+ const s = [...values].sort((a, b) => a - b);
5051
+ const m = s.length >> 1;
5052
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
5053
+ }
4623
5054
  var MultipathAccumulator = class {
4624
5055
  state = /* @__PURE__ */ new Map();
4625
5056
  results = [];
@@ -4676,6 +5107,11 @@ var MultipathAccumulator = class {
4676
5107
  const gap = bandState.lastTime > 0 ? (time - bandState.lastTime) / 1e3 : 0;
4677
5108
  if (bandState.lastTime > 0 && gap > this.interval * ARC_GAP_FACTOR) {
4678
5109
  this.closeArc(prn, band, refBand, bandState);
5110
+ } else if (bandState.arc.rawMp.length > 0) {
5111
+ const recent = bandState.arc.rawMp.slice(-MP_JUMP_WINDOW);
5112
+ if (Math.abs(mp - median(recent)) > MP_JUMP_M) {
5113
+ this.closeArc(prn, band, refBand, bandState);
5114
+ }
4679
5115
  }
4680
5116
  bandState.arc.times.push(time);
4681
5117
  bandState.arc.rawMp.push(mp);
@@ -4695,13 +5131,25 @@ var MultipathAccumulator = class {
4695
5131
  closeArc(prn, band, refBand, state) {
4696
5132
  const arc = state.arc;
4697
5133
  if (arc.times.length >= MIN_ARC_LENGTH) {
4698
- const mean = arc.rawMp.reduce((a, b) => a + b, 0) / arc.rawMp.length;
5134
+ let keep = arc.times.map((_, i) => i);
5135
+ let mean = 0;
5136
+ for (let pass = 0; pass < 3; pass++) {
5137
+ mean = keep.reduce((s, i) => s + arc.rawMp[i], 0) / keep.length;
5138
+ const sigma = Math.sqrt(
5139
+ keep.reduce((s, i) => s + (arc.rawMp[i] - mean) ** 2, 0) / keep.length
5140
+ );
5141
+ const kept = keep.filter(
5142
+ (i) => Math.abs(arc.rawMp[i] - mean) <= MP_EDIT_SIGMA * sigma
5143
+ );
5144
+ if (kept.length === keep.length || kept.length < MIN_ARC_LENGTH) break;
5145
+ keep = kept;
5146
+ }
4699
5147
  const sys = prn[0];
4700
5148
  const bLabel = BAND_LABELS[sys]?.[band] ?? band;
4701
5149
  const rLabel = BAND_LABELS[sys]?.[refBand] ?? refBand;
4702
5150
  const label2 = `${prn} MP ${bLabel}-${rLabel}`;
4703
- const points = arc.times.map((t, i) => ({
4704
- time: t,
5151
+ const points = keep.map((i) => ({
5152
+ time: arc.times[i],
4705
5153
  mp: arc.rawMp[i] - mean
4706
5154
  }));
4707
5155
  const rms = Math.sqrt(
@@ -4755,7 +5203,7 @@ var MultipathAccumulator = class {
4755
5203
  refBand,
4756
5204
  rms,
4757
5205
  count,
4758
- satellites: seriesList.length
5206
+ satellites: new Set(seriesList.map((s) => s.prn)).size
4759
5207
  });
4760
5208
  }
4761
5209
  const sysOrder = "GRECIJS";
@@ -6345,6 +6793,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
6345
6793
  MILLISECONDS_IN_WEEK,
6346
6794
  MILLISECONDS_TT_TAI,
6347
6795
  MultipathAccumulator,
6796
+ OMEGA_E,
6797
+ REFERENCE_FRAMES,
6348
6798
  RINEX_CODES,
6349
6799
  RTCM3_MESSAGE_NAMES,
6350
6800
  Rtcm3Decoder,
@@ -6371,6 +6821,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
6371
6821
  WGS84_SEMI_MINOR_AXIS,
6372
6822
  WarningAccumulator,
6373
6823
  analyzeQuality,
6824
+ applyHelmert,
6374
6825
  buildGloChannelMap,
6375
6826
  buildObsIndices,
6376
6827
  clampUnit,
@@ -6386,12 +6837,14 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
6386
6837
  createStreamStats,
6387
6838
  crxDecompress,
6388
6839
  crxRepair,
6840
+ dateToEpoch,
6389
6841
  decodeEphemeris,
6390
6842
  decodeMsmFull,
6391
6843
  deg2dms,
6392
6844
  deg2rad,
6393
6845
  ecefToAzEl,
6394
6846
  ecefToGeodetic,
6847
+ ephInfoToEphemeris,
6395
6848
  euclidean3D,
6396
6849
  fetchSourcetable,
6397
6850
  fmtF,
@@ -6459,6 +6912,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
6459
6912
  greatCircleMidpoint,
6460
6913
  hdrLine,
6461
6914
  horizonDistance,
6915
+ ionoFree,
6462
6916
  keplerPosition,
6463
6917
  msmEpochToDate,
6464
6918
  navTimesFromEph,
@@ -6481,11 +6935,14 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
6481
6935
  resetGloFreqCache,
6482
6936
  rhumbLine,
6483
6937
  rtcm3Constellation,
6938
+ satClockCorrection,
6484
6939
  selectEphemeris,
6485
6940
  setGloFreqNumber,
6486
6941
  setRtcm3DebugHandler,
6942
+ solveSpp,
6487
6943
  systemCmp,
6488
6944
  systemName,
6945
+ transformFrame,
6489
6946
  updateStationMeta,
6490
6947
  updateStreamStats,
6491
6948
  verifyChecksum,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { D as DAYS_MJD2000_MJD, a as DAYS_MJD_JULIAN, H as HourCode, M as MILLISECONDS_GPS_TAI, b as MILLISECONDS_IN_DAY, c as MILLISECONDS_IN_HOUR, d as MILLISECONDS_IN_MINUTE, e as MILLISECONDS_IN_SECOND, f as MILLISECONDS_IN_WEEK, g as MILLISECONDS_TT_TAI, R as RINEX_CODES, S as SECONDS_IN_DAY, h as SECONDS_IN_HOUR, i as SECONDS_IN_MINUTE, j as SECONDS_IN_WEEK, k as START_BDS_TIME, l as START_GAL_TIME, m as START_GLO_LEAP, n as START_GPS_TIME, o as START_JULIAN_TAI, p as START_MJD_UNIX_SECONDS, q as START_NTP_TIME, r as START_TAI_TIME, s as START_UNIX_TIME, T as TimeDifference } from './time-DnI1VpE8.cjs';
2
2
  export { WGS84_ECCENTRICITY_SQUARED, WGS84_FLATTENING, WGS84_SEMI_MAJOR_AXIS, WGS84_SEMI_MINOR_AXIS } from './constants.cjs';
3
- export { A as ARC_GAP_FACTOR, B as BAND_LABELS, a as BDS_SATELLITES, b as BdsSatellite, C as C_LIGHT, D as DEFAULT_ELEV_MASK_DEG, c as DUAL_FREQ_PAIRS, F as FREQ, G as GLO_CHANNEL_FALLBACK, d as GLO_F1_BASE, e as GLO_F1_STEP, f as GLO_F2_BASE, g as GLO_F2_STEP, h as GLO_F3, O as OnSlipDetected, S as SYSTEM_NAMES, i as SYS_SHORT, j as buildGloChannelMap, k as buildObsIndices, l as formatUTCTime, m as getFreq, n as gloFreq } from './gnss-BT6ulR17.cjs';
3
+ export { A as ARC_GAP_FACTOR, B as BAND_LABELS, a as BDS_SATELLITES, b as BdsSatellite, C as C_LIGHT, D as DEFAULT_ELEV_MASK_DEG, c as DUAL_FREQ_PAIRS, F as FREQ, G as GLO_CHANNEL_FALLBACK, d as GLO_F1_BASE, e as GLO_F1_STEP, f as GLO_F2_BASE, g as GLO_F2_STEP, h as GLO_F3, O as OMEGA_E, i as OnSlipDetected, S as SYSTEM_NAMES, j as SYS_SHORT, k as buildGloChannelMap, l as buildObsIndices, m as formatUTCTime, n as getFreq, o as gloFreq } from './gnss-BtXrG3zH.cjs';
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';
@@ -9,7 +9,9 @@ export { E as EpochSummary, P as ParseOptions, R as RinexHeader, a as RinexResul
9
9
  export { E as Ephemeris, G as GlonassEphemeris, K as KeplerEphemeris, N as NavHeader, a as NavResult, p as parseNavFile } from './nav-BAI1a9vK.cjs';
10
10
  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
11
  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
- export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris } from './orbit.cjs';
12
+ export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris } from './orbit.cjs';
13
+ export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
14
+ export { SppOptions, SppSolution, ionoFree, satClockCorrection, solveSpp } from './positioning.cjs';
13
15
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
14
16
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.cjs';
15
17
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';