gnss-js 0.1.1 → 1.0.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/LICENSE +23 -0
- package/README.md +171 -5
- package/dist/analysis.cjs +1381 -0
- package/dist/analysis.d.cts +211 -0
- package/dist/analysis.d.ts +211 -0
- package/dist/analysis.js +14 -0
- package/dist/antex.cjs +239 -0
- package/dist/antex.d.cts +77 -0
- package/dist/antex.d.ts +77 -0
- package/dist/antex.js +8 -0
- package/dist/chunk-354IRDOG.js +239 -0
- package/dist/chunk-37QNKGTC.js +65 -0
- package/dist/chunk-5CMSYIYM.js +214 -0
- package/dist/chunk-5S5MONFK.js +0 -0
- package/dist/chunk-6FAL6P4G.js +12 -0
- package/dist/{index.mjs → chunk-7NOFXKET.js} +179 -198
- package/dist/chunk-CB6EOOLA.js +753 -0
- package/dist/chunk-HKN3PUGN.js +800 -0
- package/dist/chunk-IS4UUDBV.js +577 -0
- package/dist/chunk-K7WZQFBV.js +236 -0
- package/dist/chunk-LEEU5OIO.js +75 -0
- package/dist/chunk-PRSZIWKM.js +1482 -0
- package/dist/chunk-SDRRAJT5.js +433 -0
- package/dist/chunk-WR7LCB52.js +143 -0
- package/dist/chunk-Y3R57B5P.js +473 -0
- package/dist/chunk-YDMYO3YK.js +220 -0
- package/dist/constants.cjs +394 -0
- package/dist/constants.d.cts +14 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +103 -0
- package/dist/coordinates.cjs +334 -0
- package/dist/coordinates.d.cts +81 -0
- package/dist/coordinates.d.ts +81 -0
- package/dist/coordinates.js +39 -0
- package/dist/ecef-CF0uAysr.d.cts +40 -0
- package/dist/ecef-CF0uAysr.d.ts +40 -0
- package/dist/ephemeris-BUWzfmBy.d.cts +114 -0
- package/dist/ephemeris-BUWzfmBy.d.ts +114 -0
- package/dist/gnss-BT6ulR17.d.cts +56 -0
- package/dist/gnss-C-tgoYNa.d.ts +56 -0
- package/dist/index.cjs +6286 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -89
- package/dist/index.js +355 -479
- package/dist/nav-BAI1a9vK.d.cts +108 -0
- package/dist/nav-BAI1a9vK.d.ts +108 -0
- package/dist/nmea.cjs +247 -0
- package/dist/nmea.d.cts +37 -0
- package/dist/nmea.d.ts +37 -0
- package/dist/nmea.js +12 -0
- package/dist/ntrip.cjs +169 -0
- package/dist/ntrip.d.cts +98 -0
- package/dist/ntrip.d.ts +98 -0
- package/dist/ntrip.js +10 -0
- package/dist/orbit.cjs +504 -0
- package/dist/orbit.d.cts +117 -0
- package/dist/orbit.d.ts +117 -0
- package/dist/orbit.js +29 -0
- package/dist/parser-JPjjFgeP.d.cts +73 -0
- package/dist/parser-JPjjFgeP.d.ts +73 -0
- package/dist/rinex.cjs +1375 -0
- package/dist/rinex.d.cts +116 -0
- package/dist/rinex.d.ts +116 -0
- package/dist/rinex.js +37 -0
- package/dist/rtcm3.cjs +1519 -0
- package/dist/rtcm3.d.cts +143 -0
- package/dist/rtcm3.d.ts +143 -0
- package/dist/rtcm3.js +31 -0
- package/dist/signals.cjs +858 -0
- package/dist/signals.d.cts +93 -0
- package/dist/signals.d.ts +93 -0
- package/dist/signals.js +70 -0
- package/dist/time-DnI1VpE8.d.cts +33 -0
- package/dist/time-DnI1VpE8.d.ts +33 -0
- package/dist/time.cjs +585 -0
- package/dist/time.d.cts +307 -0
- package/dist/time.d.ts +307 -0
- package/dist/time.js +154 -0
- package/package.json +112 -12
- package/.prettierrc +0 -5
- package/dist/index.d.mts +0 -89
- package/jest.config.js +0 -6
- package/test/functions.test.js +0 -69
- package/test/gnss-scales.test.js +0 -95
- package/test/julian.test.js +0 -41
- package/test/rinex.test.js +0 -11
- package/test/test.js +0 -11
- package/test/utc.test.js +0 -30
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ecefToGeodetic
|
|
3
|
+
} from "./chunk-37QNKGTC.js";
|
|
4
|
+
|
|
5
|
+
// src/orbit/index.ts
|
|
6
|
+
var GM_GPS = 3986005e8;
|
|
7
|
+
var GM_GAL = 3986004418e5;
|
|
8
|
+
var GM_BDS = 3986004418e5;
|
|
9
|
+
var GM_GLO = 39860044e7;
|
|
10
|
+
var OMEGA_E = 72921151467e-15;
|
|
11
|
+
var AE_GLO = 6378136;
|
|
12
|
+
var J2_GLO = 108263e-8;
|
|
13
|
+
var TWO_PI = 2 * Math.PI;
|
|
14
|
+
function gmForSystem(sys) {
|
|
15
|
+
if (sys === "E") return GM_GAL;
|
|
16
|
+
if (sys === "C") return GM_BDS;
|
|
17
|
+
return GM_GPS;
|
|
18
|
+
}
|
|
19
|
+
function keplerPosition(eph, t) {
|
|
20
|
+
const GM = gmForSystem(eph.system);
|
|
21
|
+
const a = eph.sqrtA * eph.sqrtA;
|
|
22
|
+
const n0 = Math.sqrt(GM / (a * a * a));
|
|
23
|
+
const n = n0 + eph.deltaN;
|
|
24
|
+
let tk = t - eph.toe;
|
|
25
|
+
if (tk > 302400) tk -= 604800;
|
|
26
|
+
if (tk < -302400) tk += 604800;
|
|
27
|
+
const Mk = eph.m0 + n * tk;
|
|
28
|
+
let Ek = Mk;
|
|
29
|
+
for (let i = 0; i < 10; i++) {
|
|
30
|
+
const dE = (Mk - (Ek - eph.e * Math.sin(Ek))) / (1 - eph.e * Math.cos(Ek));
|
|
31
|
+
Ek += dE;
|
|
32
|
+
if (Math.abs(dE) < 1e-12) break;
|
|
33
|
+
}
|
|
34
|
+
const sinE = Math.sin(Ek);
|
|
35
|
+
const cosE = Math.cos(Ek);
|
|
36
|
+
const vk = Math.atan2(
|
|
37
|
+
Math.sqrt(1 - eph.e * eph.e) * sinE,
|
|
38
|
+
cosE - eph.e
|
|
39
|
+
);
|
|
40
|
+
const phik = vk + eph.omega;
|
|
41
|
+
const sin2phi = Math.sin(2 * phik);
|
|
42
|
+
const cos2phi = Math.cos(2 * phik);
|
|
43
|
+
const duk = eph.cus * sin2phi + eph.cuc * cos2phi;
|
|
44
|
+
const drk = eph.crs * sin2phi + eph.crc * cos2phi;
|
|
45
|
+
const dik = eph.cis * sin2phi + eph.cic * cos2phi;
|
|
46
|
+
const uk = phik + duk;
|
|
47
|
+
const rk = a * (1 - eph.e * cosE) + drk;
|
|
48
|
+
const ik = eph.i0 + dik + eph.idot * tk;
|
|
49
|
+
const xp = rk * Math.cos(uk);
|
|
50
|
+
const yp = rk * Math.sin(uk);
|
|
51
|
+
const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < 0.1 && a > 4e7;
|
|
52
|
+
if (isBdsGeo) {
|
|
53
|
+
const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
|
|
54
|
+
const cosO2 = Math.cos(omegak2);
|
|
55
|
+
const sinO2 = Math.sin(omegak2);
|
|
56
|
+
const cosI2 = Math.cos(ik);
|
|
57
|
+
const sinI2 = Math.sin(ik);
|
|
58
|
+
const xg = xp * cosO2 - yp * cosI2 * sinO2;
|
|
59
|
+
const yg = xp * sinO2 + yp * cosI2 * cosO2;
|
|
60
|
+
const zg = yp * sinI2;
|
|
61
|
+
const phi = OMEGA_E * tk;
|
|
62
|
+
const cosPhi = Math.cos(phi);
|
|
63
|
+
const sinPhi = Math.sin(phi);
|
|
64
|
+
const COS5 = Math.cos(-5 * Math.PI / 180);
|
|
65
|
+
const SIN5 = Math.sin(-5 * Math.PI / 180);
|
|
66
|
+
return {
|
|
67
|
+
prn: eph.prn,
|
|
68
|
+
x: xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5,
|
|
69
|
+
y: -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5,
|
|
70
|
+
z: -yg * SIN5 + zg * COS5
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
|
|
74
|
+
const cosO = Math.cos(omegak);
|
|
75
|
+
const sinO = Math.sin(omegak);
|
|
76
|
+
const cosI = Math.cos(ik);
|
|
77
|
+
const sinI = Math.sin(ik);
|
|
78
|
+
return {
|
|
79
|
+
prn: eph.prn,
|
|
80
|
+
x: xp * cosO - yp * cosI * sinO,
|
|
81
|
+
y: xp * sinO + yp * cosI * cosO,
|
|
82
|
+
z: yp * sinI
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function gloDerivatives(state, acc) {
|
|
86
|
+
const [x, y, z, vx, vy, vz] = state;
|
|
87
|
+
const [ax, ay, az] = acc;
|
|
88
|
+
const r = Math.sqrt(x * x + y * y + z * z);
|
|
89
|
+
const r2 = r * r;
|
|
90
|
+
const r3 = r2 * r;
|
|
91
|
+
const r5 = r2 * r3;
|
|
92
|
+
const mu_r3 = GM_GLO / r3;
|
|
93
|
+
const j2_term = 1.5 * J2_GLO * AE_GLO * AE_GLO / r5;
|
|
94
|
+
const dvx = -mu_r3 * x + j2_term * x * (5 * z * z / r2 - 1) + OMEGA_E * OMEGA_E * x + 2 * OMEGA_E * vy + ax;
|
|
95
|
+
const dvy = -mu_r3 * y + j2_term * y * (5 * z * z / r2 - 1) + OMEGA_E * OMEGA_E * y - 2 * OMEGA_E * vx + ay;
|
|
96
|
+
const dvz = -mu_r3 * z + j2_term * z * (5 * z * z / r2 - 3) + az;
|
|
97
|
+
return [vx, vy, vz, dvx, dvy, dvz];
|
|
98
|
+
}
|
|
99
|
+
function rk4Step(state, acc, dt) {
|
|
100
|
+
const k1 = gloDerivatives(state, acc);
|
|
101
|
+
const s2 = state.map((v, i) => v + k1[i] * dt / 2);
|
|
102
|
+
const k2 = gloDerivatives(s2, acc);
|
|
103
|
+
const s3 = state.map((v, i) => v + k2[i] * dt / 2);
|
|
104
|
+
const k3 = gloDerivatives(s3, acc);
|
|
105
|
+
const s4 = state.map((v, i) => v + k3[i] * dt);
|
|
106
|
+
const k4 = gloDerivatives(s4, acc);
|
|
107
|
+
return state.map(
|
|
108
|
+
(v, i) => v + dt / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i])
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function glonassPosition(eph, tUtc) {
|
|
112
|
+
const ephTime = eph.tocDate.getTime() / 1e3;
|
|
113
|
+
const dt = tUtc - ephTime;
|
|
114
|
+
let state = [
|
|
115
|
+
eph.x * 1e3,
|
|
116
|
+
eph.y * 1e3,
|
|
117
|
+
eph.z * 1e3,
|
|
118
|
+
eph.xDot * 1e3,
|
|
119
|
+
eph.yDot * 1e3,
|
|
120
|
+
eph.zDot * 1e3
|
|
121
|
+
];
|
|
122
|
+
const acc = [
|
|
123
|
+
eph.xAcc * 1e3,
|
|
124
|
+
eph.yAcc * 1e3,
|
|
125
|
+
eph.zAcc * 1e3
|
|
126
|
+
];
|
|
127
|
+
const step = dt > 0 ? 60 : -60;
|
|
128
|
+
const nSteps = Math.floor(Math.abs(dt) / 60);
|
|
129
|
+
const remainder = dt - nSteps * step;
|
|
130
|
+
for (let i = 0; i < nSteps; i++) {
|
|
131
|
+
state = rk4Step(state, acc, step);
|
|
132
|
+
}
|
|
133
|
+
if (Math.abs(remainder) > 1e-3) {
|
|
134
|
+
state = rk4Step(state, acc, remainder);
|
|
135
|
+
}
|
|
136
|
+
return { prn: eph.prn, x: state[0], y: state[1], z: state[2] };
|
|
137
|
+
}
|
|
138
|
+
function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
|
|
139
|
+
const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
140
|
+
const dx = satX - rxX;
|
|
141
|
+
const dy = satY - rxY;
|
|
142
|
+
const dz = satZ - rxZ;
|
|
143
|
+
const sinLat = Math.sin(lat);
|
|
144
|
+
const cosLat = Math.cos(lat);
|
|
145
|
+
const sinLon = Math.sin(lon);
|
|
146
|
+
const cosLon = Math.cos(lon);
|
|
147
|
+
const east = -sinLon * dx + cosLon * dy;
|
|
148
|
+
const north = -sinLat * cosLon * dx - sinLat * sinLon * dy + cosLat * dz;
|
|
149
|
+
const up = cosLat * cosLon * dx + cosLat * sinLon * dy + sinLat * dz;
|
|
150
|
+
const az = Math.atan2(east, north);
|
|
151
|
+
const el = Math.atan2(up, Math.sqrt(east * east + north * north));
|
|
152
|
+
return { az: (az + TWO_PI) % TWO_PI, el };
|
|
153
|
+
}
|
|
154
|
+
function computeDop(satAzEls) {
|
|
155
|
+
const n = satAzEls.length;
|
|
156
|
+
if (n < 4) return null;
|
|
157
|
+
const H = [];
|
|
158
|
+
for (const { az, el } of satAzEls) {
|
|
159
|
+
const cosEl = Math.cos(el);
|
|
160
|
+
H.push([cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el), 1]);
|
|
161
|
+
}
|
|
162
|
+
const HtH = Array.from({ length: 4 }, () => new Array(4).fill(0));
|
|
163
|
+
for (let i = 0; i < 4; i++) {
|
|
164
|
+
for (let j = 0; j < 4; j++) {
|
|
165
|
+
let sum = 0;
|
|
166
|
+
for (let k = 0; k < n; k++) {
|
|
167
|
+
sum += H[k][i] * H[k][j];
|
|
168
|
+
}
|
|
169
|
+
HtH[i][j] = sum;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const Q = invert4x4(HtH);
|
|
173
|
+
if (!Q) return null;
|
|
174
|
+
const gdop = Math.sqrt(Q[0][0] + Q[1][1] + Q[2][2] + Q[3][3]);
|
|
175
|
+
const pdop = Math.sqrt(Q[0][0] + Q[1][1] + Q[2][2]);
|
|
176
|
+
const hdop = Math.sqrt(Q[0][0] + Q[1][1]);
|
|
177
|
+
const vdop = Math.sqrt(Q[2][2]);
|
|
178
|
+
return { gdop, pdop, hdop, vdop };
|
|
179
|
+
}
|
|
180
|
+
function invert4x4(m) {
|
|
181
|
+
const a = m.flat();
|
|
182
|
+
const inv = new Array(16).fill(0);
|
|
183
|
+
inv[0] = a[5] * a[10] * a[15] - a[5] * a[11] * a[14] - a[9] * a[6] * a[15] + a[9] * a[7] * a[14] + a[13] * a[6] * a[11] - a[13] * a[7] * a[10];
|
|
184
|
+
inv[4] = -a[4] * a[10] * a[15] + a[4] * a[11] * a[14] + a[8] * a[6] * a[15] - a[8] * a[7] * a[14] - a[12] * a[6] * a[11] + a[12] * a[7] * a[10];
|
|
185
|
+
inv[8] = a[4] * a[9] * a[15] - a[4] * a[11] * a[13] - a[8] * a[5] * a[15] + a[8] * a[7] * a[13] + a[12] * a[5] * a[11] - a[12] * a[7] * a[9];
|
|
186
|
+
inv[12] = -a[4] * a[9] * a[14] + a[4] * a[10] * a[13] + a[8] * a[5] * a[14] - a[8] * a[6] * a[13] - a[12] * a[5] * a[10] + a[12] * a[6] * a[9];
|
|
187
|
+
inv[1] = -a[1] * a[10] * a[15] + a[1] * a[11] * a[14] + a[9] * a[2] * a[15] - a[9] * a[3] * a[14] - a[13] * a[2] * a[11] + a[13] * a[3] * a[10];
|
|
188
|
+
inv[5] = a[0] * a[10] * a[15] - a[0] * a[11] * a[14] - a[8] * a[2] * a[15] + a[8] * a[3] * a[14] + a[12] * a[2] * a[11] - a[12] * a[3] * a[10];
|
|
189
|
+
inv[9] = -a[0] * a[9] * a[15] + a[0] * a[11] * a[13] + a[8] * a[1] * a[15] - a[8] * a[3] * a[13] - a[12] * a[1] * a[11] + a[12] * a[3] * a[9];
|
|
190
|
+
inv[13] = a[0] * a[9] * a[14] - a[0] * a[10] * a[13] - a[8] * a[1] * a[14] + a[8] * a[2] * a[13] + a[12] * a[1] * a[10] - a[12] * a[2] * a[9];
|
|
191
|
+
inv[2] = a[1] * a[6] * a[15] - a[1] * a[7] * a[14] - a[5] * a[2] * a[15] + a[5] * a[3] * a[14] + a[13] * a[2] * a[7] - a[13] * a[3] * a[6];
|
|
192
|
+
inv[6] = -a[0] * a[6] * a[15] + a[0] * a[7] * a[14] + a[4] * a[2] * a[15] - a[4] * a[3] * a[14] - a[12] * a[2] * a[7] + a[12] * a[3] * a[6];
|
|
193
|
+
inv[10] = a[0] * a[5] * a[15] - a[0] * a[7] * a[13] - a[4] * a[1] * a[15] + a[4] * a[3] * a[13] + a[12] * a[1] * a[7] - a[12] * a[3] * a[5];
|
|
194
|
+
inv[14] = -a[0] * a[5] * a[14] + a[0] * a[6] * a[13] + a[4] * a[1] * a[14] - a[4] * a[2] * a[13] - a[12] * a[1] * a[6] + a[12] * a[2] * a[5];
|
|
195
|
+
inv[3] = -a[1] * a[6] * a[11] + a[1] * a[7] * a[10] + a[5] * a[2] * a[11] - a[5] * a[3] * a[10] - a[9] * a[2] * a[7] + a[9] * a[3] * a[6];
|
|
196
|
+
inv[7] = a[0] * a[6] * a[11] - a[0] * a[7] * a[10] - a[4] * a[2] * a[11] + a[4] * a[3] * a[10] + a[8] * a[2] * a[7] - a[8] * a[3] * a[6];
|
|
197
|
+
inv[11] = -a[0] * a[5] * a[11] + a[0] * a[7] * a[9] + a[4] * a[1] * a[11] - a[4] * a[3] * a[9] - a[8] * a[1] * a[7] + a[8] * a[3] * a[5];
|
|
198
|
+
inv[15] = a[0] * a[5] * a[10] - a[0] * a[6] * a[9] - a[4] * a[1] * a[10] + a[4] * a[2] * a[9] + a[8] * a[1] * a[6] - a[8] * a[2] * a[5];
|
|
199
|
+
let det = a[0] * inv[0] + a[1] * inv[4] + a[2] * inv[8] + a[3] * inv[12];
|
|
200
|
+
if (Math.abs(det) < 1e-20) return null;
|
|
201
|
+
det = 1 / det;
|
|
202
|
+
const result = Array.from({ length: 4 }, () => new Array(4).fill(0));
|
|
203
|
+
for (let i = 0; i < 4; i++) {
|
|
204
|
+
for (let j = 0; j < 4; j++) {
|
|
205
|
+
result[i][j] = inv[i * 4 + j] * det;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
function selectEphemeris(ephemerides, prn, timeMs) {
|
|
211
|
+
let best = null;
|
|
212
|
+
let bestDt = Infinity;
|
|
213
|
+
for (const eph of ephemerides) {
|
|
214
|
+
if (eph.prn !== prn) continue;
|
|
215
|
+
const ephTime = eph.tocDate.getTime();
|
|
216
|
+
const dt = Math.abs(timeMs - ephTime);
|
|
217
|
+
if (dt > 4 * 3600 * 1e3) continue;
|
|
218
|
+
if (dt < bestDt) {
|
|
219
|
+
bestDt = dt;
|
|
220
|
+
best = eph;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return best;
|
|
224
|
+
}
|
|
225
|
+
function computeSatPosition(eph, timeMs) {
|
|
226
|
+
if (eph.system === "R" || eph.system === "S") {
|
|
227
|
+
return glonassPosition(eph, timeMs / 1e3);
|
|
228
|
+
}
|
|
229
|
+
const GPS_EPOCH = Date.UTC(1980, 0, 6);
|
|
230
|
+
const gpsSeconds = (timeMs - GPS_EPOCH) / 1e3;
|
|
231
|
+
const tow = gpsSeconds % (7 * 86400);
|
|
232
|
+
return keplerPosition(eph, tow);
|
|
233
|
+
}
|
|
234
|
+
function navTimesFromEph(ephs) {
|
|
235
|
+
let minT = Infinity, maxT = -Infinity;
|
|
236
|
+
for (const eph of ephs) {
|
|
237
|
+
const t = eph.tocDate.getTime();
|
|
238
|
+
if (t < minT) minT = t;
|
|
239
|
+
if (t > maxT) maxT = t;
|
|
240
|
+
}
|
|
241
|
+
const span = maxT - minT;
|
|
242
|
+
const step = Math.max(span / 500, 3e4);
|
|
243
|
+
const times = [];
|
|
244
|
+
for (let t = minT; t <= maxT; t += step) times.push(t);
|
|
245
|
+
if (times.length === 0 && isFinite(minT)) times.push(minT);
|
|
246
|
+
return times;
|
|
247
|
+
}
|
|
248
|
+
function computeAllPositions(ephemerides, times, rxPos) {
|
|
249
|
+
const ephByPrn = /* @__PURE__ */ new Map();
|
|
250
|
+
for (const eph of ephemerides) {
|
|
251
|
+
let arr = ephByPrn.get(eph.prn);
|
|
252
|
+
if (!arr) {
|
|
253
|
+
arr = [];
|
|
254
|
+
ephByPrn.set(eph.prn, arr);
|
|
255
|
+
}
|
|
256
|
+
arr.push(eph);
|
|
257
|
+
}
|
|
258
|
+
const prns = [...ephByPrn.keys()].sort();
|
|
259
|
+
const positions = {};
|
|
260
|
+
for (const prn of prns) positions[prn] = [];
|
|
261
|
+
let sinLat = 0, cosLat = 1, sinLon = 0, cosLon = 1;
|
|
262
|
+
let hasRx = false;
|
|
263
|
+
if (rxPos) {
|
|
264
|
+
const [rxX, rxY, rxZ] = rxPos;
|
|
265
|
+
if (rxX !== 0 || rxY !== 0 || rxZ !== 0) {
|
|
266
|
+
hasRx = true;
|
|
267
|
+
const [gLat, gLon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
268
|
+
sinLat = Math.sin(gLat);
|
|
269
|
+
cosLat = Math.cos(gLat);
|
|
270
|
+
sinLon = Math.sin(gLon);
|
|
271
|
+
cosLon = Math.cos(gLon);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const t of times) {
|
|
275
|
+
for (const prn of prns) {
|
|
276
|
+
const eph = selectBest(ephByPrn.get(prn), t);
|
|
277
|
+
if (!eph) {
|
|
278
|
+
positions[prn].push(null);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const pos = computeSatPosition(eph, t);
|
|
282
|
+
const [geoLat, geoLon] = ecefToGeodetic(pos.x, pos.y, pos.z);
|
|
283
|
+
let az = 0, el = 0;
|
|
284
|
+
if (hasRx) {
|
|
285
|
+
const dx = pos.x - rxPos[0];
|
|
286
|
+
const dy = pos.y - rxPos[1];
|
|
287
|
+
const dz = pos.z - rxPos[2];
|
|
288
|
+
const east = -sinLon * dx + cosLon * dy;
|
|
289
|
+
const north = -sinLat * cosLon * dx - sinLat * sinLon * dy + cosLat * dz;
|
|
290
|
+
const up = cosLat * cosLon * dx + cosLat * sinLon * dy + sinLat * dz;
|
|
291
|
+
az = (Math.atan2(east, north) + TWO_PI) % TWO_PI;
|
|
292
|
+
el = Math.atan2(up, Math.sqrt(east * east + north * north));
|
|
293
|
+
}
|
|
294
|
+
positions[prn].push({ lat: geoLat, lon: geoLon, az, el });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return { prns, times, positions };
|
|
298
|
+
}
|
|
299
|
+
function selectBest(ephs, timeMs) {
|
|
300
|
+
let best = null;
|
|
301
|
+
let bestDt = Infinity;
|
|
302
|
+
for (const eph of ephs) {
|
|
303
|
+
const dt = Math.abs(timeMs - eph.tocDate.getTime());
|
|
304
|
+
const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
|
|
305
|
+
if (dt > maxAge) continue;
|
|
306
|
+
if (dt < bestDt) {
|
|
307
|
+
bestDt = dt;
|
|
308
|
+
best = eph;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return best;
|
|
312
|
+
}
|
|
313
|
+
var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
|
|
314
|
+
var BDS_EPOCH_MS = Date.UTC(2006, 0, 1);
|
|
315
|
+
var GAL_EPOCH_MS = GPS_EPOCH_MS;
|
|
316
|
+
function ephInfoToEphemeris(info) {
|
|
317
|
+
const sys = info.prn.charAt(0);
|
|
318
|
+
if (sys === "R") {
|
|
319
|
+
if (info.x === void 0 || info.y === void 0 || info.z === void 0) return null;
|
|
320
|
+
if (info.vx === void 0 || info.vy === void 0 || info.vz === void 0) return null;
|
|
321
|
+
const now = new Date(info.lastReceived);
|
|
322
|
+
const utcMidnight = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
323
|
+
const tbSec = (info.tb ?? 0) * 900;
|
|
324
|
+
const moscowOffset = 3 * 3600;
|
|
325
|
+
const tocMs = utcMidnight + (tbSec - moscowOffset) * 1e3;
|
|
326
|
+
return {
|
|
327
|
+
system: "R",
|
|
328
|
+
prn: info.prn,
|
|
329
|
+
tocDate: new Date(tocMs),
|
|
330
|
+
tauN: -(info.af0 ?? 0),
|
|
331
|
+
// RTCM stores -tauN
|
|
332
|
+
gammaN: info.gammaN ?? 0,
|
|
333
|
+
messageFrameTime: 0,
|
|
334
|
+
x: info.x,
|
|
335
|
+
xDot: info.vx,
|
|
336
|
+
xAcc: info.ax ?? 0,
|
|
337
|
+
y: info.y,
|
|
338
|
+
yDot: info.vy,
|
|
339
|
+
yAcc: info.ay ?? 0,
|
|
340
|
+
z: info.z,
|
|
341
|
+
zDot: info.vz,
|
|
342
|
+
zAcc: info.az ?? 0,
|
|
343
|
+
health: info.health,
|
|
344
|
+
freqNum: info.freqChannel ?? 0
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (info.sqrtA === void 0 || info.eccentricity === void 0 || info.inclination === void 0 || info.omega0 === void 0 || info.argPerigee === void 0 || info.meanAnomaly === void 0 || info.toe === void 0 || info.week === void 0) return null;
|
|
348
|
+
let epochMs;
|
|
349
|
+
const tocSec = info.toc ?? info.toe;
|
|
350
|
+
if (sys === "C") {
|
|
351
|
+
epochMs = BDS_EPOCH_MS;
|
|
352
|
+
} else if (sys === "E") {
|
|
353
|
+
epochMs = GAL_EPOCH_MS;
|
|
354
|
+
} else {
|
|
355
|
+
epochMs = GPS_EPOCH_MS;
|
|
356
|
+
}
|
|
357
|
+
const tocDate = new Date(epochMs + info.week * 7 * 864e5 + tocSec * 1e3);
|
|
358
|
+
return {
|
|
359
|
+
system: sys,
|
|
360
|
+
prn: info.prn,
|
|
361
|
+
toc: tocSec,
|
|
362
|
+
tocDate,
|
|
363
|
+
af0: info.af0 ?? 0,
|
|
364
|
+
af1: info.af1 ?? 0,
|
|
365
|
+
af2: info.af2 ?? 0,
|
|
366
|
+
iode: info.iode ?? 0,
|
|
367
|
+
crs: info.crs ?? 0,
|
|
368
|
+
deltaN: info.deltaN ?? 0,
|
|
369
|
+
m0: info.meanAnomaly,
|
|
370
|
+
cuc: info.cuc ?? 0,
|
|
371
|
+
e: info.eccentricity,
|
|
372
|
+
cus: info.cus ?? 0,
|
|
373
|
+
sqrtA: info.sqrtA,
|
|
374
|
+
toe: info.toe,
|
|
375
|
+
cic: info.cic ?? 0,
|
|
376
|
+
omega0: info.omega0,
|
|
377
|
+
cis: info.cis ?? 0,
|
|
378
|
+
i0: info.inclination,
|
|
379
|
+
crc: info.crc ?? 0,
|
|
380
|
+
omega: info.argPerigee,
|
|
381
|
+
omegaDot: info.omegaDot ?? 0,
|
|
382
|
+
idot: info.idot ?? 0,
|
|
383
|
+
week: info.week,
|
|
384
|
+
svHealth: info.health,
|
|
385
|
+
tgd: 0
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function computeLiveSkyPositions(ephemerides, rxPos, cn0Map) {
|
|
389
|
+
const now = Date.now();
|
|
390
|
+
const result = [];
|
|
391
|
+
const [rxX, rxY, rxZ] = rxPos;
|
|
392
|
+
const [rxLat, rxLon] = ecefToGeodetic(rxX, rxY, rxZ);
|
|
393
|
+
const sinRxLat = Math.sin(rxLat), cosRxLat = Math.cos(rxLat);
|
|
394
|
+
const sinRxLon = Math.sin(rxLon), cosRxLon = Math.cos(rxLon);
|
|
395
|
+
for (const info of ephemerides.values()) {
|
|
396
|
+
const eph = ephInfoToEphemeris(info);
|
|
397
|
+
if (!eph) continue;
|
|
398
|
+
try {
|
|
399
|
+
const pos = computeSatPosition(eph, now);
|
|
400
|
+
const [satLat, satLon] = ecefToGeodetic(pos.x, pos.y, pos.z);
|
|
401
|
+
const dx = pos.x - rxX, dy = pos.y - rxY, dz = pos.z - rxZ;
|
|
402
|
+
const east = -sinRxLon * dx + cosRxLon * dy;
|
|
403
|
+
const north = -sinRxLat * cosRxLon * dx - sinRxLat * sinRxLon * dy + cosRxLat * dz;
|
|
404
|
+
const up = cosRxLat * cosRxLon * dx + cosRxLat * sinRxLon * dy + sinRxLat * dz;
|
|
405
|
+
const az = (Math.atan2(east, north) + TWO_PI) % TWO_PI;
|
|
406
|
+
const el = Math.atan2(up, Math.sqrt(east * east + north * north));
|
|
407
|
+
if (el > -0.05) {
|
|
408
|
+
result.push({
|
|
409
|
+
prn: info.prn,
|
|
410
|
+
az,
|
|
411
|
+
el,
|
|
412
|
+
lat: satLat,
|
|
413
|
+
lon: satLon,
|
|
414
|
+
cn0: cn0Map?.get(info.prn)
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export {
|
|
424
|
+
keplerPosition,
|
|
425
|
+
glonassPosition,
|
|
426
|
+
ecefToAzEl,
|
|
427
|
+
computeDop,
|
|
428
|
+
selectEphemeris,
|
|
429
|
+
computeSatPosition,
|
|
430
|
+
navTimesFromEph,
|
|
431
|
+
computeAllPositions,
|
|
432
|
+
computeLiveSkyPositions
|
|
433
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/ntrip/index.ts
|
|
2
|
+
function parseStreamEntry(fields) {
|
|
3
|
+
if (fields.length < 19) return null;
|
|
4
|
+
return {
|
|
5
|
+
type: "STR",
|
|
6
|
+
mountpoint: fields[1] ?? "",
|
|
7
|
+
identifier: fields[2] ?? "",
|
|
8
|
+
format: fields[3] ?? "",
|
|
9
|
+
formatDetails: fields[4] ?? "",
|
|
10
|
+
carrier: parseInt(fields[5] ?? "0") || 0,
|
|
11
|
+
navSystem: fields[6] ?? "",
|
|
12
|
+
network: fields[7] ?? "",
|
|
13
|
+
country: fields[8] ?? "",
|
|
14
|
+
latitude: parseFloat(fields[9] ?? "0") || 0,
|
|
15
|
+
longitude: parseFloat(fields[10] ?? "0") || 0,
|
|
16
|
+
nmea: parseInt(fields[11] ?? "0") || 0,
|
|
17
|
+
solution: parseInt(fields[12] ?? "0") || 0,
|
|
18
|
+
generator: fields[13] ?? "",
|
|
19
|
+
compression: fields[14] ?? "",
|
|
20
|
+
authentication: fields[15] ?? "N",
|
|
21
|
+
fee: fields[16] ?? "N",
|
|
22
|
+
bitrate: parseInt(fields[17] ?? "0") || 0,
|
|
23
|
+
misc: fields[18] ?? ""
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function parseCasterEntry(fields) {
|
|
27
|
+
if (fields.length < 12) return null;
|
|
28
|
+
return {
|
|
29
|
+
type: "CAS",
|
|
30
|
+
host: fields[1] ?? "",
|
|
31
|
+
port: parseInt(fields[2] ?? "0") || 0,
|
|
32
|
+
identifier: fields[3] ?? "",
|
|
33
|
+
operator: fields[4] ?? "",
|
|
34
|
+
nmea: parseInt(fields[5] ?? "0") || 0,
|
|
35
|
+
country: fields[6] ?? "",
|
|
36
|
+
latitude: parseFloat(fields[7] ?? "0") || 0,
|
|
37
|
+
longitude: parseFloat(fields[8] ?? "0") || 0,
|
|
38
|
+
fallbackHost: fields[9] ?? "",
|
|
39
|
+
fallbackPort: parseInt(fields[10] ?? "0") || 0,
|
|
40
|
+
misc: fields[11] ?? ""
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function parseNetworkEntry(fields) {
|
|
44
|
+
if (fields.length < 9) return null;
|
|
45
|
+
return {
|
|
46
|
+
type: "NET",
|
|
47
|
+
identifier: fields[1] ?? "",
|
|
48
|
+
operator: fields[2] ?? "",
|
|
49
|
+
authentication: fields[3] ?? "",
|
|
50
|
+
fee: fields[4] ?? "",
|
|
51
|
+
webUrl: fields[5] ?? "",
|
|
52
|
+
streamUrl: fields[6] ?? "",
|
|
53
|
+
registrationUrl: fields[7] ?? "",
|
|
54
|
+
misc: fields[8] ?? ""
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function parseSourcetable(text) {
|
|
58
|
+
const streams = [];
|
|
59
|
+
const casters = [];
|
|
60
|
+
const networks = [];
|
|
61
|
+
for (const rawLine of text.split("\n")) {
|
|
62
|
+
const line = rawLine.trim();
|
|
63
|
+
if (!line || line === "ENDSOURCETABLE") continue;
|
|
64
|
+
const fields = line.split(";");
|
|
65
|
+
const entryType = fields[0]?.toUpperCase();
|
|
66
|
+
if (entryType === "STR") {
|
|
67
|
+
const entry = parseStreamEntry(fields);
|
|
68
|
+
if (entry) streams.push(entry);
|
|
69
|
+
} else if (entryType === "CAS") {
|
|
70
|
+
const entry = parseCasterEntry(fields);
|
|
71
|
+
if (entry) casters.push(entry);
|
|
72
|
+
} else if (entryType === "NET") {
|
|
73
|
+
const entry = parseNetworkEntry(fields);
|
|
74
|
+
if (entry) networks.push(entry);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { streams, casters, networks, raw: text };
|
|
78
|
+
}
|
|
79
|
+
function buildAuthHeader(username, password) {
|
|
80
|
+
if (!username) return null;
|
|
81
|
+
return "Basic " + btoa(`${username}:${password ?? ""}`);
|
|
82
|
+
}
|
|
83
|
+
function ntripHeaders(info) {
|
|
84
|
+
const headers = {
|
|
85
|
+
"Ntrip-Version": info.version === "2.0" ? "Ntrip/2.0" : "Ntrip/1.0",
|
|
86
|
+
"User-Agent": "NTRIP gnss-js/1.0",
|
|
87
|
+
"X-Ntrip-Host": info.host,
|
|
88
|
+
"X-Ntrip-Port": String(info.port)
|
|
89
|
+
};
|
|
90
|
+
const auth = buildAuthHeader(info.username, info.password);
|
|
91
|
+
if (auth) headers["Authorization"] = auth;
|
|
92
|
+
return headers;
|
|
93
|
+
}
|
|
94
|
+
async function ntripFetch(proxyUrl, path, headers, signal) {
|
|
95
|
+
const url = `${proxyUrl}${path}`;
|
|
96
|
+
try {
|
|
97
|
+
return await fetch(url, { headers, signal });
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (err instanceof Error && err.name === "AbortError") throw err;
|
|
100
|
+
const message = err instanceof Error ? err.message : "The service may be temporarily unavailable.";
|
|
101
|
+
throw new Error(`Could not reach the NTRIP proxy: ${message}`, { cause: err });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function fetchSourcetable(proxyUrl, info, signal) {
|
|
105
|
+
const headers = ntripHeaders(info);
|
|
106
|
+
const res = await ntripFetch(proxyUrl, "/", headers, signal);
|
|
107
|
+
if (res.status === 401) {
|
|
108
|
+
throw new Error("Authentication required. Please provide valid credentials.");
|
|
109
|
+
}
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
throw new Error(`Caster returned ${res.status} ${res.statusText}`);
|
|
112
|
+
}
|
|
113
|
+
const text = await res.text();
|
|
114
|
+
return parseSourcetable(text);
|
|
115
|
+
}
|
|
116
|
+
async function connectToMountpoint(proxyUrl, info, signal) {
|
|
117
|
+
const headers = ntripHeaders(info);
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
120
|
+
const res = await ntripFetch(proxyUrl, `/${info.mountpoint}`, headers, combinedSignal);
|
|
121
|
+
if (res.status === 401) {
|
|
122
|
+
throw new Error("Authentication required for this mountpoint.");
|
|
123
|
+
}
|
|
124
|
+
if (res.status === 404) {
|
|
125
|
+
throw new Error(`Mountpoint "/${info.mountpoint}" not found on caster.`);
|
|
126
|
+
}
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
throw new Error(`Caster returned ${res.status} ${res.statusText}`);
|
|
129
|
+
}
|
|
130
|
+
if (!res.body) {
|
|
131
|
+
throw new Error("No response body \u2014 streaming not supported by this environment.");
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
reader: res.body.getReader(),
|
|
135
|
+
abort: () => controller.abort()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
parseSourcetable,
|
|
141
|
+
fetchSourcetable,
|
|
142
|
+
connectToMountpoint
|
|
143
|
+
};
|