gnss-js 1.10.0 → 1.11.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.
- package/dist/{chunk-AK6LNWZX.js → chunk-DMNFB7Q6.js} +57 -7
- package/dist/{chunk-ZTFKRZMM.js → chunk-LKFSXVIZ.js} +60 -4
- package/dist/index.cjs +115 -10
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -2
- package/dist/orbit.cjs +59 -7
- package/dist/orbit.d.cts +12 -2
- package/dist/orbit.d.ts +12 -2
- package/dist/orbit.js +5 -1
- package/dist/positioning.cjs +101 -10
- package/dist/positioning.d.cts +44 -4
- package/dist/positioning.d.ts +44 -4
- package/dist/positioning.js +4 -2
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
START_GPS_TIME
|
|
10
10
|
} from "./chunk-LEEU5OIO.js";
|
|
11
11
|
import {
|
|
12
|
+
C_LIGHT,
|
|
12
13
|
OMEGA_E
|
|
13
14
|
} from "./chunk-FIEWO4J4.js";
|
|
14
15
|
|
|
@@ -27,6 +28,7 @@ function gmForSystem(sys) {
|
|
|
27
28
|
if (sys === "C") return GM_BDS;
|
|
28
29
|
return GM_GPS;
|
|
29
30
|
}
|
|
31
|
+
var geoVelocityPass = false;
|
|
30
32
|
function keplerPosition(eph, t) {
|
|
31
33
|
const GM = gmForSystem(eph.system);
|
|
32
34
|
const a = eph.sqrtA * eph.sqrtA;
|
|
@@ -54,8 +56,16 @@ function keplerPosition(eph, t) {
|
|
|
54
56
|
const uk = phik + duk;
|
|
55
57
|
const rk = a * (1 - eph.e * cosE) + drk;
|
|
56
58
|
const ik = eph.i0 + dik + eph.idot * tk;
|
|
59
|
+
const EkDot = n / (1 - eph.e * cosE);
|
|
60
|
+
const vkDot = EkDot * Math.sqrt(1 - eph.e * eph.e) / (1 - eph.e * cosE);
|
|
61
|
+
const dukDot = 2 * vkDot * (eph.cus * cos2phi - eph.cuc * sin2phi);
|
|
62
|
+
const drkDot = a * eph.e * sinE * EkDot + 2 * vkDot * (eph.crs * cos2phi - eph.crc * sin2phi);
|
|
63
|
+
const dikDot = eph.idot + 2 * vkDot * (eph.cis * cos2phi - eph.cic * sin2phi);
|
|
64
|
+
const ukDot = vkDot + dukDot;
|
|
57
65
|
const xp = rk * Math.cos(uk);
|
|
58
66
|
const yp = rk * Math.sin(uk);
|
|
67
|
+
const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
|
|
68
|
+
const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
|
|
59
69
|
const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
|
|
60
70
|
if (isBdsGeo) {
|
|
61
71
|
const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
|
|
@@ -71,23 +81,43 @@ function keplerPosition(eph, t) {
|
|
|
71
81
|
const sinPhi = Math.sin(phi);
|
|
72
82
|
const COS5 = Math.cos(-5 * Math.PI / 180);
|
|
73
83
|
const SIN5 = Math.sin(-5 * Math.PI / 180);
|
|
84
|
+
const px = xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5;
|
|
85
|
+
const py = -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5;
|
|
86
|
+
const pz = -yg * SIN5 + zg * COS5;
|
|
87
|
+
if (geoVelocityPass) {
|
|
88
|
+
return { prn: eph.prn, x: px, y: py, z: pz, vx: 0, vy: 0, vz: 0 };
|
|
89
|
+
}
|
|
90
|
+
geoVelocityPass = true;
|
|
91
|
+
const h = 0.5;
|
|
92
|
+
const pm = keplerPosition(eph, t - h);
|
|
93
|
+
const pp = keplerPosition(eph, t + h);
|
|
94
|
+
geoVelocityPass = false;
|
|
74
95
|
return {
|
|
75
96
|
prn: eph.prn,
|
|
76
|
-
x:
|
|
77
|
-
y:
|
|
78
|
-
z:
|
|
97
|
+
x: px,
|
|
98
|
+
y: py,
|
|
99
|
+
z: pz,
|
|
100
|
+
vx: (pp.x - pm.x) / (2 * h),
|
|
101
|
+
vy: (pp.y - pm.y) / (2 * h),
|
|
102
|
+
vz: (pp.z - pm.z) / (2 * h)
|
|
79
103
|
};
|
|
80
104
|
}
|
|
81
105
|
const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
|
|
106
|
+
const omegakDot = eph.omegaDot - OMEGA_E;
|
|
82
107
|
const cosO = Math.cos(omegak);
|
|
83
108
|
const sinO = Math.sin(omegak);
|
|
84
109
|
const cosI = Math.cos(ik);
|
|
85
110
|
const sinI = Math.sin(ik);
|
|
111
|
+
const x = xp * cosO - yp * cosI * sinO;
|
|
112
|
+
const y = xp * sinO + yp * cosI * cosO;
|
|
86
113
|
return {
|
|
87
114
|
prn: eph.prn,
|
|
88
|
-
x
|
|
89
|
-
y
|
|
90
|
-
z: yp * sinI
|
|
115
|
+
x,
|
|
116
|
+
y,
|
|
117
|
+
z: yp * sinI,
|
|
118
|
+
vx: xpDot * cosO - ypDot * cosI * sinO + yp * sinI * sinO * dikDot - omegakDot * y,
|
|
119
|
+
vy: xpDot * sinO + ypDot * cosI * cosO - yp * sinI * cosO * dikDot + omegakDot * x,
|
|
120
|
+
vz: ypDot * sinI + yp * cosI * dikDot
|
|
91
121
|
};
|
|
92
122
|
}
|
|
93
123
|
function gloDerivatives(state, acc) {
|
|
@@ -141,7 +171,25 @@ function glonassPosition(eph, tUtc) {
|
|
|
141
171
|
if (Math.abs(remainder) > 1e-3) {
|
|
142
172
|
state = rk4Step(state, acc, remainder);
|
|
143
173
|
}
|
|
144
|
-
return {
|
|
174
|
+
return {
|
|
175
|
+
prn: eph.prn,
|
|
176
|
+
x: state[0],
|
|
177
|
+
y: state[1],
|
|
178
|
+
z: state[2],
|
|
179
|
+
vx: state[3],
|
|
180
|
+
vy: state[4],
|
|
181
|
+
vz: state[5]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function rangeRate(sat, rx, rxVel = [0, 0, 0]) {
|
|
185
|
+
const dx = sat.x - rx[0];
|
|
186
|
+
const dy = sat.y - rx[1];
|
|
187
|
+
const dz = sat.z - rx[2];
|
|
188
|
+
const rho = Math.hypot(dx, dy, dz);
|
|
189
|
+
return ((sat.vx - rxVel[0]) * dx + (sat.vy - rxVel[1]) * dy + (sat.vz - rxVel[2]) * dz) / rho;
|
|
190
|
+
}
|
|
191
|
+
function dopplerHz(rangeRateMs, freqHz) {
|
|
192
|
+
return -rangeRateMs * freqHz / C_LIGHT;
|
|
145
193
|
}
|
|
146
194
|
function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
|
|
147
195
|
const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
@@ -546,6 +594,8 @@ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, el
|
|
|
546
594
|
export {
|
|
547
595
|
keplerPosition,
|
|
548
596
|
glonassPosition,
|
|
597
|
+
rangeRate,
|
|
598
|
+
dopplerHz,
|
|
549
599
|
ecefToAzEl,
|
|
550
600
|
computeDop,
|
|
551
601
|
selectEphemeris,
|
|
@@ -2,12 +2,46 @@ import {
|
|
|
2
2
|
computeDop,
|
|
3
3
|
computeSatPosition,
|
|
4
4
|
ecefToAzEl
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-DMNFB7Q6.js";
|
|
6
|
+
import {
|
|
7
|
+
ecefToGeodetic
|
|
8
|
+
} from "./chunk-37QNKGTC.js";
|
|
6
9
|
import {
|
|
7
10
|
C_LIGHT,
|
|
8
11
|
OMEGA_E
|
|
9
12
|
} from "./chunk-FIEWO4J4.js";
|
|
10
13
|
|
|
14
|
+
// src/positioning/klobuchar.ts
|
|
15
|
+
function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
|
|
16
|
+
const { alpha, beta } = coeffs;
|
|
17
|
+
if (alpha.length < 4 || beta.length < 4) return 0;
|
|
18
|
+
const E = elRad / Math.PI;
|
|
19
|
+
const phiU = latRad / Math.PI;
|
|
20
|
+
const lambdaU = lonRad / Math.PI;
|
|
21
|
+
const psi = 0.0137 / (E + 0.11) - 0.022;
|
|
22
|
+
let phiI = phiU + psi * Math.cos(azRad);
|
|
23
|
+
if (phiI > 0.416) phiI = 0.416;
|
|
24
|
+
else if (phiI < -0.416) phiI = -0.416;
|
|
25
|
+
const lambdaI = lambdaU + psi * Math.sin(azRad) / Math.cos(phiI * Math.PI);
|
|
26
|
+
const phiM = phiI + 0.064 * Math.cos((lambdaI - 1.617) * Math.PI);
|
|
27
|
+
let t = 43200 * lambdaI + gpsTowSec;
|
|
28
|
+
t %= 86400;
|
|
29
|
+
if (t < 0) t += 86400;
|
|
30
|
+
let amp = 0;
|
|
31
|
+
let per = 0;
|
|
32
|
+
for (let n = 3; n >= 0; n--) {
|
|
33
|
+
amp = amp * phiM + alpha[n];
|
|
34
|
+
per = per * phiM + beta[n];
|
|
35
|
+
}
|
|
36
|
+
if (amp < 0) amp = 0;
|
|
37
|
+
if (per < 72e3) per = 72e3;
|
|
38
|
+
const F = 1 + 16 * Math.pow(0.53 - E, 3);
|
|
39
|
+
const x = 2 * Math.PI * (t - 50400) / per;
|
|
40
|
+
if (Math.abs(x) >= 1.57) return F * 5e-9;
|
|
41
|
+
const x2 = x * x;
|
|
42
|
+
return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
// src/positioning/index.ts
|
|
12
46
|
var GM_GPS = 3986005e8;
|
|
13
47
|
var F_REL = -4442807633e-19;
|
|
@@ -34,6 +68,16 @@ function ionoFree(p1, p2, f1, f2) {
|
|
|
34
68
|
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
35
69
|
return g * p1 - (g - 1) * p2;
|
|
36
70
|
}
|
|
71
|
+
var GPS_EPOCH_MS_SPP = Date.UTC(1980, 0, 6);
|
|
72
|
+
var PRIMARY_FREQ_HZ = {
|
|
73
|
+
G: 157542e4,
|
|
74
|
+
E: 157542e4,
|
|
75
|
+
J: 157542e4,
|
|
76
|
+
S: 157542e4,
|
|
77
|
+
C: 1561098e3,
|
|
78
|
+
R: 1602e6
|
|
79
|
+
};
|
|
80
|
+
var F_L1 = 157542e4;
|
|
37
81
|
function tropoDelay(elevationRad) {
|
|
38
82
|
const sinEl = Math.sin(elevationRad);
|
|
39
83
|
return 2.47 / (sinEl + 0.0121);
|
|
@@ -68,8 +112,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
68
112
|
troposphere = true,
|
|
69
113
|
maxIterations = 15,
|
|
70
114
|
convergenceM = 1e-4,
|
|
71
|
-
tgd = true
|
|
115
|
+
tgd = true,
|
|
116
|
+
iono
|
|
72
117
|
} = opts;
|
|
118
|
+
const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
|
|
73
119
|
const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
|
|
74
120
|
const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
|
|
75
121
|
if (all.length < minSats(all)) return null;
|
|
@@ -93,6 +139,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
93
139
|
const Htv = new Array(dim).fill(0);
|
|
94
140
|
let rows = 0;
|
|
95
141
|
for (const k of Object.keys(residuals2)) delete residuals2[k];
|
|
142
|
+
const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
|
|
143
|
+
const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
|
|
96
144
|
for (const prn of prns) {
|
|
97
145
|
const psr = pseudoranges.get(prn);
|
|
98
146
|
const eph = ephemerides.get(prn);
|
|
@@ -111,17 +159,24 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
111
159
|
const uy = (y2 - sy) / rho;
|
|
112
160
|
const uz = (z2 - sz) / rho;
|
|
113
161
|
let elev = Math.PI / 2;
|
|
162
|
+
let azim = 0;
|
|
114
163
|
let weight = 1;
|
|
115
|
-
const positionSane =
|
|
164
|
+
const positionSane = positionSaneIter;
|
|
116
165
|
if (positionSane) {
|
|
117
166
|
const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
|
|
118
167
|
elev = azel.el;
|
|
168
|
+
azim = azel.az;
|
|
119
169
|
if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
|
|
120
170
|
const sinEl = Math.max(Math.sin(elev), 0.1);
|
|
121
171
|
weight = sinEl * sinEl;
|
|
122
172
|
}
|
|
123
173
|
const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
|
|
124
|
-
|
|
174
|
+
let ionoM = 0;
|
|
175
|
+
if (iono && positionSane) {
|
|
176
|
+
const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
|
|
177
|
+
ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
|
|
178
|
+
}
|
|
179
|
+
const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
|
|
125
180
|
const v = psr - predicted;
|
|
126
181
|
const h = new Array(dim).fill(0);
|
|
127
182
|
h[0] = ux;
|
|
@@ -200,6 +255,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
200
255
|
}
|
|
201
256
|
|
|
202
257
|
export {
|
|
258
|
+
klobucharDelay,
|
|
203
259
|
satClockCorrection,
|
|
204
260
|
ionoFree,
|
|
205
261
|
solveSpp
|
package/dist/index.cjs
CHANGED
|
@@ -128,6 +128,7 @@ __export(src_exports, {
|
|
|
128
128
|
deg2dms: () => deg2dms,
|
|
129
129
|
deg2rad: () => deg2rad,
|
|
130
130
|
detrendIonoArcs: () => detrendIonoArcs,
|
|
131
|
+
dopplerHz: () => dopplerHz,
|
|
131
132
|
ecefToAzEl: () => ecefToAzEl,
|
|
132
133
|
ecefToGeodetic: () => ecefToGeodetic,
|
|
133
134
|
ephInfoToEphemeris: () => ephInfoToEphemeris,
|
|
@@ -200,6 +201,7 @@ __export(src_exports, {
|
|
|
200
201
|
horizonDistance: () => horizonDistance,
|
|
201
202
|
ionoFree: () => ionoFree,
|
|
202
203
|
keplerPosition: () => keplerPosition,
|
|
204
|
+
klobucharDelay: () => klobucharDelay,
|
|
203
205
|
maskRadForAzimuth: () => maskRadForAzimuth,
|
|
204
206
|
msmEpochToDate: () => msmEpochToDate,
|
|
205
207
|
navTimesFromEph: () => navTimesFromEph,
|
|
@@ -219,6 +221,7 @@ __export(src_exports, {
|
|
|
219
221
|
phiBOCs: () => phiBOCs,
|
|
220
222
|
phiBPSK: () => phiBPSK,
|
|
221
223
|
rad2deg: () => rad2deg,
|
|
224
|
+
rangeRate: () => rangeRate,
|
|
222
225
|
readString: () => readString,
|
|
223
226
|
reportDecodeError: () => reportDecodeError,
|
|
224
227
|
resetGloFreqCache: () => resetGloFreqCache,
|
|
@@ -4104,6 +4107,7 @@ function gmForSystem(sys) {
|
|
|
4104
4107
|
if (sys === "C") return GM_BDS;
|
|
4105
4108
|
return GM_GPS;
|
|
4106
4109
|
}
|
|
4110
|
+
var geoVelocityPass = false;
|
|
4107
4111
|
function keplerPosition(eph, t) {
|
|
4108
4112
|
const GM = gmForSystem(eph.system);
|
|
4109
4113
|
const a = eph.sqrtA * eph.sqrtA;
|
|
@@ -4131,8 +4135,16 @@ function keplerPosition(eph, t) {
|
|
|
4131
4135
|
const uk = phik + duk;
|
|
4132
4136
|
const rk = a * (1 - eph.e * cosE) + drk;
|
|
4133
4137
|
const ik = eph.i0 + dik + eph.idot * tk;
|
|
4138
|
+
const EkDot = n / (1 - eph.e * cosE);
|
|
4139
|
+
const vkDot = EkDot * Math.sqrt(1 - eph.e * eph.e) / (1 - eph.e * cosE);
|
|
4140
|
+
const dukDot = 2 * vkDot * (eph.cus * cos2phi - eph.cuc * sin2phi);
|
|
4141
|
+
const drkDot = a * eph.e * sinE * EkDot + 2 * vkDot * (eph.crs * cos2phi - eph.crc * sin2phi);
|
|
4142
|
+
const dikDot = eph.idot + 2 * vkDot * (eph.cis * cos2phi - eph.cic * sin2phi);
|
|
4143
|
+
const ukDot = vkDot + dukDot;
|
|
4134
4144
|
const xp = rk * Math.cos(uk);
|
|
4135
4145
|
const yp = rk * Math.sin(uk);
|
|
4146
|
+
const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
|
|
4147
|
+
const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
|
|
4136
4148
|
const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
|
|
4137
4149
|
if (isBdsGeo) {
|
|
4138
4150
|
const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
|
|
@@ -4148,23 +4160,43 @@ function keplerPosition(eph, t) {
|
|
|
4148
4160
|
const sinPhi = Math.sin(phi);
|
|
4149
4161
|
const COS5 = Math.cos(-5 * Math.PI / 180);
|
|
4150
4162
|
const SIN5 = Math.sin(-5 * Math.PI / 180);
|
|
4163
|
+
const px = xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5;
|
|
4164
|
+
const py = -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5;
|
|
4165
|
+
const pz = -yg * SIN5 + zg * COS5;
|
|
4166
|
+
if (geoVelocityPass) {
|
|
4167
|
+
return { prn: eph.prn, x: px, y: py, z: pz, vx: 0, vy: 0, vz: 0 };
|
|
4168
|
+
}
|
|
4169
|
+
geoVelocityPass = true;
|
|
4170
|
+
const h = 0.5;
|
|
4171
|
+
const pm = keplerPosition(eph, t - h);
|
|
4172
|
+
const pp = keplerPosition(eph, t + h);
|
|
4173
|
+
geoVelocityPass = false;
|
|
4151
4174
|
return {
|
|
4152
4175
|
prn: eph.prn,
|
|
4153
|
-
x:
|
|
4154
|
-
y:
|
|
4155
|
-
z:
|
|
4176
|
+
x: px,
|
|
4177
|
+
y: py,
|
|
4178
|
+
z: pz,
|
|
4179
|
+
vx: (pp.x - pm.x) / (2 * h),
|
|
4180
|
+
vy: (pp.y - pm.y) / (2 * h),
|
|
4181
|
+
vz: (pp.z - pm.z) / (2 * h)
|
|
4156
4182
|
};
|
|
4157
4183
|
}
|
|
4158
4184
|
const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
|
|
4185
|
+
const omegakDot = eph.omegaDot - OMEGA_E;
|
|
4159
4186
|
const cosO = Math.cos(omegak);
|
|
4160
4187
|
const sinO = Math.sin(omegak);
|
|
4161
4188
|
const cosI = Math.cos(ik);
|
|
4162
4189
|
const sinI = Math.sin(ik);
|
|
4190
|
+
const x = xp * cosO - yp * cosI * sinO;
|
|
4191
|
+
const y = xp * sinO + yp * cosI * cosO;
|
|
4163
4192
|
return {
|
|
4164
4193
|
prn: eph.prn,
|
|
4165
|
-
x
|
|
4166
|
-
y
|
|
4167
|
-
z: yp * sinI
|
|
4194
|
+
x,
|
|
4195
|
+
y,
|
|
4196
|
+
z: yp * sinI,
|
|
4197
|
+
vx: xpDot * cosO - ypDot * cosI * sinO + yp * sinI * sinO * dikDot - omegakDot * y,
|
|
4198
|
+
vy: xpDot * sinO + ypDot * cosI * cosO - yp * sinI * cosO * dikDot + omegakDot * x,
|
|
4199
|
+
vz: ypDot * sinI + yp * cosI * dikDot
|
|
4168
4200
|
};
|
|
4169
4201
|
}
|
|
4170
4202
|
function gloDerivatives(state, acc) {
|
|
@@ -4218,7 +4250,25 @@ function glonassPosition(eph, tUtc) {
|
|
|
4218
4250
|
if (Math.abs(remainder) > 1e-3) {
|
|
4219
4251
|
state = rk4Step(state, acc, remainder);
|
|
4220
4252
|
}
|
|
4221
|
-
return {
|
|
4253
|
+
return {
|
|
4254
|
+
prn: eph.prn,
|
|
4255
|
+
x: state[0],
|
|
4256
|
+
y: state[1],
|
|
4257
|
+
z: state[2],
|
|
4258
|
+
vx: state[3],
|
|
4259
|
+
vy: state[4],
|
|
4260
|
+
vz: state[5]
|
|
4261
|
+
};
|
|
4262
|
+
}
|
|
4263
|
+
function rangeRate(sat, rx, rxVel = [0, 0, 0]) {
|
|
4264
|
+
const dx = sat.x - rx[0];
|
|
4265
|
+
const dy = sat.y - rx[1];
|
|
4266
|
+
const dz = sat.z - rx[2];
|
|
4267
|
+
const rho = Math.hypot(dx, dy, dz);
|
|
4268
|
+
return ((sat.vx - rxVel[0]) * dx + (sat.vy - rxVel[1]) * dy + (sat.vz - rxVel[2]) * dz) / rho;
|
|
4269
|
+
}
|
|
4270
|
+
function dopplerHz(rangeRateMs, freqHz) {
|
|
4271
|
+
return -rangeRateMs * freqHz / C_LIGHT;
|
|
4222
4272
|
}
|
|
4223
4273
|
function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
|
|
4224
4274
|
const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
@@ -4837,6 +4887,37 @@ function dateToEpoch(date) {
|
|
|
4837
4887
|
return year + (date.getTime() - start) / (end - start);
|
|
4838
4888
|
}
|
|
4839
4889
|
|
|
4890
|
+
// src/positioning/klobuchar.ts
|
|
4891
|
+
function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
|
|
4892
|
+
const { alpha, beta } = coeffs;
|
|
4893
|
+
if (alpha.length < 4 || beta.length < 4) return 0;
|
|
4894
|
+
const E = elRad / Math.PI;
|
|
4895
|
+
const phiU = latRad / Math.PI;
|
|
4896
|
+
const lambdaU = lonRad / Math.PI;
|
|
4897
|
+
const psi = 0.0137 / (E + 0.11) - 0.022;
|
|
4898
|
+
let phiI = phiU + psi * Math.cos(azRad);
|
|
4899
|
+
if (phiI > 0.416) phiI = 0.416;
|
|
4900
|
+
else if (phiI < -0.416) phiI = -0.416;
|
|
4901
|
+
const lambdaI = lambdaU + psi * Math.sin(azRad) / Math.cos(phiI * Math.PI);
|
|
4902
|
+
const phiM = phiI + 0.064 * Math.cos((lambdaI - 1.617) * Math.PI);
|
|
4903
|
+
let t = 43200 * lambdaI + gpsTowSec;
|
|
4904
|
+
t %= 86400;
|
|
4905
|
+
if (t < 0) t += 86400;
|
|
4906
|
+
let amp = 0;
|
|
4907
|
+
let per = 0;
|
|
4908
|
+
for (let n = 3; n >= 0; n--) {
|
|
4909
|
+
amp = amp * phiM + alpha[n];
|
|
4910
|
+
per = per * phiM + beta[n];
|
|
4911
|
+
}
|
|
4912
|
+
if (amp < 0) amp = 0;
|
|
4913
|
+
if (per < 72e3) per = 72e3;
|
|
4914
|
+
const F = 1 + 16 * Math.pow(0.53 - E, 3);
|
|
4915
|
+
const x = 2 * Math.PI * (t - 50400) / per;
|
|
4916
|
+
if (Math.abs(x) >= 1.57) return F * 5e-9;
|
|
4917
|
+
const x2 = x * x;
|
|
4918
|
+
return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4840
4921
|
// src/positioning/index.ts
|
|
4841
4922
|
var GM_GPS2 = 3986005e8;
|
|
4842
4923
|
var F_REL = -4442807633e-19;
|
|
@@ -4863,6 +4944,16 @@ function ionoFree(p1, p2, f1, f2) {
|
|
|
4863
4944
|
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
4864
4945
|
return g * p1 - (g - 1) * p2;
|
|
4865
4946
|
}
|
|
4947
|
+
var GPS_EPOCH_MS_SPP = Date.UTC(1980, 0, 6);
|
|
4948
|
+
var PRIMARY_FREQ_HZ = {
|
|
4949
|
+
G: 157542e4,
|
|
4950
|
+
E: 157542e4,
|
|
4951
|
+
J: 157542e4,
|
|
4952
|
+
S: 157542e4,
|
|
4953
|
+
C: 1561098e3,
|
|
4954
|
+
R: 1602e6
|
|
4955
|
+
};
|
|
4956
|
+
var F_L1 = 157542e4;
|
|
4866
4957
|
function tropoDelay(elevationRad) {
|
|
4867
4958
|
const sinEl = Math.sin(elevationRad);
|
|
4868
4959
|
return 2.47 / (sinEl + 0.0121);
|
|
@@ -4897,8 +4988,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
4897
4988
|
troposphere = true,
|
|
4898
4989
|
maxIterations = 15,
|
|
4899
4990
|
convergenceM = 1e-4,
|
|
4900
|
-
tgd = true
|
|
4991
|
+
tgd = true,
|
|
4992
|
+
iono
|
|
4901
4993
|
} = opts;
|
|
4994
|
+
const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
|
|
4902
4995
|
const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
|
|
4903
4996
|
const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
|
|
4904
4997
|
if (all.length < minSats(all)) return null;
|
|
@@ -4922,6 +5015,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
4922
5015
|
const Htv = new Array(dim).fill(0);
|
|
4923
5016
|
let rows = 0;
|
|
4924
5017
|
for (const k of Object.keys(residuals2)) delete residuals2[k];
|
|
5018
|
+
const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
|
|
5019
|
+
const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
|
|
4925
5020
|
for (const prn of prns) {
|
|
4926
5021
|
const psr = pseudoranges.get(prn);
|
|
4927
5022
|
const eph = ephemerides.get(prn);
|
|
@@ -4940,17 +5035,24 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
4940
5035
|
const uy = (y2 - sy) / rho;
|
|
4941
5036
|
const uz = (z2 - sz) / rho;
|
|
4942
5037
|
let elev = Math.PI / 2;
|
|
5038
|
+
let azim = 0;
|
|
4943
5039
|
let weight = 1;
|
|
4944
|
-
const positionSane =
|
|
5040
|
+
const positionSane = positionSaneIter;
|
|
4945
5041
|
if (positionSane) {
|
|
4946
5042
|
const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
|
|
4947
5043
|
elev = azel.el;
|
|
5044
|
+
azim = azel.az;
|
|
4948
5045
|
if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
|
|
4949
5046
|
const sinEl = Math.max(Math.sin(elev), 0.1);
|
|
4950
5047
|
weight = sinEl * sinEl;
|
|
4951
5048
|
}
|
|
4952
5049
|
const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
|
|
4953
|
-
|
|
5050
|
+
let ionoM = 0;
|
|
5051
|
+
if (iono && positionSane) {
|
|
5052
|
+
const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
|
|
5053
|
+
ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
|
|
5054
|
+
}
|
|
5055
|
+
const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
|
|
4954
5056
|
const v = psr - predicted;
|
|
4955
5057
|
const h = new Array(dim).fill(0);
|
|
4956
5058
|
h[0] = ux;
|
|
@@ -7396,6 +7498,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
7396
7498
|
deg2dms,
|
|
7397
7499
|
deg2rad,
|
|
7398
7500
|
detrendIonoArcs,
|
|
7501
|
+
dopplerHz,
|
|
7399
7502
|
ecefToAzEl,
|
|
7400
7503
|
ecefToGeodetic,
|
|
7401
7504
|
ephInfoToEphemeris,
|
|
@@ -7468,6 +7571,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
7468
7571
|
horizonDistance,
|
|
7469
7572
|
ionoFree,
|
|
7470
7573
|
keplerPosition,
|
|
7574
|
+
klobucharDelay,
|
|
7471
7575
|
maskRadForAzimuth,
|
|
7472
7576
|
msmEpochToDate,
|
|
7473
7577
|
navTimesFromEph,
|
|
@@ -7487,6 +7591,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
7487
7591
|
phiBOCs,
|
|
7488
7592
|
phiBPSK,
|
|
7489
7593
|
rad2deg,
|
|
7594
|
+
rangeRate,
|
|
7490
7595
|
readString,
|
|
7491
7596
|
reportDecodeError,
|
|
7492
7597
|
resetGloFreqCache,
|
package/dist/index.d.cts
CHANGED
|
@@ -9,9 +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, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, selectEphemeris } from './orbit.cjs';
|
|
12
|
+
export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.cjs';
|
|
13
13
|
export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
|
|
14
|
-
export { SppOptions, SppSolution, ionoFree, satClockCorrection, solveSpp } from './positioning.cjs';
|
|
14
|
+
export { KlobucharCoeffs, SppOptions, SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveSpp } from './positioning.cjs';
|
|
15
15
|
export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
|
|
16
16
|
export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.cjs';
|
|
17
17
|
export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -9,9 +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.js';
|
|
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.js';
|
|
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.js';
|
|
12
|
-
export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, selectEphemeris } from './orbit.js';
|
|
12
|
+
export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.js';
|
|
13
13
|
export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
|
|
14
|
-
export { SppOptions, SppSolution, ionoFree, satClockCorrection, solveSpp } from './positioning.js';
|
|
14
|
+
export { KlobucharCoeffs, SppOptions, SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveSpp } from './positioning.js';
|
|
15
15
|
export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
|
|
16
16
|
export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.js';
|
|
17
17
|
export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.js';
|
package/dist/index.js
CHANGED
|
@@ -100,23 +100,26 @@ import {
|
|
|
100
100
|
} from "./chunk-4KOXMHUW.js";
|
|
101
101
|
import {
|
|
102
102
|
ionoFree,
|
|
103
|
+
klobucharDelay,
|
|
103
104
|
satClockCorrection,
|
|
104
105
|
solveSpp
|
|
105
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-LKFSXVIZ.js";
|
|
106
107
|
import {
|
|
107
108
|
computeAllPositions,
|
|
108
109
|
computeDop,
|
|
109
110
|
computeLiveSkyPositions,
|
|
110
111
|
computeSatPosition,
|
|
111
112
|
computeVisibility,
|
|
113
|
+
dopplerHz,
|
|
112
114
|
ecefToAzEl,
|
|
113
115
|
ephInfoToEphemeris,
|
|
114
116
|
glonassPosition,
|
|
115
117
|
keplerPosition,
|
|
116
118
|
maskRadForAzimuth,
|
|
117
119
|
navTimesFromEph,
|
|
120
|
+
rangeRate,
|
|
118
121
|
selectEphemeris
|
|
119
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-DMNFB7Q6.js";
|
|
120
123
|
import {
|
|
121
124
|
getBdsTime,
|
|
122
125
|
getDateFromBdsTime,
|
|
@@ -361,6 +364,7 @@ export {
|
|
|
361
364
|
deg2dms,
|
|
362
365
|
deg2rad,
|
|
363
366
|
detrendIonoArcs,
|
|
367
|
+
dopplerHz,
|
|
364
368
|
ecefToAzEl,
|
|
365
369
|
ecefToGeodetic,
|
|
366
370
|
ephInfoToEphemeris,
|
|
@@ -433,6 +437,7 @@ export {
|
|
|
433
437
|
horizonDistance,
|
|
434
438
|
ionoFree,
|
|
435
439
|
keplerPosition,
|
|
440
|
+
klobucharDelay,
|
|
436
441
|
maskRadForAzimuth,
|
|
437
442
|
msmEpochToDate,
|
|
438
443
|
navTimesFromEph,
|
|
@@ -452,6 +457,7 @@ export {
|
|
|
452
457
|
phiBOCs,
|
|
453
458
|
phiBPSK,
|
|
454
459
|
rad2deg,
|
|
460
|
+
rangeRate,
|
|
455
461
|
readString,
|
|
456
462
|
reportDecodeError,
|
|
457
463
|
resetGloFreqCache,
|
package/dist/orbit.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(orbit_exports, {
|
|
|
25
25
|
computeLiveSkyPositions: () => computeLiveSkyPositions,
|
|
26
26
|
computeSatPosition: () => computeSatPosition,
|
|
27
27
|
computeVisibility: () => computeVisibility,
|
|
28
|
+
dopplerHz: () => dopplerHz,
|
|
28
29
|
ecefToAzEl: () => ecefToAzEl,
|
|
29
30
|
ecefToGeodetic: () => ecefToGeodetic,
|
|
30
31
|
ephInfoToEphemeris: () => ephInfoToEphemeris,
|
|
@@ -33,6 +34,7 @@ __export(orbit_exports, {
|
|
|
33
34
|
keplerPosition: () => keplerPosition,
|
|
34
35
|
maskRadForAzimuth: () => maskRadForAzimuth,
|
|
35
36
|
navTimesFromEph: () => navTimesFromEph,
|
|
37
|
+
rangeRate: () => rangeRate,
|
|
36
38
|
selectEphemeris: () => selectEphemeris
|
|
37
39
|
});
|
|
38
40
|
module.exports = __toCommonJS(orbit_exports);
|
|
@@ -82,6 +84,7 @@ var START_BDS_TIME = /* @__PURE__ */ new Date("2006-01-01T00:00:14Z");
|
|
|
82
84
|
var START_NTP_TIME = /* @__PURE__ */ new Date("1900-01-01T00:00:00Z");
|
|
83
85
|
|
|
84
86
|
// src/constants/gnss.ts
|
|
87
|
+
var C_LIGHT = 299792458;
|
|
85
88
|
var OMEGA_E = 72921151467e-15;
|
|
86
89
|
|
|
87
90
|
// src/time/gnss.ts
|
|
@@ -181,6 +184,7 @@ function gmForSystem(sys) {
|
|
|
181
184
|
if (sys === "C") return GM_BDS;
|
|
182
185
|
return GM_GPS;
|
|
183
186
|
}
|
|
187
|
+
var geoVelocityPass = false;
|
|
184
188
|
function keplerPosition(eph, t) {
|
|
185
189
|
const GM = gmForSystem(eph.system);
|
|
186
190
|
const a = eph.sqrtA * eph.sqrtA;
|
|
@@ -208,8 +212,16 @@ function keplerPosition(eph, t) {
|
|
|
208
212
|
const uk = phik + duk;
|
|
209
213
|
const rk = a * (1 - eph.e * cosE) + drk;
|
|
210
214
|
const ik = eph.i0 + dik + eph.idot * tk;
|
|
215
|
+
const EkDot = n / (1 - eph.e * cosE);
|
|
216
|
+
const vkDot = EkDot * Math.sqrt(1 - eph.e * eph.e) / (1 - eph.e * cosE);
|
|
217
|
+
const dukDot = 2 * vkDot * (eph.cus * cos2phi - eph.cuc * sin2phi);
|
|
218
|
+
const drkDot = a * eph.e * sinE * EkDot + 2 * vkDot * (eph.crs * cos2phi - eph.crc * sin2phi);
|
|
219
|
+
const dikDot = eph.idot + 2 * vkDot * (eph.cis * cos2phi - eph.cic * sin2phi);
|
|
220
|
+
const ukDot = vkDot + dukDot;
|
|
211
221
|
const xp = rk * Math.cos(uk);
|
|
212
222
|
const yp = rk * Math.sin(uk);
|
|
223
|
+
const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
|
|
224
|
+
const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
|
|
213
225
|
const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
|
|
214
226
|
if (isBdsGeo) {
|
|
215
227
|
const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
|
|
@@ -225,23 +237,43 @@ function keplerPosition(eph, t) {
|
|
|
225
237
|
const sinPhi = Math.sin(phi);
|
|
226
238
|
const COS5 = Math.cos(-5 * Math.PI / 180);
|
|
227
239
|
const SIN5 = Math.sin(-5 * Math.PI / 180);
|
|
240
|
+
const px = xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5;
|
|
241
|
+
const py = -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5;
|
|
242
|
+
const pz = -yg * SIN5 + zg * COS5;
|
|
243
|
+
if (geoVelocityPass) {
|
|
244
|
+
return { prn: eph.prn, x: px, y: py, z: pz, vx: 0, vy: 0, vz: 0 };
|
|
245
|
+
}
|
|
246
|
+
geoVelocityPass = true;
|
|
247
|
+
const h = 0.5;
|
|
248
|
+
const pm = keplerPosition(eph, t - h);
|
|
249
|
+
const pp = keplerPosition(eph, t + h);
|
|
250
|
+
geoVelocityPass = false;
|
|
228
251
|
return {
|
|
229
252
|
prn: eph.prn,
|
|
230
|
-
x:
|
|
231
|
-
y:
|
|
232
|
-
z:
|
|
253
|
+
x: px,
|
|
254
|
+
y: py,
|
|
255
|
+
z: pz,
|
|
256
|
+
vx: (pp.x - pm.x) / (2 * h),
|
|
257
|
+
vy: (pp.y - pm.y) / (2 * h),
|
|
258
|
+
vz: (pp.z - pm.z) / (2 * h)
|
|
233
259
|
};
|
|
234
260
|
}
|
|
235
261
|
const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
|
|
262
|
+
const omegakDot = eph.omegaDot - OMEGA_E;
|
|
236
263
|
const cosO = Math.cos(omegak);
|
|
237
264
|
const sinO = Math.sin(omegak);
|
|
238
265
|
const cosI = Math.cos(ik);
|
|
239
266
|
const sinI = Math.sin(ik);
|
|
267
|
+
const x = xp * cosO - yp * cosI * sinO;
|
|
268
|
+
const y = xp * sinO + yp * cosI * cosO;
|
|
240
269
|
return {
|
|
241
270
|
prn: eph.prn,
|
|
242
|
-
x
|
|
243
|
-
y
|
|
244
|
-
z: yp * sinI
|
|
271
|
+
x,
|
|
272
|
+
y,
|
|
273
|
+
z: yp * sinI,
|
|
274
|
+
vx: xpDot * cosO - ypDot * cosI * sinO + yp * sinI * sinO * dikDot - omegakDot * y,
|
|
275
|
+
vy: xpDot * sinO + ypDot * cosI * cosO - yp * sinI * cosO * dikDot + omegakDot * x,
|
|
276
|
+
vz: ypDot * sinI + yp * cosI * dikDot
|
|
245
277
|
};
|
|
246
278
|
}
|
|
247
279
|
function gloDerivatives(state, acc) {
|
|
@@ -295,7 +327,25 @@ function glonassPosition(eph, tUtc) {
|
|
|
295
327
|
if (Math.abs(remainder) > 1e-3) {
|
|
296
328
|
state = rk4Step(state, acc, remainder);
|
|
297
329
|
}
|
|
298
|
-
return {
|
|
330
|
+
return {
|
|
331
|
+
prn: eph.prn,
|
|
332
|
+
x: state[0],
|
|
333
|
+
y: state[1],
|
|
334
|
+
z: state[2],
|
|
335
|
+
vx: state[3],
|
|
336
|
+
vy: state[4],
|
|
337
|
+
vz: state[5]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function rangeRate(sat, rx, rxVel = [0, 0, 0]) {
|
|
341
|
+
const dx = sat.x - rx[0];
|
|
342
|
+
const dy = sat.y - rx[1];
|
|
343
|
+
const dz = sat.z - rx[2];
|
|
344
|
+
const rho = Math.hypot(dx, dy, dz);
|
|
345
|
+
return ((sat.vx - rxVel[0]) * dx + (sat.vy - rxVel[1]) * dy + (sat.vz - rxVel[2]) * dz) / rho;
|
|
346
|
+
}
|
|
347
|
+
function dopplerHz(rangeRateMs, freqHz) {
|
|
348
|
+
return -rangeRateMs * freqHz / C_LIGHT;
|
|
299
349
|
}
|
|
300
350
|
function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
|
|
301
351
|
const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
@@ -703,6 +753,7 @@ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, el
|
|
|
703
753
|
computeLiveSkyPositions,
|
|
704
754
|
computeSatPosition,
|
|
705
755
|
computeVisibility,
|
|
756
|
+
dopplerHz,
|
|
706
757
|
ecefToAzEl,
|
|
707
758
|
ecefToGeodetic,
|
|
708
759
|
ephInfoToEphemeris,
|
|
@@ -711,5 +762,6 @@ function computeVisibility(ephemerides, rxPos, startMs, endMs, stepSec = 300, el
|
|
|
711
762
|
keplerPosition,
|
|
712
763
|
maskRadForAzimuth,
|
|
713
764
|
navTimesFromEph,
|
|
765
|
+
rangeRate,
|
|
714
766
|
selectEphemeris
|
|
715
767
|
});
|
package/dist/orbit.d.cts
CHANGED
|
@@ -12,6 +12,9 @@ interface SatPosition {
|
|
|
12
12
|
x: number;
|
|
13
13
|
y: number;
|
|
14
14
|
z: number;
|
|
15
|
+
vx: number;
|
|
16
|
+
vy: number;
|
|
17
|
+
vz: number;
|
|
15
18
|
}
|
|
16
19
|
interface SatAzEl {
|
|
17
20
|
prn: string;
|
|
@@ -28,7 +31,7 @@ interface DopValues {
|
|
|
28
31
|
vdop: number;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
|
-
* Compute satellite ECEF position from Keplerian ephemeris.
|
|
34
|
+
* Compute satellite ECEF position and velocity from Keplerian ephemeris.
|
|
32
35
|
* @param eph Broadcast ephemeris
|
|
33
36
|
* @param t GPS time of week in seconds
|
|
34
37
|
*/
|
|
@@ -39,6 +42,13 @@ declare function keplerPosition(eph: KeplerEphemeris, t: number): SatPosition;
|
|
|
39
42
|
* @param tUtc Target time as UTC seconds since Unix epoch
|
|
40
43
|
*/
|
|
41
44
|
declare function glonassPosition(eph: GlonassEphemeris, tUtc: number): SatPosition;
|
|
45
|
+
/**
|
|
46
|
+
* Geometric range rate (m/s, positive = receding) between a satellite
|
|
47
|
+
* with ECEF velocity and a receiver (static by default).
|
|
48
|
+
*/
|
|
49
|
+
declare function rangeRate(sat: SatPosition, rx: [number, number, number], rxVel?: [number, number, number]): number;
|
|
50
|
+
/** Predicted carrier Doppler (Hz) from a range rate at frequency f. */
|
|
51
|
+
declare function dopplerHz(rangeRateMs: number, freqHz: number): number;
|
|
42
52
|
/**
|
|
43
53
|
* Compute azimuth and elevation from receiver ECEF to satellite ECEF.
|
|
44
54
|
* @param rxX Receiver X in meters (ECEF)
|
|
@@ -176,4 +186,4 @@ declare function maskRadForAzimuth(maskDeg: number | number[]): (azRad: number)
|
|
|
176
186
|
*/
|
|
177
187
|
declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, number, number], startMs: number, endMs: number, stepSec?: number, elevationMaskDeg?: number | number[]): VisibilityResult;
|
|
178
188
|
|
|
179
|
-
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, maskRadForAzimuth, navTimesFromEph, selectEphemeris };
|
|
189
|
+
export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris };
|
package/dist/orbit.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ interface SatPosition {
|
|
|
12
12
|
x: number;
|
|
13
13
|
y: number;
|
|
14
14
|
z: number;
|
|
15
|
+
vx: number;
|
|
16
|
+
vy: number;
|
|
17
|
+
vz: number;
|
|
15
18
|
}
|
|
16
19
|
interface SatAzEl {
|
|
17
20
|
prn: string;
|
|
@@ -28,7 +31,7 @@ interface DopValues {
|
|
|
28
31
|
vdop: number;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
|
-
* Compute satellite ECEF position from Keplerian ephemeris.
|
|
34
|
+
* Compute satellite ECEF position and velocity from Keplerian ephemeris.
|
|
32
35
|
* @param eph Broadcast ephemeris
|
|
33
36
|
* @param t GPS time of week in seconds
|
|
34
37
|
*/
|
|
@@ -39,6 +42,13 @@ declare function keplerPosition(eph: KeplerEphemeris, t: number): SatPosition;
|
|
|
39
42
|
* @param tUtc Target time as UTC seconds since Unix epoch
|
|
40
43
|
*/
|
|
41
44
|
declare function glonassPosition(eph: GlonassEphemeris, tUtc: number): SatPosition;
|
|
45
|
+
/**
|
|
46
|
+
* Geometric range rate (m/s, positive = receding) between a satellite
|
|
47
|
+
* with ECEF velocity and a receiver (static by default).
|
|
48
|
+
*/
|
|
49
|
+
declare function rangeRate(sat: SatPosition, rx: [number, number, number], rxVel?: [number, number, number]): number;
|
|
50
|
+
/** Predicted carrier Doppler (Hz) from a range rate at frequency f. */
|
|
51
|
+
declare function dopplerHz(rangeRateMs: number, freqHz: number): number;
|
|
42
52
|
/**
|
|
43
53
|
* Compute azimuth and elevation from receiver ECEF to satellite ECEF.
|
|
44
54
|
* @param rxX Receiver X in meters (ECEF)
|
|
@@ -176,4 +186,4 @@ declare function maskRadForAzimuth(maskDeg: number | number[]): (azRad: number)
|
|
|
176
186
|
*/
|
|
177
187
|
declare function computeVisibility(ephemerides: Ephemeris[], rxPos: [number, number, number], startMs: number, endMs: number, stepSec?: number, elevationMaskDeg?: number | number[]): VisibilityResult;
|
|
178
188
|
|
|
179
|
-
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, maskRadForAzimuth, navTimesFromEph, selectEphemeris };
|
|
189
|
+
export { type AllPositionsData, type DopValues, type EpochSkyData, type SatAzEl, type SatPoint, type SatPosition, type VisibilityPass, type VisibilityResult, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris };
|
package/dist/orbit.js
CHANGED
|
@@ -4,14 +4,16 @@ import {
|
|
|
4
4
|
computeLiveSkyPositions,
|
|
5
5
|
computeSatPosition,
|
|
6
6
|
computeVisibility,
|
|
7
|
+
dopplerHz,
|
|
7
8
|
ecefToAzEl,
|
|
8
9
|
ephInfoToEphemeris,
|
|
9
10
|
glonassPosition,
|
|
10
11
|
keplerPosition,
|
|
11
12
|
maskRadForAzimuth,
|
|
12
13
|
navTimesFromEph,
|
|
14
|
+
rangeRate,
|
|
13
15
|
selectEphemeris
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-DMNFB7Q6.js";
|
|
15
17
|
import "./chunk-HVXYFUCB.js";
|
|
16
18
|
import {
|
|
17
19
|
ecefToGeodetic,
|
|
@@ -26,6 +28,7 @@ export {
|
|
|
26
28
|
computeLiveSkyPositions,
|
|
27
29
|
computeSatPosition,
|
|
28
30
|
computeVisibility,
|
|
31
|
+
dopplerHz,
|
|
29
32
|
ecefToAzEl,
|
|
30
33
|
ecefToGeodetic,
|
|
31
34
|
ephInfoToEphemeris,
|
|
@@ -34,5 +37,6 @@ export {
|
|
|
34
37
|
keplerPosition,
|
|
35
38
|
maskRadForAzimuth,
|
|
36
39
|
navTimesFromEph,
|
|
40
|
+
rangeRate,
|
|
37
41
|
selectEphemeris
|
|
38
42
|
};
|
package/dist/positioning.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var positioning_exports = {};
|
|
22
22
|
__export(positioning_exports, {
|
|
23
23
|
ionoFree: () => ionoFree,
|
|
24
|
+
klobucharDelay: () => klobucharDelay,
|
|
24
25
|
satClockCorrection: () => satClockCorrection,
|
|
25
26
|
solveSpp: () => solveSpp
|
|
26
27
|
});
|
|
@@ -164,6 +165,7 @@ function gmForSystem(sys) {
|
|
|
164
165
|
if (sys === "C") return GM_BDS;
|
|
165
166
|
return GM_GPS;
|
|
166
167
|
}
|
|
168
|
+
var geoVelocityPass = false;
|
|
167
169
|
function keplerPosition(eph, t) {
|
|
168
170
|
const GM = gmForSystem(eph.system);
|
|
169
171
|
const a = eph.sqrtA * eph.sqrtA;
|
|
@@ -191,8 +193,16 @@ function keplerPosition(eph, t) {
|
|
|
191
193
|
const uk = phik + duk;
|
|
192
194
|
const rk = a * (1 - eph.e * cosE) + drk;
|
|
193
195
|
const ik = eph.i0 + dik + eph.idot * tk;
|
|
196
|
+
const EkDot = n / (1 - eph.e * cosE);
|
|
197
|
+
const vkDot = EkDot * Math.sqrt(1 - eph.e * eph.e) / (1 - eph.e * cosE);
|
|
198
|
+
const dukDot = 2 * vkDot * (eph.cus * cos2phi - eph.cuc * sin2phi);
|
|
199
|
+
const drkDot = a * eph.e * sinE * EkDot + 2 * vkDot * (eph.crs * cos2phi - eph.crc * sin2phi);
|
|
200
|
+
const dikDot = eph.idot + 2 * vkDot * (eph.cis * cos2phi - eph.cic * sin2phi);
|
|
201
|
+
const ukDot = vkDot + dukDot;
|
|
194
202
|
const xp = rk * Math.cos(uk);
|
|
195
203
|
const yp = rk * Math.sin(uk);
|
|
204
|
+
const xpDot = drkDot * Math.cos(uk) - rk * ukDot * Math.sin(uk);
|
|
205
|
+
const ypDot = drkDot * Math.sin(uk) + rk * ukDot * Math.cos(uk);
|
|
196
206
|
const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
|
|
197
207
|
if (isBdsGeo) {
|
|
198
208
|
const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
|
|
@@ -208,23 +218,43 @@ function keplerPosition(eph, t) {
|
|
|
208
218
|
const sinPhi = Math.sin(phi);
|
|
209
219
|
const COS5 = Math.cos(-5 * Math.PI / 180);
|
|
210
220
|
const SIN5 = Math.sin(-5 * Math.PI / 180);
|
|
221
|
+
const px = xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5;
|
|
222
|
+
const py = -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5;
|
|
223
|
+
const pz = -yg * SIN5 + zg * COS5;
|
|
224
|
+
if (geoVelocityPass) {
|
|
225
|
+
return { prn: eph.prn, x: px, y: py, z: pz, vx: 0, vy: 0, vz: 0 };
|
|
226
|
+
}
|
|
227
|
+
geoVelocityPass = true;
|
|
228
|
+
const h = 0.5;
|
|
229
|
+
const pm = keplerPosition(eph, t - h);
|
|
230
|
+
const pp = keplerPosition(eph, t + h);
|
|
231
|
+
geoVelocityPass = false;
|
|
211
232
|
return {
|
|
212
233
|
prn: eph.prn,
|
|
213
|
-
x:
|
|
214
|
-
y:
|
|
215
|
-
z:
|
|
234
|
+
x: px,
|
|
235
|
+
y: py,
|
|
236
|
+
z: pz,
|
|
237
|
+
vx: (pp.x - pm.x) / (2 * h),
|
|
238
|
+
vy: (pp.y - pm.y) / (2 * h),
|
|
239
|
+
vz: (pp.z - pm.z) / (2 * h)
|
|
216
240
|
};
|
|
217
241
|
}
|
|
218
242
|
const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
|
|
243
|
+
const omegakDot = eph.omegaDot - OMEGA_E;
|
|
219
244
|
const cosO = Math.cos(omegak);
|
|
220
245
|
const sinO = Math.sin(omegak);
|
|
221
246
|
const cosI = Math.cos(ik);
|
|
222
247
|
const sinI = Math.sin(ik);
|
|
248
|
+
const x = xp * cosO - yp * cosI * sinO;
|
|
249
|
+
const y = xp * sinO + yp * cosI * cosO;
|
|
223
250
|
return {
|
|
224
251
|
prn: eph.prn,
|
|
225
|
-
x
|
|
226
|
-
y
|
|
227
|
-
z: yp * sinI
|
|
252
|
+
x,
|
|
253
|
+
y,
|
|
254
|
+
z: yp * sinI,
|
|
255
|
+
vx: xpDot * cosO - ypDot * cosI * sinO + yp * sinI * sinO * dikDot - omegakDot * y,
|
|
256
|
+
vy: xpDot * sinO + ypDot * cosI * cosO - yp * sinI * cosO * dikDot + omegakDot * x,
|
|
257
|
+
vz: ypDot * sinI + yp * cosI * dikDot
|
|
228
258
|
};
|
|
229
259
|
}
|
|
230
260
|
function gloDerivatives(state, acc) {
|
|
@@ -278,7 +308,15 @@ function glonassPosition(eph, tUtc) {
|
|
|
278
308
|
if (Math.abs(remainder) > 1e-3) {
|
|
279
309
|
state = rk4Step(state, acc, remainder);
|
|
280
310
|
}
|
|
281
|
-
return {
|
|
311
|
+
return {
|
|
312
|
+
prn: eph.prn,
|
|
313
|
+
x: state[0],
|
|
314
|
+
y: state[1],
|
|
315
|
+
z: state[2],
|
|
316
|
+
vx: state[3],
|
|
317
|
+
vy: state[4],
|
|
318
|
+
vz: state[5]
|
|
319
|
+
};
|
|
282
320
|
}
|
|
283
321
|
function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
|
|
284
322
|
const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
@@ -371,6 +409,37 @@ var MS_PER_WEEK = 7 * 864e5;
|
|
|
371
409
|
var BDS_EPOCH_MS = START_BDS_TIME.getTime();
|
|
372
410
|
var GAL_EPOCH_MS = GPS_EPOCH_MS + 1024 * MS_PER_WEEK;
|
|
373
411
|
|
|
412
|
+
// src/positioning/klobuchar.ts
|
|
413
|
+
function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
|
|
414
|
+
const { alpha, beta } = coeffs;
|
|
415
|
+
if (alpha.length < 4 || beta.length < 4) return 0;
|
|
416
|
+
const E = elRad / Math.PI;
|
|
417
|
+
const phiU = latRad / Math.PI;
|
|
418
|
+
const lambdaU = lonRad / Math.PI;
|
|
419
|
+
const psi = 0.0137 / (E + 0.11) - 0.022;
|
|
420
|
+
let phiI = phiU + psi * Math.cos(azRad);
|
|
421
|
+
if (phiI > 0.416) phiI = 0.416;
|
|
422
|
+
else if (phiI < -0.416) phiI = -0.416;
|
|
423
|
+
const lambdaI = lambdaU + psi * Math.sin(azRad) / Math.cos(phiI * Math.PI);
|
|
424
|
+
const phiM = phiI + 0.064 * Math.cos((lambdaI - 1.617) * Math.PI);
|
|
425
|
+
let t = 43200 * lambdaI + gpsTowSec;
|
|
426
|
+
t %= 86400;
|
|
427
|
+
if (t < 0) t += 86400;
|
|
428
|
+
let amp = 0;
|
|
429
|
+
let per = 0;
|
|
430
|
+
for (let n = 3; n >= 0; n--) {
|
|
431
|
+
amp = amp * phiM + alpha[n];
|
|
432
|
+
per = per * phiM + beta[n];
|
|
433
|
+
}
|
|
434
|
+
if (amp < 0) amp = 0;
|
|
435
|
+
if (per < 72e3) per = 72e3;
|
|
436
|
+
const F = 1 + 16 * Math.pow(0.53 - E, 3);
|
|
437
|
+
const x = 2 * Math.PI * (t - 50400) / per;
|
|
438
|
+
if (Math.abs(x) >= 1.57) return F * 5e-9;
|
|
439
|
+
const x2 = x * x;
|
|
440
|
+
return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
|
|
441
|
+
}
|
|
442
|
+
|
|
374
443
|
// src/positioning/index.ts
|
|
375
444
|
var GM_GPS2 = 3986005e8;
|
|
376
445
|
var F_REL = -4442807633e-19;
|
|
@@ -397,6 +466,16 @@ function ionoFree(p1, p2, f1, f2) {
|
|
|
397
466
|
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
398
467
|
return g * p1 - (g - 1) * p2;
|
|
399
468
|
}
|
|
469
|
+
var GPS_EPOCH_MS_SPP = Date.UTC(1980, 0, 6);
|
|
470
|
+
var PRIMARY_FREQ_HZ = {
|
|
471
|
+
G: 157542e4,
|
|
472
|
+
E: 157542e4,
|
|
473
|
+
J: 157542e4,
|
|
474
|
+
S: 157542e4,
|
|
475
|
+
C: 1561098e3,
|
|
476
|
+
R: 1602e6
|
|
477
|
+
};
|
|
478
|
+
var F_L1 = 157542e4;
|
|
400
479
|
function tropoDelay(elevationRad) {
|
|
401
480
|
const sinEl = Math.sin(elevationRad);
|
|
402
481
|
return 2.47 / (sinEl + 0.0121);
|
|
@@ -431,8 +510,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
431
510
|
troposphere = true,
|
|
432
511
|
maxIterations = 15,
|
|
433
512
|
convergenceM = 1e-4,
|
|
434
|
-
tgd = true
|
|
513
|
+
tgd = true,
|
|
514
|
+
iono
|
|
435
515
|
} = opts;
|
|
516
|
+
const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
|
|
436
517
|
const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
|
|
437
518
|
const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
|
|
438
519
|
if (all.length < minSats(all)) return null;
|
|
@@ -456,6 +537,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
456
537
|
const Htv = new Array(dim).fill(0);
|
|
457
538
|
let rows = 0;
|
|
458
539
|
for (const k of Object.keys(residuals2)) delete residuals2[k];
|
|
540
|
+
const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
|
|
541
|
+
const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
|
|
459
542
|
for (const prn of prns) {
|
|
460
543
|
const psr = pseudoranges.get(prn);
|
|
461
544
|
const eph = ephemerides.get(prn);
|
|
@@ -474,17 +557,24 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
474
557
|
const uy = (y2 - sy) / rho;
|
|
475
558
|
const uz = (z2 - sz) / rho;
|
|
476
559
|
let elev = Math.PI / 2;
|
|
560
|
+
let azim = 0;
|
|
477
561
|
let weight = 1;
|
|
478
|
-
const positionSane =
|
|
562
|
+
const positionSane = positionSaneIter;
|
|
479
563
|
if (positionSane) {
|
|
480
564
|
const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
|
|
481
565
|
elev = azel.el;
|
|
566
|
+
azim = azel.az;
|
|
482
567
|
if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
|
|
483
568
|
const sinEl = Math.max(Math.sin(elev), 0.1);
|
|
484
569
|
weight = sinEl * sinEl;
|
|
485
570
|
}
|
|
486
571
|
const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
|
|
487
|
-
|
|
572
|
+
let ionoM = 0;
|
|
573
|
+
if (iono && positionSane) {
|
|
574
|
+
const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
|
|
575
|
+
ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
|
|
576
|
+
}
|
|
577
|
+
const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
|
|
488
578
|
const v = psr - predicted;
|
|
489
579
|
const h = new Array(dim).fill(0);
|
|
490
580
|
h[0] = ux;
|
|
@@ -564,6 +654,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
564
654
|
// Annotate the CommonJS export names for ESM import in node:
|
|
565
655
|
0 && (module.exports = {
|
|
566
656
|
ionoFree,
|
|
657
|
+
klobucharDelay,
|
|
567
658
|
satClockCorrection,
|
|
568
659
|
solveSpp
|
|
569
660
|
});
|
package/dist/positioning.d.cts
CHANGED
|
@@ -3,6 +3,36 @@ import { DopValues } from './orbit.cjs';
|
|
|
3
3
|
import './ephemeris-Ohl6NAfw.cjs';
|
|
4
4
|
import './ecef-CF0uAysr.cjs';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Klobuchar broadcast ionospheric model (IS-GPS-200, 20.3.3.5.2.5).
|
|
8
|
+
*
|
|
9
|
+
* Evaluates the single-frequency vertical→slant ionospheric delay from
|
|
10
|
+
* the eight coefficients broadcast in the GPS navigation message (and
|
|
11
|
+
* carried in RINEX nav headers as GPSA/GPSB, or ION ALPHA/BETA in
|
|
12
|
+
* RINEX 2). The model captures ~50% RMS of the actual delay — crude,
|
|
13
|
+
* but exactly what a single-frequency receiver applies, which makes it
|
|
14
|
+
* both a useful SPP correction and an honest "what broadcast users
|
|
15
|
+
* get" reference against measured or GIM TEC.
|
|
16
|
+
*/
|
|
17
|
+
/** The eight Klobuchar coefficients from the navigation message. */
|
|
18
|
+
interface KlobucharCoeffs {
|
|
19
|
+
/** GPSA: alpha0..alpha3 (s, s/sc, s/sc², s/sc³). */
|
|
20
|
+
alpha: number[];
|
|
21
|
+
/** GPSB: beta0..beta3 (s, s/sc, s/sc², s/sc³). */
|
|
22
|
+
beta: number[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Slant ionospheric group delay on GPS L1, in seconds.
|
|
26
|
+
*
|
|
27
|
+
* @param coeffs Broadcast alpha/beta coefficients
|
|
28
|
+
* @param latRad Receiver geodetic latitude (radians)
|
|
29
|
+
* @param lonRad Receiver geodetic longitude (radians)
|
|
30
|
+
* @param azRad Satellite azimuth (radians)
|
|
31
|
+
* @param elRad Satellite elevation (radians)
|
|
32
|
+
* @param gpsTowSec GPS time of week (seconds)
|
|
33
|
+
*/
|
|
34
|
+
declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad: number, azRad: number, elRad: number, gpsTowSec: number): number;
|
|
35
|
+
|
|
6
36
|
/**
|
|
7
37
|
* Single-point positioning (SPP) from pseudoranges and broadcast
|
|
8
38
|
* ephemerides.
|
|
@@ -11,9 +41,11 @@ import './ecef-CF0uAysr.cjs';
|
|
|
11
41
|
* constellation (absorbing inter-system time offsets), satellite clock
|
|
12
42
|
* polynomial + relativistic correction, broadcast group delay
|
|
13
43
|
* (TGD/BGD), Sagnac (Earth-rotation) correction, elevation
|
|
14
|
-
* masking/weighting, and a simple tropospheric model. Ionospheric
|
|
15
|
-
*
|
|
16
|
-
*
|
|
44
|
+
* masking/weighting, and a simple tropospheric model. Ionospheric
|
|
45
|
+
* delay can be modelled from the broadcast Klobuchar coefficients
|
|
46
|
+
* (`iono` option) for single-frequency measurements; for metre-level
|
|
47
|
+
* results prefer the iono-free combination (ionoFree helper) with
|
|
48
|
+
* dual-frequency pseudoranges.
|
|
17
49
|
*/
|
|
18
50
|
|
|
19
51
|
interface SppOptions {
|
|
@@ -25,6 +57,14 @@ interface SppOptions {
|
|
|
25
57
|
maxIterations?: number;
|
|
26
58
|
/** Convergence threshold on the position update (m). Default 1e-4. */
|
|
27
59
|
convergenceM?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Broadcast Klobuchar coefficients (RINEX nav header GPSA/GPSB —
|
|
62
|
+
* `NavResult.header.ionoCorrections`). When given, the modelled
|
|
63
|
+
* slant delay is removed from every single-frequency pseudorange,
|
|
64
|
+
* scaled to each system's primary frequency. Omit when feeding
|
|
65
|
+
* iono-free combinations.
|
|
66
|
+
*/
|
|
67
|
+
iono?: KlobucharCoeffs;
|
|
28
68
|
/**
|
|
29
69
|
* Apply the broadcast group-delay correction (GPS TGD, Galileo
|
|
30
70
|
* BGD E5a/E1, BeiDou TGD1) to the satellite clock. Correct for
|
|
@@ -73,4 +113,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
|
|
|
73
113
|
*/
|
|
74
114
|
declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
|
|
75
115
|
|
|
76
|
-
export { type SppOptions, type SppSolution, ionoFree, satClockCorrection, solveSpp };
|
|
116
|
+
export { type KlobucharCoeffs, type SppOptions, type SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveSpp };
|
package/dist/positioning.d.ts
CHANGED
|
@@ -3,6 +3,36 @@ import { DopValues } from './orbit.js';
|
|
|
3
3
|
import './ephemeris-Ohl6NAfw.js';
|
|
4
4
|
import './ecef-CF0uAysr.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Klobuchar broadcast ionospheric model (IS-GPS-200, 20.3.3.5.2.5).
|
|
8
|
+
*
|
|
9
|
+
* Evaluates the single-frequency vertical→slant ionospheric delay from
|
|
10
|
+
* the eight coefficients broadcast in the GPS navigation message (and
|
|
11
|
+
* carried in RINEX nav headers as GPSA/GPSB, or ION ALPHA/BETA in
|
|
12
|
+
* RINEX 2). The model captures ~50% RMS of the actual delay — crude,
|
|
13
|
+
* but exactly what a single-frequency receiver applies, which makes it
|
|
14
|
+
* both a useful SPP correction and an honest "what broadcast users
|
|
15
|
+
* get" reference against measured or GIM TEC.
|
|
16
|
+
*/
|
|
17
|
+
/** The eight Klobuchar coefficients from the navigation message. */
|
|
18
|
+
interface KlobucharCoeffs {
|
|
19
|
+
/** GPSA: alpha0..alpha3 (s, s/sc, s/sc², s/sc³). */
|
|
20
|
+
alpha: number[];
|
|
21
|
+
/** GPSB: beta0..beta3 (s, s/sc, s/sc², s/sc³). */
|
|
22
|
+
beta: number[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Slant ionospheric group delay on GPS L1, in seconds.
|
|
26
|
+
*
|
|
27
|
+
* @param coeffs Broadcast alpha/beta coefficients
|
|
28
|
+
* @param latRad Receiver geodetic latitude (radians)
|
|
29
|
+
* @param lonRad Receiver geodetic longitude (radians)
|
|
30
|
+
* @param azRad Satellite azimuth (radians)
|
|
31
|
+
* @param elRad Satellite elevation (radians)
|
|
32
|
+
* @param gpsTowSec GPS time of week (seconds)
|
|
33
|
+
*/
|
|
34
|
+
declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad: number, azRad: number, elRad: number, gpsTowSec: number): number;
|
|
35
|
+
|
|
6
36
|
/**
|
|
7
37
|
* Single-point positioning (SPP) from pseudoranges and broadcast
|
|
8
38
|
* ephemerides.
|
|
@@ -11,9 +41,11 @@ import './ecef-CF0uAysr.js';
|
|
|
11
41
|
* constellation (absorbing inter-system time offsets), satellite clock
|
|
12
42
|
* polynomial + relativistic correction, broadcast group delay
|
|
13
43
|
* (TGD/BGD), Sagnac (Earth-rotation) correction, elevation
|
|
14
|
-
* masking/weighting, and a simple tropospheric model. Ionospheric
|
|
15
|
-
*
|
|
16
|
-
*
|
|
44
|
+
* masking/weighting, and a simple tropospheric model. Ionospheric
|
|
45
|
+
* delay can be modelled from the broadcast Klobuchar coefficients
|
|
46
|
+
* (`iono` option) for single-frequency measurements; for metre-level
|
|
47
|
+
* results prefer the iono-free combination (ionoFree helper) with
|
|
48
|
+
* dual-frequency pseudoranges.
|
|
17
49
|
*/
|
|
18
50
|
|
|
19
51
|
interface SppOptions {
|
|
@@ -25,6 +57,14 @@ interface SppOptions {
|
|
|
25
57
|
maxIterations?: number;
|
|
26
58
|
/** Convergence threshold on the position update (m). Default 1e-4. */
|
|
27
59
|
convergenceM?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Broadcast Klobuchar coefficients (RINEX nav header GPSA/GPSB —
|
|
62
|
+
* `NavResult.header.ionoCorrections`). When given, the modelled
|
|
63
|
+
* slant delay is removed from every single-frequency pseudorange,
|
|
64
|
+
* scaled to each system's primary frequency. Omit when feeding
|
|
65
|
+
* iono-free combinations.
|
|
66
|
+
*/
|
|
67
|
+
iono?: KlobucharCoeffs;
|
|
28
68
|
/**
|
|
29
69
|
* Apply the broadcast group-delay correction (GPS TGD, Galileo
|
|
30
70
|
* BGD E5a/E1, BeiDou TGD1) to the satellite clock. Correct for
|
|
@@ -73,4 +113,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
|
|
|
73
113
|
*/
|
|
74
114
|
declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
|
|
75
115
|
|
|
76
|
-
export { type SppOptions, type SppSolution, ionoFree, satClockCorrection, solveSpp };
|
|
116
|
+
export { type KlobucharCoeffs, type SppOptions, type SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveSpp };
|
package/dist/positioning.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ionoFree,
|
|
3
|
+
klobucharDelay,
|
|
3
4
|
satClockCorrection,
|
|
4
5
|
solveSpp
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-LKFSXVIZ.js";
|
|
7
|
+
import "./chunk-DMNFB7Q6.js";
|
|
7
8
|
import "./chunk-HVXYFUCB.js";
|
|
8
9
|
import "./chunk-37QNKGTC.js";
|
|
9
10
|
import "./chunk-6FAL6P4G.js";
|
|
@@ -11,6 +12,7 @@ import "./chunk-LEEU5OIO.js";
|
|
|
11
12
|
import "./chunk-FIEWO4J4.js";
|
|
12
13
|
export {
|
|
13
14
|
ionoFree,
|
|
15
|
+
klobucharDelay,
|
|
14
16
|
satClockCorrection,
|
|
15
17
|
solveSpp
|
|
16
18
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnss-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|