gnss-js 1.0.1 → 1.2.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.
Files changed (42) hide show
  1. package/README.md +54 -0
  2. package/dist/analysis.cjs +3 -3
  3. package/dist/analysis.js +3 -3
  4. package/dist/{chunk-HKN3PUGN.js → chunk-5SPJH4MG.js} +35 -26
  5. package/dist/{chunk-WP2JFDLA.js → chunk-7EEWQ5DU.js} +93 -118
  6. package/dist/{chunk-SO3POWWR.js → chunk-G3N4S3DM.js} +2 -2
  7. package/dist/{chunk-7NOFXKET.js → chunk-HVXYFUCB.js} +72 -257
  8. package/dist/chunk-L5OUUNLT.js +198 -0
  9. package/dist/{chunk-HBLU2EJ4.js → chunk-OZCYOM5D.js} +1 -1
  10. package/dist/{chunk-4YN353Q7.js → chunk-VANVV4F7.js} +21 -7
  11. package/dist/{chunk-LWNTWBHB.js → chunk-W5WKEV7U.js} +3 -2
  12. package/dist/constants.cjs +3 -2
  13. package/dist/constants.js +7 -7
  14. package/dist/{ephemeris-C10stHhM.d.cts → ephemeris-Ohl6NAfw.d.cts} +11 -1
  15. package/dist/{ephemeris-C10stHhM.d.ts → ephemeris-Ohl6NAfw.d.ts} +11 -1
  16. package/dist/frames.cjs +250 -0
  17. package/dist/frames.d.cts +56 -0
  18. package/dist/frames.d.ts +56 -0
  19. package/dist/frames.js +222 -0
  20. package/dist/index.cjs +133 -152
  21. package/dist/index.d.cts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +96 -88
  24. package/dist/orbit.cjs +103 -7
  25. package/dist/orbit.d.cts +1 -1
  26. package/dist/orbit.d.ts +1 -1
  27. package/dist/orbit.js +3 -1
  28. package/dist/positioning.cjs +565 -0
  29. package/dist/positioning.d.cts +68 -0
  30. package/dist/positioning.d.ts +68 -0
  31. package/dist/positioning.js +206 -0
  32. package/dist/rinex.js +2 -2
  33. package/dist/rtcm3.cjs +116 -117
  34. package/dist/rtcm3.d.cts +9 -3
  35. package/dist/rtcm3.d.ts +9 -3
  36. package/dist/rtcm3.js +9 -2
  37. package/dist/signals.cjs +55 -26
  38. package/dist/signals.d.cts +26 -26
  39. package/dist/signals.d.ts +26 -26
  40. package/dist/signals.js +2 -1
  41. package/dist/time.js +20 -18
  42. package/package.json +11 -2
@@ -0,0 +1,565 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/positioning/index.ts
21
+ var positioning_exports = {};
22
+ __export(positioning_exports, {
23
+ ionoFree: () => ionoFree,
24
+ satClockCorrection: () => satClockCorrection,
25
+ solveSpp: () => solveSpp
26
+ });
27
+ module.exports = __toCommonJS(positioning_exports);
28
+
29
+ // src/constants/wgs84.ts
30
+ var WGS84_SEMI_MAJOR_AXIS = 6378137;
31
+ var WGS84_FLATTENING = 1 / 298.257223563;
32
+ var WGS84_ECCENTRICITY_SQUARED = 0.006694379990197;
33
+
34
+ // src/coordinates/ecef.ts
35
+ function ecefToGeodetic(x, y, z) {
36
+ const lon = Math.atan2(y, x);
37
+ const p = Math.sqrt(x * x + y * y);
38
+ if (p < 1e-10) {
39
+ const b = WGS84_SEMI_MAJOR_AXIS * Math.sqrt(1 - WGS84_ECCENTRICITY_SQUARED);
40
+ const lat2 = z >= 0 ? Math.PI / 2 : -Math.PI / 2;
41
+ const alt2 = Math.abs(z) - b;
42
+ return [lat2, lon, alt2];
43
+ }
44
+ let lat = Math.atan2(z, p * (1 - WGS84_ECCENTRICITY_SQUARED));
45
+ let N;
46
+ for (let i = 0; i < 10; i++) {
47
+ const sinLat2 = Math.sin(lat);
48
+ N = WGS84_SEMI_MAJOR_AXIS / Math.sqrt(1 - WGS84_ECCENTRICITY_SQUARED * sinLat2 * sinLat2);
49
+ const prevLat = lat;
50
+ lat = Math.atan2(z + WGS84_ECCENTRICITY_SQUARED * N * sinLat2, p);
51
+ if (Math.abs(lat - prevLat) < 1e-15) break;
52
+ }
53
+ const sinLat = Math.sin(lat);
54
+ N = WGS84_SEMI_MAJOR_AXIS / Math.sqrt(1 - WGS84_ECCENTRICITY_SQUARED * sinLat * sinLat);
55
+ const alt = p / Math.cos(lat) - N;
56
+ return [lat, lon, alt];
57
+ }
58
+
59
+ // src/constants/time.ts
60
+ var MILLISECONDS_IN_SECOND = 1e3;
61
+ var MILLISECONDS_GPS_TAI = 19e3;
62
+ var START_GPS_TIME = /* @__PURE__ */ new Date("1980-01-06T00:00:00Z");
63
+ var START_BDS_TIME = /* @__PURE__ */ new Date("2006-01-01T00:00:14Z");
64
+ var START_NTP_TIME = /* @__PURE__ */ new Date("1900-01-01T00:00:00Z");
65
+
66
+ // src/time/gnss.ts
67
+ function getTaiDate(date) {
68
+ return new Date(date.getTime() + MILLISECONDS_GPS_TAI);
69
+ }
70
+ function getNtpTime(date) {
71
+ return getTaiDate(date).getTime() - START_NTP_TIME.getTime();
72
+ }
73
+
74
+ // src/time/utc.ts
75
+ var LEAP_SECONDS_TABLE = [
76
+ [3692217600, 37],
77
+ // 1 Jan 2017
78
+ [3644697600, 36],
79
+ // 1 Jul 2015
80
+ [3550089600, 35],
81
+ // 1 Jul 2012
82
+ [3439756800, 34],
83
+ // 1 Jan 2009
84
+ [3345062400, 33],
85
+ // 1 Jan 2006
86
+ [3124137600, 32],
87
+ // 1 Jan 1999
88
+ [3076704e3, 31],
89
+ // 1 Jul 1997
90
+ [3029443200, 30],
91
+ // 1 Jan 1996
92
+ [2982009600, 29],
93
+ // 1 Jul 1994
94
+ [2950473600, 28],
95
+ // 1 Jul 1993
96
+ [2918937600, 27],
97
+ // 1 Jul 1992
98
+ [2871676800, 26],
99
+ // 1 Jan 1991
100
+ [2840140800, 25],
101
+ // 1 Jan 1990
102
+ [2776982400, 24],
103
+ // 1 Jan 1988
104
+ [2698012800, 23],
105
+ // 1 Jul 1985
106
+ [2634854400, 22],
107
+ // 1 Jul 1983
108
+ [2603318400, 21],
109
+ // 1 Jul 1982
110
+ [2571782400, 20],
111
+ // 1 Jul 1981
112
+ [2524521600, 19],
113
+ // 1 Jan 1980
114
+ [2492985600, 18],
115
+ // 1 Jan 1979
116
+ [2461449600, 17],
117
+ // 1 Jan 1978
118
+ [2429913600, 16],
119
+ // 1 Jan 1977
120
+ [2398291200, 15],
121
+ // 1 Jan 1976
122
+ [2366755200, 14],
123
+ // 1 Jan 1975
124
+ [2335219200, 13],
125
+ // 1 Jan 1974
126
+ [2303683200, 12],
127
+ // 1 Jan 1973
128
+ [2287785600, 11],
129
+ // 1 Jul 1972
130
+ [2272060800, 10]
131
+ // 1 Jan 1972
132
+ ];
133
+ function getLeap(date) {
134
+ const ntp_time = getNtpTime(date);
135
+ for (const [timestamp, leapSeconds] of LEAP_SECONDS_TABLE) {
136
+ if (ntp_time / MILLISECONDS_IN_SECOND - leapSeconds >= timestamp) {
137
+ return leapSeconds;
138
+ }
139
+ }
140
+ return 8;
141
+ }
142
+ function getGpsLeap(date) {
143
+ const leap_seconds = getLeap(date);
144
+ if (leap_seconds < 0) return 0;
145
+ return leap_seconds - 19;
146
+ }
147
+
148
+ // src/orbit/index.ts
149
+ var GM_GPS = 3986005e8;
150
+ var GM_GAL = 3986004418e5;
151
+ var GM_BDS = 3986004418e5;
152
+ var GM_GLO = 39860044e7;
153
+ var OMEGA_E = 72921151467e-15;
154
+ var BDS_GEO_MAX_INCLINATION_RAD = 0.1;
155
+ var BDS_GEO_MIN_SEMIMAJOR_AXIS_M = 4e7;
156
+ var AE_GLO = 6378136;
157
+ var J2_GLO = 108263e-8;
158
+ var TWO_PI = 2 * Math.PI;
159
+ function gmForSystem(sys) {
160
+ if (sys === "E") return GM_GAL;
161
+ if (sys === "C") return GM_BDS;
162
+ return GM_GPS;
163
+ }
164
+ function keplerPosition(eph, t) {
165
+ const GM = gmForSystem(eph.system);
166
+ const a = eph.sqrtA * eph.sqrtA;
167
+ const n0 = Math.sqrt(GM / (a * a * a));
168
+ const n = n0 + eph.deltaN;
169
+ let tk = t - eph.toe;
170
+ if (tk > 302400) tk -= 604800;
171
+ if (tk < -302400) tk += 604800;
172
+ const Mk = eph.m0 + n * tk;
173
+ let Ek = Mk;
174
+ for (let i = 0; i < 10; i++) {
175
+ const dE = (Mk - (Ek - eph.e * Math.sin(Ek))) / (1 - eph.e * Math.cos(Ek));
176
+ Ek += dE;
177
+ if (Math.abs(dE) < 1e-12) break;
178
+ }
179
+ const sinE = Math.sin(Ek);
180
+ const cosE = Math.cos(Ek);
181
+ const vk = Math.atan2(Math.sqrt(1 - eph.e * eph.e) * sinE, cosE - eph.e);
182
+ const phik = vk + eph.omega;
183
+ const sin2phi = Math.sin(2 * phik);
184
+ const cos2phi = Math.cos(2 * phik);
185
+ const duk = eph.cus * sin2phi + eph.cuc * cos2phi;
186
+ const drk = eph.crs * sin2phi + eph.crc * cos2phi;
187
+ const dik = eph.cis * sin2phi + eph.cic * cos2phi;
188
+ const uk = phik + duk;
189
+ const rk = a * (1 - eph.e * cosE) + drk;
190
+ const ik = eph.i0 + dik + eph.idot * tk;
191
+ const xp = rk * Math.cos(uk);
192
+ const yp = rk * Math.sin(uk);
193
+ const isBdsGeo = eph.system === "C" && Math.abs(eph.i0) < BDS_GEO_MAX_INCLINATION_RAD && a > BDS_GEO_MIN_SEMIMAJOR_AXIS_M;
194
+ if (isBdsGeo) {
195
+ const omegak2 = eph.omega0 + eph.omegaDot * tk - OMEGA_E * eph.toe;
196
+ const cosO2 = Math.cos(omegak2);
197
+ const sinO2 = Math.sin(omegak2);
198
+ const cosI2 = Math.cos(ik);
199
+ const sinI2 = Math.sin(ik);
200
+ const xg = xp * cosO2 - yp * cosI2 * sinO2;
201
+ const yg = xp * sinO2 + yp * cosI2 * cosO2;
202
+ const zg = yp * sinI2;
203
+ const phi = OMEGA_E * tk;
204
+ const cosPhi = Math.cos(phi);
205
+ const sinPhi = Math.sin(phi);
206
+ const COS5 = Math.cos(-5 * Math.PI / 180);
207
+ const SIN5 = Math.sin(-5 * Math.PI / 180);
208
+ return {
209
+ prn: eph.prn,
210
+ x: xg * cosPhi + yg * sinPhi * COS5 + zg * sinPhi * SIN5,
211
+ y: -xg * sinPhi + yg * cosPhi * COS5 + zg * cosPhi * SIN5,
212
+ z: -yg * SIN5 + zg * COS5
213
+ };
214
+ }
215
+ const omegak = eph.omega0 + (eph.omegaDot - OMEGA_E) * tk - OMEGA_E * eph.toe;
216
+ const cosO = Math.cos(omegak);
217
+ const sinO = Math.sin(omegak);
218
+ const cosI = Math.cos(ik);
219
+ const sinI = Math.sin(ik);
220
+ return {
221
+ prn: eph.prn,
222
+ x: xp * cosO - yp * cosI * sinO,
223
+ y: xp * sinO + yp * cosI * cosO,
224
+ z: yp * sinI
225
+ };
226
+ }
227
+ function gloDerivatives(state, acc) {
228
+ const [x, y, z, vx, vy, vz] = state;
229
+ const [ax, ay, az] = acc;
230
+ const r = Math.sqrt(x * x + y * y + z * z);
231
+ const r2 = r * r;
232
+ const r3 = r2 * r;
233
+ const r5 = r2 * r3;
234
+ const mu_r3 = GM_GLO / r3;
235
+ const j2_term = 1.5 * J2_GLO * AE_GLO * AE_GLO / r5;
236
+ const dvx = -mu_r3 * x + j2_term * x * (5 * z * z / r2 - 1) + OMEGA_E * OMEGA_E * x + 2 * OMEGA_E * vy + ax;
237
+ const dvy = -mu_r3 * y + j2_term * y * (5 * z * z / r2 - 1) + OMEGA_E * OMEGA_E * y - 2 * OMEGA_E * vx + ay;
238
+ const dvz = -mu_r3 * z + j2_term * z * (5 * z * z / r2 - 3) + az;
239
+ return [vx, vy, vz, dvx, dvy, dvz];
240
+ }
241
+ function rk4Step(state, acc, dt) {
242
+ const k1 = gloDerivatives(state, acc);
243
+ const s2 = state.map((v, i) => v + k1[i] * dt / 2);
244
+ const k2 = gloDerivatives(s2, acc);
245
+ const s3 = state.map((v, i) => v + k2[i] * dt / 2);
246
+ const k3 = gloDerivatives(s3, acc);
247
+ const s4 = state.map((v, i) => v + k3[i] * dt);
248
+ const k4 = gloDerivatives(s4, acc);
249
+ return state.map(
250
+ (v, i) => v + dt / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i])
251
+ );
252
+ }
253
+ function glonassPosition(eph, tUtc) {
254
+ const ephTime = eph.tocDate.getTime() / 1e3;
255
+ const dt = tUtc - ephTime;
256
+ let state = [
257
+ eph.x * 1e3,
258
+ eph.y * 1e3,
259
+ eph.z * 1e3,
260
+ eph.xDot * 1e3,
261
+ eph.yDot * 1e3,
262
+ eph.zDot * 1e3
263
+ ];
264
+ const acc = [
265
+ eph.xAcc * 1e3,
266
+ eph.yAcc * 1e3,
267
+ eph.zAcc * 1e3
268
+ ];
269
+ const step = dt > 0 ? 60 : -60;
270
+ const nSteps = Math.floor(Math.abs(dt) / 60);
271
+ const remainder = dt - nSteps * step;
272
+ for (let i = 0; i < nSteps; i++) {
273
+ state = rk4Step(state, acc, step);
274
+ }
275
+ if (Math.abs(remainder) > 1e-3) {
276
+ state = rk4Step(state, acc, remainder);
277
+ }
278
+ return { prn: eph.prn, x: state[0], y: state[1], z: state[2] };
279
+ }
280
+ function ecefToAzEl(rxX, rxY, rxZ, satX, satY, satZ) {
281
+ const [lat, lon] = ecefToGeodetic(rxX, rxY, rxZ);
282
+ const dx = satX - rxX;
283
+ const dy = satY - rxY;
284
+ const dz = satZ - rxZ;
285
+ const sinLat = Math.sin(lat);
286
+ const cosLat = Math.cos(lat);
287
+ const sinLon = Math.sin(lon);
288
+ const cosLon = Math.cos(lon);
289
+ const east = -sinLon * dx + cosLon * dy;
290
+ const north = -sinLat * cosLon * dx - sinLat * sinLon * dy + cosLat * dz;
291
+ const up = cosLat * cosLon * dx + cosLat * sinLon * dy + sinLat * dz;
292
+ const az = Math.atan2(east, north);
293
+ const el = Math.atan2(up, Math.sqrt(east * east + north * north));
294
+ return { az: (az + TWO_PI) % TWO_PI, el };
295
+ }
296
+ function computeDop(satAzEls) {
297
+ const n = satAzEls.length;
298
+ if (n < 4) return null;
299
+ const H = [];
300
+ for (const { az, el } of satAzEls) {
301
+ const cosEl = Math.cos(el);
302
+ H.push([cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el), 1]);
303
+ }
304
+ const HtH = Array.from({ length: 4 }, () => new Array(4).fill(0));
305
+ for (let i = 0; i < 4; i++) {
306
+ for (let j = 0; j < 4; j++) {
307
+ let sum = 0;
308
+ for (let k = 0; k < n; k++) {
309
+ sum += H[k][i] * H[k][j];
310
+ }
311
+ HtH[i][j] = sum;
312
+ }
313
+ }
314
+ const Q = invert4x4(HtH);
315
+ if (!Q) return null;
316
+ const gdop = Math.sqrt(Q[0][0] + Q[1][1] + Q[2][2] + Q[3][3]);
317
+ const pdop = Math.sqrt(Q[0][0] + Q[1][1] + Q[2][2]);
318
+ const hdop = Math.sqrt(Q[0][0] + Q[1][1]);
319
+ const vdop = Math.sqrt(Q[2][2]);
320
+ return { gdop, pdop, hdop, vdop };
321
+ }
322
+ function invert4x4(m) {
323
+ const a = m.flat();
324
+ const inv = new Array(16).fill(0);
325
+ 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];
326
+ 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];
327
+ 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];
328
+ 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];
329
+ 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];
330
+ 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];
331
+ 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];
332
+ 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];
333
+ 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];
334
+ 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];
335
+ 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];
336
+ 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];
337
+ 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];
338
+ 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];
339
+ 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];
340
+ 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];
341
+ let det = a[0] * inv[0] + a[1] * inv[4] + a[2] * inv[8] + a[3] * inv[12];
342
+ if (Math.abs(det) < 1e-20) return null;
343
+ det = 1 / det;
344
+ const result = Array.from(
345
+ { length: 4 },
346
+ () => new Array(4).fill(0)
347
+ );
348
+ for (let i = 0; i < 4; i++) {
349
+ for (let j = 0; j < 4; j++) {
350
+ result[i][j] = inv[i * 4 + j] * det;
351
+ }
352
+ }
353
+ return result;
354
+ }
355
+ function computeSatPosition(eph, timeMs) {
356
+ if (eph.system === "R" || eph.system === "S") {
357
+ const leapMs = getGpsLeap(new Date(timeMs)) * 1e3;
358
+ return glonassPosition(eph, (timeMs - leapMs) / 1e3);
359
+ }
360
+ const GPS_EPOCH = START_GPS_TIME.getTime();
361
+ const gpsSeconds = (timeMs - GPS_EPOCH) / 1e3;
362
+ let tow = gpsSeconds % (7 * 86400);
363
+ if (eph.system === "C") tow = (tow - 14 + 604800) % 604800;
364
+ return keplerPosition(eph, tow);
365
+ }
366
+ var GPS_EPOCH_MS = START_GPS_TIME.getTime();
367
+ var BDS_EPOCH_MS = START_BDS_TIME.getTime();
368
+
369
+ // src/constants/gnss.ts
370
+ var C_LIGHT = 299792458;
371
+
372
+ // src/positioning/index.ts
373
+ var GM_GPS2 = 3986005e8;
374
+ var F_REL = -4442807633e-19;
375
+ function satClockCorrection(eph, tMs) {
376
+ if (eph.system === "R" || eph.system === "S") {
377
+ const dt2 = (tMs - eph.tocDate.getTime()) / 1e3;
378
+ return eph.tauN + eph.gammaN * dt2;
379
+ }
380
+ const k = eph;
381
+ const dt = (tMs - k.tocDate.getTime()) / 1e3;
382
+ const poly = k.af0 + k.af1 * dt + k.af2 * dt * dt;
383
+ const a = k.sqrtA * k.sqrtA;
384
+ const n = Math.sqrt(GM_GPS2 / (a * a * a)) + k.deltaN;
385
+ const tk = dt;
386
+ const Mk = k.m0 + n * tk;
387
+ let Ek = Mk;
388
+ for (let i = 0; i < 8; i++) {
389
+ Ek = Mk + k.e * Math.sin(Ek);
390
+ }
391
+ const rel = F_REL * k.e * k.sqrtA * Math.sin(Ek);
392
+ return poly + rel;
393
+ }
394
+ function ionoFree(p1, p2, f1, f2) {
395
+ const g = f1 * f1 / (f1 * f1 - f2 * f2);
396
+ return g * p1 - (g - 1) * p2;
397
+ }
398
+ function tropoDelay(elevationRad) {
399
+ const sinEl = Math.sin(elevationRad);
400
+ return 2.47 / (sinEl + 0.0121);
401
+ }
402
+ function sagnac(pos, travelTimeS) {
403
+ const OMEGA_E2 = 72921151467e-15;
404
+ const theta = OMEGA_E2 * travelTimeS;
405
+ const c = Math.cos(theta);
406
+ const s = Math.sin(theta);
407
+ return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
408
+ }
409
+ function solveLinear(A, b) {
410
+ const n = b.length;
411
+ const M = A.map((row, i) => [...row, b[i]]);
412
+ for (let col = 0; col < n; col++) {
413
+ let pivot = col;
414
+ for (let r = col + 1; r < n; r++) {
415
+ if (Math.abs(M[r][col]) > Math.abs(M[pivot][col])) pivot = r;
416
+ }
417
+ if (Math.abs(M[pivot][col]) < 1e-12) return null;
418
+ [M[col], M[pivot]] = [M[pivot], M[col]];
419
+ for (let r = 0; r < n; r++) {
420
+ if (r === col) continue;
421
+ const f = M[r][col] / M[col][col];
422
+ for (let c = col; c <= n; c++) M[r][c] -= f * M[col][c];
423
+ }
424
+ }
425
+ return M.map((row, i) => row[n] / M[i][i]);
426
+ }
427
+ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
428
+ const {
429
+ elevationMaskDeg = 10,
430
+ troposphere = true,
431
+ maxIterations = 15,
432
+ convergenceM = 1e-4
433
+ } = opts;
434
+ const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
435
+ const minSats = (list) => 3 + new Set(list.map((p) => p[0])).size;
436
+ if (all.length < minSats(all)) return null;
437
+ const gaussNewton = (prns) => {
438
+ const systems = [...new Set(prns.map((p) => p[0]))].sort();
439
+ const dim = 3 + systems.length;
440
+ const sysIndex = new Map(systems.map((s, i) => [s, 3 + i]));
441
+ let x2 = 0;
442
+ let y2 = 0;
443
+ let z2 = 0;
444
+ const clock2 = new Map(systems.map((s) => [s, 0]));
445
+ const residuals2 = {};
446
+ let iterations2 = 0;
447
+ let converged2 = false;
448
+ for (let iter = 0; iter < maxIterations; iter++) {
449
+ iterations2 = iter + 1;
450
+ const HtH = Array.from(
451
+ { length: dim },
452
+ () => new Array(dim).fill(0)
453
+ );
454
+ const Htv = new Array(dim).fill(0);
455
+ let rows = 0;
456
+ for (const k of Object.keys(residuals2)) delete residuals2[k];
457
+ for (const prn of prns) {
458
+ const psr = pseudoranges.get(prn);
459
+ const eph = ephemerides.get(prn);
460
+ const sys = prn[0];
461
+ const clk = clock2.get(sys);
462
+ const tTx = timeMs - psr / C_LIGHT * 1e3;
463
+ const sat = computeSatPosition(eph, tTx);
464
+ if (!Number.isFinite(sat.x)) continue;
465
+ const dts = satClockCorrection(eph, tTx);
466
+ const travel = Math.hypot(sat.x - x2, sat.y - y2, sat.z - z2) / C_LIGHT;
467
+ const [sx, sy, sz] = sagnac(sat, travel);
468
+ const rho = Math.hypot(sx - x2, sy - y2, sz - z2);
469
+ const ux = (x2 - sx) / rho;
470
+ const uy = (y2 - sy) / rho;
471
+ const uz = (z2 - sz) / rho;
472
+ let elev = Math.PI / 2;
473
+ let weight = 1;
474
+ const positionSane = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
475
+ if (positionSane) {
476
+ const azel = ecefToAzEl(x2, y2, z2, sx, sy, sz);
477
+ elev = azel.el;
478
+ if (iter >= 2 && elev < elevationMaskDeg * Math.PI / 180) continue;
479
+ const sinEl = Math.max(Math.sin(elev), 0.1);
480
+ weight = sinEl * sinEl;
481
+ }
482
+ const tropo = troposphere && positionSane ? tropoDelay(elev) : 0;
483
+ const predicted = rho + clk - C_LIGHT * dts + tropo;
484
+ const v = psr - predicted;
485
+ const h = new Array(dim).fill(0);
486
+ h[0] = ux;
487
+ h[1] = uy;
488
+ h[2] = uz;
489
+ h[sysIndex.get(sys)] = 1;
490
+ for (let i = 0; i < dim; i++) {
491
+ for (let j = 0; j < dim; j++) HtH[i][j] += weight * h[i] * h[j];
492
+ Htv[i] += weight * h[i] * v;
493
+ }
494
+ residuals2[prn] = v;
495
+ rows++;
496
+ }
497
+ if (rows < dim) return null;
498
+ const dx = solveLinear(HtH, Htv);
499
+ if (!dx) return null;
500
+ x2 += dx[0];
501
+ y2 += dx[1];
502
+ z2 += dx[2];
503
+ for (const s of systems)
504
+ clock2.set(s, clock2.get(s) + dx[sysIndex.get(s)]);
505
+ if (Math.hypot(dx[0], dx[1], dx[2]) < convergenceM) {
506
+ converged2 = true;
507
+ break;
508
+ }
509
+ }
510
+ return { x: x2, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
511
+ };
512
+ const REJECT_THRESHOLD_M = 50;
513
+ let candidates = all;
514
+ const rejected = [];
515
+ let inner = null;
516
+ for (let round = 0; round <= all.length; round++) {
517
+ inner = gaussNewton(candidates);
518
+ if (!inner) return null;
519
+ const vals = Object.entries(inner.residuals);
520
+ let worst = null;
521
+ let worstAbs = REJECT_THRESHOLD_M;
522
+ for (const [prn, v] of vals) {
523
+ if (Math.abs(v) > worstAbs) {
524
+ worstAbs = Math.abs(v);
525
+ worst = prn;
526
+ }
527
+ }
528
+ if (!worst) break;
529
+ const remaining = candidates.filter((p) => p !== worst);
530
+ if (remaining.length < minSats(remaining)) break;
531
+ rejected.push(worst);
532
+ candidates = remaining;
533
+ }
534
+ if (!inner) return null;
535
+ const { x, y, z, clock, residuals, iterations, converged } = inner;
536
+ const used = [];
537
+ const azels = [];
538
+ for (const prn of candidates) {
539
+ if (!(prn in residuals)) continue;
540
+ const eph = ephemerides.get(prn);
541
+ const sat = computeSatPosition(eph, timeMs);
542
+ if (!Number.isFinite(sat.x)) continue;
543
+ const azel = ecefToAzEl(x, y, z, sat.x, sat.y, sat.z);
544
+ if (azel.el >= elevationMaskDeg * Math.PI / 180) {
545
+ used.push(prn);
546
+ azels.push(azel);
547
+ }
548
+ }
549
+ return {
550
+ position: [x, y, z],
551
+ clockBias: Object.fromEntries(clock),
552
+ usedSatellites: used,
553
+ rejectedSatellites: rejected,
554
+ residuals,
555
+ dop: computeDop(azels),
556
+ iterations,
557
+ converged
558
+ };
559
+ }
560
+ // Annotate the CommonJS export names for ESM import in node:
561
+ 0 && (module.exports = {
562
+ ionoFree,
563
+ satClockCorrection,
564
+ solveSpp
565
+ });
@@ -0,0 +1,68 @@
1
+ import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
2
+ import { DopValues } from './orbit.cjs';
3
+ import './ephemeris-Ohl6NAfw.cjs';
4
+ import './ecef-CF0uAysr.cjs';
5
+
6
+ /**
7
+ * Single-point positioning (SPP) from pseudoranges and broadcast
8
+ * ephemerides.
9
+ *
10
+ * Weighted iterative least squares with one receiver-clock unknown per
11
+ * constellation (absorbing inter-system time offsets), satellite clock
12
+ * polynomial + relativistic correction, Sagnac (Earth-rotation)
13
+ * correction, elevation masking/weighting, and a simple tropospheric
14
+ * model. Ionospheric delay is NOT modelled — use the iono-free
15
+ * combination (ionoFree helper) with dual-frequency pseudoranges for
16
+ * metre-level results, or expect ~2–10 m of iono bias on L1-only.
17
+ */
18
+
19
+ interface SppOptions {
20
+ /** Elevation mask in degrees (applied after the first pass). Default 10. */
21
+ elevationMaskDeg?: number;
22
+ /** Apply the simple tropospheric model. Default true. */
23
+ troposphere?: boolean;
24
+ /** Maximum Gauss-Newton iterations. Default 15. */
25
+ maxIterations?: number;
26
+ /** Convergence threshold on the position update (m). Default 1e-4. */
27
+ convergenceM?: number;
28
+ }
29
+ interface SppSolution {
30
+ /** Receiver position, ECEF meters. */
31
+ position: [number, number, number];
32
+ /** Receiver clock bias per constellation (meters). */
33
+ clockBias: Record<string, number>;
34
+ /** PRNs used in the final solution. */
35
+ usedSatellites: string[];
36
+ /** PRNs rejected as outliers (bad ephemeris/measurement). */
37
+ rejectedSatellites: string[];
38
+ /** Post-fit residuals per PRN (m). */
39
+ residuals: Record<string, number>;
40
+ /** Dilution of precision at the solution. */
41
+ dop: DopValues | null;
42
+ iterations: number;
43
+ converged: boolean;
44
+ }
45
+ /**
46
+ * Broadcast satellite clock correction in seconds at time `tMs`
47
+ * (GPS-scale epoch milliseconds), including the relativistic
48
+ * eccentricity term for Keplerian systems.
49
+ */
50
+ declare function satClockCorrection(eph: Ephemeris, tMs: number): number;
51
+ /**
52
+ * Ionosphere-free pseudorange combination of two frequencies (Hz).
53
+ * Removes the first-order ionospheric delay.
54
+ */
55
+ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): number;
56
+ /**
57
+ * Solve receiver position from one epoch of pseudoranges.
58
+ *
59
+ * @param pseudoranges PRN → pseudorange in meters (single frequency, or
60
+ * an iono-free combination built with `ionoFree`).
61
+ * @param ephemerides PRN → broadcast ephemeris (from `parseNavFile` or
62
+ * the RTCM decoder via `ephInfoToEphemeris`).
63
+ * @param timeMs Receiver epoch in GPS-scale milliseconds (RINEX epoch
64
+ * time as produced by the parser).
65
+ */
66
+ declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
67
+
68
+ export { type SppOptions, type SppSolution, ionoFree, satClockCorrection, solveSpp };
@@ -0,0 +1,68 @@
1
+ import { E as Ephemeris } from './nav-BAI1a9vK.js';
2
+ import { DopValues } from './orbit.js';
3
+ import './ephemeris-Ohl6NAfw.js';
4
+ import './ecef-CF0uAysr.js';
5
+
6
+ /**
7
+ * Single-point positioning (SPP) from pseudoranges and broadcast
8
+ * ephemerides.
9
+ *
10
+ * Weighted iterative least squares with one receiver-clock unknown per
11
+ * constellation (absorbing inter-system time offsets), satellite clock
12
+ * polynomial + relativistic correction, Sagnac (Earth-rotation)
13
+ * correction, elevation masking/weighting, and a simple tropospheric
14
+ * model. Ionospheric delay is NOT modelled — use the iono-free
15
+ * combination (ionoFree helper) with dual-frequency pseudoranges for
16
+ * metre-level results, or expect ~2–10 m of iono bias on L1-only.
17
+ */
18
+
19
+ interface SppOptions {
20
+ /** Elevation mask in degrees (applied after the first pass). Default 10. */
21
+ elevationMaskDeg?: number;
22
+ /** Apply the simple tropospheric model. Default true. */
23
+ troposphere?: boolean;
24
+ /** Maximum Gauss-Newton iterations. Default 15. */
25
+ maxIterations?: number;
26
+ /** Convergence threshold on the position update (m). Default 1e-4. */
27
+ convergenceM?: number;
28
+ }
29
+ interface SppSolution {
30
+ /** Receiver position, ECEF meters. */
31
+ position: [number, number, number];
32
+ /** Receiver clock bias per constellation (meters). */
33
+ clockBias: Record<string, number>;
34
+ /** PRNs used in the final solution. */
35
+ usedSatellites: string[];
36
+ /** PRNs rejected as outliers (bad ephemeris/measurement). */
37
+ rejectedSatellites: string[];
38
+ /** Post-fit residuals per PRN (m). */
39
+ residuals: Record<string, number>;
40
+ /** Dilution of precision at the solution. */
41
+ dop: DopValues | null;
42
+ iterations: number;
43
+ converged: boolean;
44
+ }
45
+ /**
46
+ * Broadcast satellite clock correction in seconds at time `tMs`
47
+ * (GPS-scale epoch milliseconds), including the relativistic
48
+ * eccentricity term for Keplerian systems.
49
+ */
50
+ declare function satClockCorrection(eph: Ephemeris, tMs: number): number;
51
+ /**
52
+ * Ionosphere-free pseudorange combination of two frequencies (Hz).
53
+ * Removes the first-order ionospheric delay.
54
+ */
55
+ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): number;
56
+ /**
57
+ * Solve receiver position from one epoch of pseudoranges.
58
+ *
59
+ * @param pseudoranges PRN → pseudorange in meters (single frequency, or
60
+ * an iono-free combination built with `ionoFree`).
61
+ * @param ephemerides PRN → broadcast ephemeris (from `parseNavFile` or
62
+ * the RTCM decoder via `ephInfoToEphemeris`).
63
+ * @param timeMs Receiver epoch in GPS-scale milliseconds (RINEX epoch
64
+ * time as produced by the parser).
65
+ */
66
+ declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
67
+
68
+ export { type SppOptions, type SppSolution, ionoFree, satClockCorrection, solveSpp };