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/README.md +10 -0
- package/dist/chunk-4KOXMHUW.js +223 -0
- package/dist/{chunk-VANVV4F7.js → chunk-CC2R4JGS.js} +69 -7
- package/dist/chunk-USHP4IND.js +206 -0
- package/dist/frames.js +6 -216
- package/dist/index.cjs +496 -9
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +23 -1
- package/dist/orbit.cjs +70 -6
- package/dist/orbit.d.cts +40 -1
- package/dist/orbit.d.ts +40 -1
- package/dist/orbit.js +5 -1
- package/dist/positioning.cjs +6 -3
- package/dist/positioning.d.cts +11 -3
- package/dist/positioning.d.ts +11 -3
- package/dist/positioning.js +6 -196
- package/package.json +1 -1
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-
|
|
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
|
-
|
|
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
|
|
488
|
+
const moscow = new Date(info.lastReceived + 3 * 3600 * 1e3);
|
|
486
489
|
const utcMidnight = Date.UTC(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
+
moscow.getUTCFullYear(),
|
|
491
|
+
moscow.getUTCMonth(),
|
|
492
|
+
moscow.getUTCDate()
|
|
490
493
|
);
|
|
491
|
-
const tbSec = (info.tb ?? 0) *
|
|
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-
|
|
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,
|
package/dist/positioning.cjs
CHANGED
|
@@ -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
|
|
464
|
+
const dtsClock = satClockCorrection(eph, tTx);
|
|
465
|
+
const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
|
|
464
466
|
if (!Number.isFinite(sat.x)) continue;
|
|
465
|
-
const
|
|
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);
|
package/dist/positioning.d.cts
CHANGED
|
@@ -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,
|
|
13
|
-
*
|
|
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. */
|
package/dist/positioning.d.ts
CHANGED
|
@@ -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,
|
|
13
|
-
*
|
|
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. */
|
package/dist/positioning.js
CHANGED
|
@@ -1,204 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-
|
|
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.
|
|
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,
|