gnss-js 1.2.0 → 1.3.1

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.js CHANGED
@@ -87,17 +87,30 @@ import {
87
87
  rhumbLine,
88
88
  vincenty
89
89
  } from "./chunk-MIIM4LDY.js";
90
+ import {
91
+ REFERENCE_FRAMES,
92
+ applyHelmert,
93
+ dateToEpoch,
94
+ transformFrame
95
+ } from "./chunk-4KOXMHUW.js";
96
+ import {
97
+ ionoFree,
98
+ satClockCorrection,
99
+ solveSpp
100
+ } from "./chunk-USHP4IND.js";
90
101
  import {
91
102
  computeAllPositions,
92
103
  computeDop,
93
104
  computeLiveSkyPositions,
94
105
  computeSatPosition,
106
+ computeVisibility,
95
107
  ecefToAzEl,
108
+ ephInfoToEphemeris,
96
109
  glonassPosition,
97
110
  keplerPosition,
98
111
  navTimesFromEph,
99
112
  selectEphemeris
100
- } from "./chunk-VANVV4F7.js";
113
+ } from "./chunk-CC2R4JGS.js";
101
114
  import {
102
115
  getBdsTime,
103
116
  getDateFromBdsTime,
@@ -287,6 +300,7 @@ export {
287
300
  MILLISECONDS_IN_WEEK,
288
301
  MILLISECONDS_TT_TAI,
289
302
  MultipathAccumulator,
303
+ REFERENCE_FRAMES,
290
304
  RINEX_CODES,
291
305
  RTCM3_MESSAGE_NAMES,
292
306
  Rtcm3Decoder,
@@ -313,6 +327,7 @@ export {
313
327
  WGS84_SEMI_MINOR_AXIS,
314
328
  WarningAccumulator,
315
329
  analyzeQuality,
330
+ applyHelmert,
316
331
  buildGloChannelMap,
317
332
  buildObsIndices,
318
333
  clampUnit,
@@ -322,17 +337,20 @@ export {
322
337
  computePsdDb,
323
338
  computeSatPosition,
324
339
  computeStats,
340
+ computeVisibility,
325
341
  connectToMountpoint,
326
342
  createStationMeta,
327
343
  createStreamStats,
328
344
  crxDecompress,
329
345
  crxRepair,
346
+ dateToEpoch,
330
347
  decodeEphemeris,
331
348
  decodeMsmFull,
332
349
  deg2dms,
333
350
  deg2rad,
334
351
  ecefToAzEl,
335
352
  ecefToGeodetic,
353
+ ephInfoToEphemeris,
336
354
  euclidean3D,
337
355
  fetchSourcetable,
338
356
  fmtF,
@@ -400,6 +418,7 @@ export {
400
418
  greatCircleMidpoint,
401
419
  hdrLine,
402
420
  horizonDistance,
421
+ ionoFree,
403
422
  keplerPosition,
404
423
  msmEpochToDate,
405
424
  navTimesFromEph,
@@ -422,11 +441,14 @@ export {
422
441
  resetGloFreqCache,
423
442
  rhumbLine,
424
443
  rtcm3Constellation,
444
+ satClockCorrection,
425
445
  selectEphemeris,
426
446
  setGloFreqNumber,
427
447
  setRtcm3DebugHandler,
448
+ solveSpp,
428
449
  systemCmp,
429
450
  systemName,
451
+ transformFrame,
430
452
  updateStationMeta,
431
453
  updateStreamStats,
432
454
  verifyChecksum,
package/dist/orbit.cjs CHANGED
@@ -24,8 +24,10 @@ __export(orbit_exports, {
24
24
  computeDop: () => computeDop,
25
25
  computeLiveSkyPositions: () => computeLiveSkyPositions,
26
26
  computeSatPosition: () => computeSatPosition,
27
+ computeVisibility: () => computeVisibility,
27
28
  ecefToAzEl: () => ecefToAzEl,
28
29
  ecefToGeodetic: () => ecefToGeodetic,
30
+ ephInfoToEphemeris: () => ephInfoToEphemeris,
29
31
  geodeticToEcef: () => geodeticToEcef,
30
32
  glonassPosition: () => glonassPosition,
31
33
  keplerPosition: () => keplerPosition,
@@ -374,7 +376,8 @@ function selectEphemeris(ephemerides, prn, timeMs) {
374
376
  if (eph.prn !== prn) continue;
375
377
  const ephTime = eph.tocDate.getTime();
376
378
  const dt = Math.abs(timeMs - ephTime);
377
- if (dt > 4 * 3600 * 1e3) continue;
379
+ const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
380
+ if (dt > maxAge) continue;
378
381
  if (dt < bestDt) {
379
382
  bestDt = dt;
380
383
  best = eph;
@@ -482,13 +485,13 @@ function ephInfoToEphemeris(info) {
482
485
  return null;
483
486
  if (info.vx === void 0 || info.vy === void 0 || info.vz === void 0)
484
487
  return null;
485
- const now = new Date(info.lastReceived);
488
+ const moscow = new Date(info.lastReceived + 3 * 3600 * 1e3);
486
489
  const utcMidnight = Date.UTC(
487
- now.getUTCFullYear(),
488
- now.getUTCMonth(),
489
- now.getUTCDate()
490
+ moscow.getUTCFullYear(),
491
+ moscow.getUTCMonth(),
492
+ moscow.getUTCDate()
490
493
  );
491
- const tbSec = (info.tb ?? 0) * 900;
494
+ const tbSec = (info.tb ?? 0) * 60;
492
495
  const moscowOffset = 3 * 3600;
493
496
  const tocMs = utcMidnight + (tbSec - moscowOffset) * 1e3;
494
497
  return {
@@ -591,14 +594,75 @@ function computeLiveSkyPositions(ephemerides, rxPos, cn0Map) {
591
594
  }
592
595
  return result;
593
596
  }
597
+ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, elevationMaskDeg = 10) {
598
+ const stepMs = stepSec * 1e3;
599
+ const times = [];
600
+ for (let t = startMs; t <= endMs; t += stepMs) times.push(t);
601
+ const maskRad = elevationMaskDeg * Math.PI / 180;
602
+ const all = computeAllPositions(ephemerides, times, rxPos);
603
+ const elevation = {};
604
+ const visibleCount = new Array(times.length).fill(0);
605
+ const pdop = new Array(times.length).fill(null);
606
+ const visiblePerEpoch = times.map(() => []);
607
+ for (const prn of all.prns) {
608
+ const series = all.positions[prn];
609
+ elevation[prn] = series.map((p) => p ? p.el : null);
610
+ for (let i = 0; i < series.length; i++) {
611
+ const p = series[i];
612
+ if (p && p.el >= maskRad) {
613
+ visibleCount[i]++;
614
+ visiblePerEpoch[i].push({ az: p.az, el: p.el });
615
+ }
616
+ }
617
+ }
618
+ for (let i = 0; i < times.length; i++) {
619
+ const dop = computeDop(visiblePerEpoch[i]);
620
+ pdop[i] = dop ? dop.pdop : null;
621
+ }
622
+ const passes = [];
623
+ for (const prn of all.prns) {
624
+ const els = elevation[prn];
625
+ let start = -1;
626
+ let peakEl = -Infinity;
627
+ let peakTime = 0;
628
+ for (let i = 0; i <= els.length; i++) {
629
+ const el = i < els.length ? els[i] : null;
630
+ const above = el !== null && el !== void 0 && el >= maskRad;
631
+ if (above) {
632
+ if (start === -1) {
633
+ start = i;
634
+ peakEl = -Infinity;
635
+ }
636
+ if (el > peakEl) {
637
+ peakEl = el;
638
+ peakTime = times[i];
639
+ }
640
+ } else if (start !== -1) {
641
+ passes.push({
642
+ prn,
643
+ system: prn[0],
644
+ rise: times[start],
645
+ set: times[i - 1],
646
+ peakTime,
647
+ peakEl
648
+ });
649
+ start = -1;
650
+ }
651
+ }
652
+ }
653
+ passes.sort((a, b) => a.rise - b.rise);
654
+ return { times, elevation, visibleCount, pdop, passes };
655
+ }
594
656
  // Annotate the CommonJS export names for ESM import in node:
595
657
  0 && (module.exports = {
596
658
  computeAllPositions,
597
659
  computeDop,
598
660
  computeLiveSkyPositions,
599
661
  computeSatPosition,
662
+ computeVisibility,
600
663
  ecefToAzEl,
601
664
  ecefToGeodetic,
665
+ ephInfoToEphemeris,
602
666
  geodeticToEcef,
603
667
  glonassPosition,
604
668
  keplerPosition,
package/dist/orbit.d.cts CHANGED
@@ -108,10 +108,49 @@ interface AllPositionsData {
108
108
  * When rxPos is provided, az/el are computed; otherwise they are 0.
109
109
  */
110
110
  declare function computeAllPositions(ephemerides: Ephemeris[], times: number[], rxPos?: [number, number, number]): AllPositionsData;
111
+ /** Convert an RTCM3 EphemerisInfo to the Ephemeris type used by orbit computation. */
112
+ declare function ephInfoToEphemeris(info: EphemerisInfo): Ephemeris | null;
111
113
  /**
112
114
  * Compute current satellite az/el from live RTCM3 ephemeris data.
113
115
  * Returns array of SatAzEl for all satellites with valid ephemeris.
114
116
  */
115
117
  declare function computeLiveSkyPositions(ephemerides: Map<string, EphemerisInfo>, rxPos: [number, number, number], cn0Map?: Map<string, number>): SatAzEl[];
118
+ /** A single above-mask visibility interval for one satellite. */
119
+ interface VisibilityPass {
120
+ prn: string;
121
+ system: string;
122
+ /** Rise time (Unix ms) — first sample at or above the mask. */
123
+ rise: number;
124
+ /** Set time (Unix ms) — last sample at or above the mask. */
125
+ set: number;
126
+ /** Time of peak elevation (Unix ms). */
127
+ peakTime: number;
128
+ /** Peak elevation (radians). */
129
+ peakEl: number;
130
+ }
131
+ interface VisibilityResult {
132
+ /** Sample epochs (Unix ms). */
133
+ times: number[];
134
+ /** Elevation (radians) per PRN per epoch; null when below −0.05 rad / no eph. */
135
+ elevation: Record<string, (number | null)[]>;
136
+ /** Number of satellites at or above the mask, per epoch. */
137
+ visibleCount: number[];
138
+ /** PDOP per epoch (null when < 4 satellites above the mask). */
139
+ pdop: (number | null)[];
140
+ /** Discrete above-mask passes, sorted by rise time. */
141
+ passes: VisibilityPass[];
142
+ }
143
+ /**
144
+ * Predict satellite visibility and DOP over a time window for a fixed
145
+ * receiver location.
146
+ *
147
+ * @param ephemerides Broadcast ephemerides covering the window.
148
+ * @param rxPos Receiver ECEF position (meters).
149
+ * @param startMs Window start (Unix ms).
150
+ * @param endMs Window end (Unix ms).
151
+ * @param stepSec Sample spacing in seconds (default 300).
152
+ * @param elevationMaskDeg Elevation mask in degrees (default 10).
153
+ */
154
+ declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, number, number], startMs: number, endMs: number, stepSec?: number, elevationMaskDeg?: number): VisibilityResult;
116
155
 
117
- export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
156
+ export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
package/dist/orbit.d.ts CHANGED
@@ -108,10 +108,49 @@ interface AllPositionsData {
108
108
  * When rxPos is provided, az/el are computed; otherwise they are 0.
109
109
  */
110
110
  declare function computeAllPositions(ephemerides: Ephemeris[], times: number[], rxPos?: [number, number, number]): AllPositionsData;
111
+ /** Convert an RTCM3 EphemerisInfo to the Ephemeris type used by orbit computation. */
112
+ declare function ephInfoToEphemeris(info: EphemerisInfo): Ephemeris | null;
111
113
  /**
112
114
  * Compute current satellite az/el from live RTCM3 ephemeris data.
113
115
  * Returns array of SatAzEl for all satellites with valid ephemeris.
114
116
  */
115
117
  declare function computeLiveSkyPositions(ephemerides: Map<string, EphemerisInfo>, rxPos: [number, number, number], cn0Map?: Map<string, number>): SatAzEl[];
118
+ /** A single above-mask visibility interval for one satellite. */
119
+ interface VisibilityPass {
120
+ prn: string;
121
+ system: string;
122
+ /** Rise time (Unix ms) — first sample at or above the mask. */
123
+ rise: number;
124
+ /** Set time (Unix ms) — last sample at or above the mask. */
125
+ set: number;
126
+ /** Time of peak elevation (Unix ms). */
127
+ peakTime: number;
128
+ /** Peak elevation (radians). */
129
+ peakEl: number;
130
+ }
131
+ interface VisibilityResult {
132
+ /** Sample epochs (Unix ms). */
133
+ times: number[];
134
+ /** Elevation (radians) per PRN per epoch; null when below −0.05 rad / no eph. */
135
+ elevation: Record<string, (number | null)[]>;
136
+ /** Number of satellites at or above the mask, per epoch. */
137
+ visibleCount: number[];
138
+ /** PDOP per epoch (null when < 4 satellites above the mask). */
139
+ pdop: (number | null)[];
140
+ /** Discrete above-mask passes, sorted by rise time. */
141
+ passes: VisibilityPass[];
142
+ }
143
+ /**
144
+ * Predict satellite visibility and DOP over a time window for a fixed
145
+ * receiver location.
146
+ *
147
+ * @param ephemerides Broadcast ephemerides covering the window.
148
+ * @param rxPos Receiver ECEF position (meters).
149
+ * @param startMs Window start (Unix ms).
150
+ * @param endMs Window end (Unix ms).
151
+ * @param stepSec Sample spacing in seconds (default 300).
152
+ * @param elevationMaskDeg Elevation mask in degrees (default 10).
153
+ */
154
+ declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, number, number], startMs: number, endMs: number, stepSec?: number, elevationMaskDeg?: number): VisibilityResult;
116
155
 
117
- export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, ecefToAzEl, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
156
+ export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, navTimesFromEph, selectEphemeris };
package/dist/orbit.js CHANGED
@@ -3,12 +3,14 @@ import {
3
3
  computeDop,
4
4
  computeLiveSkyPositions,
5
5
  computeSatPosition,
6
+ computeVisibility,
6
7
  ecefToAzEl,
8
+ ephInfoToEphemeris,
7
9
  glonassPosition,
8
10
  keplerPosition,
9
11
  navTimesFromEph,
10
12
  selectEphemeris
11
- } from "./chunk-VANVV4F7.js";
13
+ } from "./chunk-CC2R4JGS.js";
12
14
  import "./chunk-HVXYFUCB.js";
13
15
  import {
14
16
  ecefToGeodetic,
@@ -21,8 +23,10 @@ export {
21
23
  computeDop,
22
24
  computeLiveSkyPositions,
23
25
  computeSatPosition,
26
+ computeVisibility,
24
27
  ecefToAzEl,
25
28
  ecefToGeodetic,
29
+ ephInfoToEphemeris,
26
30
  geodeticToEcef,
27
31
  glonassPosition,
28
32
  keplerPosition,
@@ -429,7 +429,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
429
429
  elevationMaskDeg = 10,
430
430
  troposphere = true,
431
431
  maxIterations = 15,
432
- convergenceM = 1e-4
432
+ convergenceM = 1e-4,
433
+ tgd = true
433
434
  } = opts;
434
435
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
435
436
  const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
@@ -460,9 +461,11 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
460
461
  const sys = prn[0];
461
462
  const clk = clock2.get(sys);
462
463
  const tTx = timeMs - psr / C_LIGHT * 1e3;
463
- const sat = computeSatPosition(eph, tTx);
464
+ const dtsClock = satClockCorrection(eph, tTx);
465
+ const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
464
466
  if (!Number.isFinite(sat.x)) continue;
465
- const dts = satClockCorrection(eph, tTx);
467
+ const isKepler = eph.system !== "R" && eph.system !== "S";
468
+ const dts = dtsClock - (tgd && isKepler ? eph.tgd : 0);
466
469
  const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
467
470
  const [sx, sy, sz] = sagnac(sat, travel);
468
471
  const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
@@ -9,9 +9,9 @@ import './ecef-CF0uAysr.cjs';
9
9
  *
10
10
  * Weighted iterative least squares with one receiver-clock unknown per
11
11
  * constellation (absorbing inter-system time offsets), satellite clock
12
- * polynomial + relativistic correction, Sagnac (Earth-rotation)
13
- * correction, elevation masking/weighting, and a simple tropospheric
14
- * model. Ionospheric delay is NOT modelled — use the iono-free
12
+ * polynomial + relativistic correction, broadcast group delay
13
+ * (TGD/BGD), Sagnac (Earth-rotation) correction, elevation
14
+ * masking/weighting, and a simple tropospheric model. Ionospheric delay is NOT modelled — use the iono-free
15
15
  * combination (ionoFree helper) with dual-frequency pseudoranges for
16
16
  * metre-level results, or expect ~2–10 m of iono bias on L1-only.
17
17
  */
@@ -25,6 +25,14 @@ interface SppOptions {
25
25
  maxIterations?: number;
26
26
  /** Convergence threshold on the position update (m). Default 1e-4. */
27
27
  convergenceM?: number;
28
+ /**
29
+ * Apply the broadcast group-delay correction (GPS TGD, Galileo
30
+ * BGD E5a/E1, BeiDou TGD1) to the satellite clock. Correct for
31
+ * single-frequency measurements on the primary frequency (C1C/E1/B1I)
32
+ * — the default. Disable when feeding iono-free combinations, whose
33
+ * reference the broadcast clock already matches. Default true.
34
+ */
35
+ tgd?: boolean;
28
36
  }
29
37
  interface SppSolution {
30
38
  /** Receiver position, ECEF meters. */
@@ -9,9 +9,9 @@ import './ecef-CF0uAysr.js';
9
9
  *
10
10
  * Weighted iterative least squares with one receiver-clock unknown per
11
11
  * constellation (absorbing inter-system time offsets), satellite clock
12
- * polynomial + relativistic correction, Sagnac (Earth-rotation)
13
- * correction, elevation masking/weighting, and a simple tropospheric
14
- * model. Ionospheric delay is NOT modelled — use the iono-free
12
+ * polynomial + relativistic correction, broadcast group delay
13
+ * (TGD/BGD), Sagnac (Earth-rotation) correction, elevation
14
+ * masking/weighting, and a simple tropospheric model. Ionospheric delay is NOT modelled — use the iono-free
15
15
  * combination (ionoFree helper) with dual-frequency pseudoranges for
16
16
  * metre-level results, or expect ~2–10 m of iono bias on L1-only.
17
17
  */
@@ -25,6 +25,14 @@ interface SppOptions {
25
25
  maxIterations?: number;
26
26
  /** Convergence threshold on the position update (m). Default 1e-4. */
27
27
  convergenceM?: number;
28
+ /**
29
+ * Apply the broadcast group-delay correction (GPS TGD, Galileo
30
+ * BGD E5a/E1, BeiDou TGD1) to the satellite clock. Correct for
31
+ * single-frequency measurements on the primary frequency (C1C/E1/B1I)
32
+ * — the default. Disable when feeding iono-free combinations, whose
33
+ * reference the broadcast clock already matches. Default true.
34
+ */
35
+ tgd?: boolean;
28
36
  }
29
37
  interface SppSolution {
30
38
  /** Receiver position, ECEF meters. */
@@ -1,204 +1,14 @@
1
1
  import {
2
- computeDop,
3
- computeSatPosition,
4
- ecefToAzEl
5
- } from "./chunk-VANVV4F7.js";
2
+ ionoFree,
3
+ satClockCorrection,
4
+ solveSpp
5
+ } from "./chunk-USHP4IND.js";
6
+ import "./chunk-CC2R4JGS.js";
6
7
  import "./chunk-HVXYFUCB.js";
7
8
  import "./chunk-37QNKGTC.js";
8
9
  import "./chunk-6FAL6P4G.js";
9
10
  import "./chunk-LEEU5OIO.js";
10
- import {
11
- C_LIGHT
12
- } from "./chunk-W5WKEV7U.js";
13
-
14
- // src/positioning/index.ts
15
- var GM_GPS = 3986005e8;
16
- var F_REL = -4442807633e-19;
17
- function satClockCorrection(eph, tMs) {
18
- if (eph.system === "R" || eph.system === "S") {
19
- const dt2 = (tMs - eph.tocDate.getTime()) / 1e3;
20
- return eph.tauN + eph.gammaN * dt2;
21
- }
22
- const k = eph;
23
- const dt = (tMs - k.tocDate.getTime()) / 1e3;
24
- const poly = k.af0 + k.af1 * dt + k.af2 * dt * dt;
25
- const a = k.sqrtA * k.sqrtA;
26
- const n = Math.sqrt(GM_GPS / (a * a * a)) + k.deltaN;
27
- const tk = dt;
28
- const Mk = k.m0 + n * tk;
29
- let Ek = Mk;
30
- for (let i = 0; i < 8; i++) {
31
- Ek = Mk + k.e * Math.sin(Ek);
32
- }
33
- const rel = F_REL * k.e * k.sqrtA * Math.sin(Ek);
34
- return poly + rel;
35
- }
36
- function ionoFree(p1, p2, f1, f2) {
37
- const g = f1 * f1 / (f1 * f1 - f2 * f2);
38
- return g * p1 - (g - 1) * p2;
39
- }
40
- function tropoDelay(elevationRad) {
41
- const sinEl = Math.sin(elevationRad);
42
- return 2.47 / (sinEl + 0.0121);
43
- }
44
- function sagnac(pos, travelTimeS) {
45
- const OMEGA_E = 72921151467e-15;
46
- const theta = OMEGA_E * travelTimeS;
47
- const c = Math.cos(theta);
48
- const s = Math.sin(theta);
49
- return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
50
- }
51
- function solveLinear(A, b) {
52
- const n = b.length;
53
- const M = A.map((row, i) => [...row, b[i]]);
54
- for (let col = 0; col < n; col++) {
55
- let pivot = col;
56
- for (let r = col + 1; r < n; r++) {
57
- if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
58
- }
59
- if (Math.abs(M[pivot][col]) < 1e-12) return null;
60
- [M[col], M[pivot]] = [M[pivot], M[col]];
61
- for (let r = 0; r < n; r++) {
62
- if (r === col) continue;
63
- const f = M[r][col] / M[col][col];
64
- for (let c = col; c <= n; c++) M[r][c] -= f * M[col][c];
65
- }
66
- }
67
- return M.map((row, i) => row[n] / M[i][i]);
68
- }
69
- function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
70
- const {
71
- elevationMaskDeg = 10,
72
- troposphere = true,
73
- maxIterations = 15,
74
- convergenceM = 1e-4
75
- } = opts;
76
- const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
77
- const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
78
- if (all.length < minSats(all)) return null;
79
- const gaussNewton = (prns) => {
80
- const systems = [...new Set(prns.map((p) => p[0]))].sort();
81
- const dim = 3 + systems.length;
82
- const sysIndex = new Map(systems.map((s, i) => [s, 3 + i]));
83
- let x2 = 0;
84
- let y2 = 0;
85
- let z2 = 0;
86
- const clock2 = new Map(systems.map((s) => [s, 0]));
87
- const residuals2 = {};
88
- let iterations2 = 0;
89
- let converged2 = false;
90
- for (let iter = 0; iter < maxIterations; iter++) {
91
- iterations2 = iter + 1;
92
- const HtH = Array.from(
93
- { length: dim },
94
- () => new Array(dim).fill(0)
95
- );
96
- const Htv = new Array(dim).fill(0);
97
- let rows = 0;
98
- for (const k of Object.keys(residuals2)) delete residuals2[k];
99
- for (const prn of prns) {
100
- const psr = pseudoranges.get(prn);
101
- const eph = ephemerides.get(prn);
102
- const sys = prn[0];
103
- const clk = clock2.get(sys);
104
- const tTx = timeMs - psr / C_LIGHT * 1e3;
105
- const sat = computeSatPosition(eph, tTx);
106
- if (!Number.isFinite(sat.x)) continue;
107
- const dts = satClockCorrection(eph, tTx);
108
- const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
109
- const [sx, sy, sz] = sagnac(sat, travel);
110
- const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
111
- const ux = (x2 - sx) / rho;
112
- const uy = (y2 - sy) / rho;
113
- const uz = (z2 - sz) / rho;
114
- let elev = Math.PI / 2;
115
- let weight = 1;
116
- const positionSane = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
117
- if (positionSane) {
118
- const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
119
- elev = azel.el;
120
- if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
121
- const sinEl = Math.max(Math.sin(elev), 0.1);
122
- weight = sinEl * sinEl;
123
- }
124
- const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
125
- const predicted = rho + clk - C_LIGHT * dts + tropo;
126
- const v = psr - predicted;
127
- const h = new Array(dim).fill(0);
128
- h[0] = ux;
129
- h[1] = uy;
130
- h[2] = uz;
131
- h[sysIndex.get(sys)] = 1;
132
- for (let i = 0; i < dim; i++) {
133
- for (let j = 0; j < dim; j++) HtH[i][j] += weight * h[i] * h[j];
134
- Htv[i] += weight * h[i] * v;
135
- }
136
- residuals2[prn] = v;
137
- rows++;
138
- }
139
- if (rows < dim) return null;
140
- const dx = solveLinear(HtH, Htv);
141
- if (!dx) return null;
142
- x2 += dx[0];
143
- y2 += dx[1];
144
- z2 += dx[2];
145
- for (const s of systems)
146
- clock2.set(s, clock2.get(s) + dx[sysIndex.get(s)]);
147
- if (Math.hypot(dx[0], dx[1], dx[2]) < convergenceM) {
148
- converged2 = true;
149
- break;
150
- }
151
- }
152
- return { x: x2, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
153
- };
154
- const REJECT_THRESHOLD_M = 50;
155
- let candidates = all;
156
- const rejected = [];
157
- let inner = null;
158
- for (let round = 0; round <= all.length; round++) {
159
- inner = gaussNewton(candidates);
160
- if (!inner) return null;
161
- const vals = Object.entries(inner.residuals);
162
- let worst = null;
163
- let worstAbs = REJECT_THRESHOLD_M;
164
- for (const [prn, v] of vals) {
165
- if (Math.abs(v) > worstAbs) {
166
- worstAbs = Math.abs(v);
167
- worst = prn;
168
- }
169
- }
170
- if (!worst) break;
171
- const remaining = candidates.filter((p) => p !== worst);
172
- if (remaining.length < minSats(remaining)) break;
173
- rejected.push(worst);
174
- candidates = remaining;
175
- }
176
- if (!inner) return null;
177
- const { x, y, z, clock, residuals, iterations, converged } = inner;
178
- const used = [];
179
- const azels = [];
180
- for (const prn of candidates) {
181
- if (!(prn in residuals)) continue;
182
- const eph = ephemerides.get(prn);
183
- const sat = computeSatPosition(eph, timeMs);
184
- if (!Number.isFinite(sat.x)) continue;
185
- const azel = ecefToAzEl(x, y, z, sat.x, sat.y, sat.z);
186
- if (azel.el >= elevationMaskDeg * Math.PI / 180) {
187
- used.push(prn);
188
- azels.push(azel);
189
- }
190
- }
191
- return {
192
- position: [x, y, z],
193
- clockBias: Object.fromEntries(clock),
194
- usedSatellites: used,
195
- rejectedSatellites: rejected,
196
- residuals,
197
- dop: computeDop(azels),
198
- iterations,
199
- converged
200
- };
201
- }
11
+ import "./chunk-W5WKEV7U.js";
202
12
  export {
203
13
  ionoFree,
204
14
  satClockCorrection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
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,