gnss-js 1.19.0 → 1.21.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-2IM5VXNI.js → chunk-7RKLWE6R.js} +23 -2
- package/dist/{chunk-QXZZNXE3.js → chunk-B5LU4746.js} +1 -1
- package/dist/{chunk-JW2ICDR7.js → chunk-DLXTTHRK.js} +2 -4
- package/dist/{chunk-OMVL3X7V.js → chunk-JNFV5BYB.js} +186 -8
- package/dist/chunk-K3V3NHWL.js +2541 -0
- package/dist/{chunk-K73SERD5.js → chunk-NOQAAUGY.js} +2 -4
- package/dist/{chunk-6NCMACDO.js → chunk-WZDFZ76K.js} +149 -8
- package/dist/index.cjs +1675 -57
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +23 -8
- package/dist/nav-BYmYoh9A.d.cts +137 -0
- package/dist/nav-CN8rxL4h.d.ts +137 -0
- package/dist/novatel.cjs +230 -15
- package/dist/novatel.d.cts +8 -1
- package/dist/novatel.d.ts +8 -1
- package/dist/novatel.js +2 -2
- package/dist/orbit.cjs +190 -8
- package/dist/orbit.d.cts +90 -1
- package/dist/orbit.d.ts +90 -1
- package/dist/orbit.js +9 -1
- package/dist/positioning.d.cts +1 -0
- package/dist/positioning.d.ts +1 -0
- package/dist/positioning.js +2 -2
- package/dist/rinex.js +2 -3
- package/dist/sbf.cjs +1433 -11
- package/dist/sbf.d.cts +286 -136
- package/dist/sbf.d.ts +286 -136
- package/dist/sbf.js +10 -3
- package/dist/ubx.js +2 -3
- package/package.json +1 -1
- package/dist/chunk-5S5HMAKW.js +0 -1123
- package/dist/chunk-IQ5OIMQD.js +0 -148
|
@@ -0,0 +1,2541 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CnavAssembler,
|
|
3
|
+
GPS_PI,
|
|
4
|
+
cnavCrcOk,
|
|
5
|
+
crc24q,
|
|
6
|
+
getBitS,
|
|
7
|
+
getBitU
|
|
8
|
+
} from "./chunk-WZDFZ76K.js";
|
|
9
|
+
import {
|
|
10
|
+
getGpsLeap,
|
|
11
|
+
getUtcDate
|
|
12
|
+
} from "./chunk-HVXYFUCB.js";
|
|
13
|
+
|
|
14
|
+
// src/sbf/frame.ts
|
|
15
|
+
var CRC_TABLE = (() => {
|
|
16
|
+
const t = new Uint16Array(256);
|
|
17
|
+
for (let n = 0; n < 256; n++) {
|
|
18
|
+
let c = n << 8;
|
|
19
|
+
for (let k = 0; k < 8; k++)
|
|
20
|
+
c = c & 32768 ? (c << 1 ^ 4129) & 65535 : c << 1 & 65535;
|
|
21
|
+
t[n] = c;
|
|
22
|
+
}
|
|
23
|
+
return t;
|
|
24
|
+
})();
|
|
25
|
+
function crc16(data, start, end) {
|
|
26
|
+
let crc = 0;
|
|
27
|
+
for (let i = start; i < end; i++)
|
|
28
|
+
crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
|
|
29
|
+
return crc;
|
|
30
|
+
}
|
|
31
|
+
function scanSbfFrames(data, view, onBlock) {
|
|
32
|
+
let badCrc = 0;
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i + 8 <= data.length) {
|
|
35
|
+
if (data[i] !== 36 || data[i + 1] !== 64) {
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const len = view.getUint16(i + 6, true);
|
|
40
|
+
if (len < 8 || len % 4 !== 0) {
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (i + len > data.length) break;
|
|
45
|
+
if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
|
|
46
|
+
badCrc++;
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
onBlock(view.getUint16(i + 4, true) & 8191, i, len);
|
|
51
|
+
i += len;
|
|
52
|
+
}
|
|
53
|
+
return badCrc;
|
|
54
|
+
}
|
|
55
|
+
var two = (n) => String(n).padStart(2, "0");
|
|
56
|
+
function svidToPrn(svid) {
|
|
57
|
+
if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
|
|
58
|
+
if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
|
|
59
|
+
if (svid <= 62) return null;
|
|
60
|
+
if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
|
|
61
|
+
if (svid <= 70) return null;
|
|
62
|
+
if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
|
|
63
|
+
if (svid <= 119) return null;
|
|
64
|
+
if (svid <= 140) return `S${two(svid - 100)}`;
|
|
65
|
+
if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
|
|
66
|
+
if (svid <= 190) return `J${two(svid - 180)}`;
|
|
67
|
+
if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
|
|
68
|
+
if (svid <= 215) return `S${two(svid - 157)}`;
|
|
69
|
+
if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
|
|
70
|
+
if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/sbf/nav.ts
|
|
75
|
+
var PI = Math.PI;
|
|
76
|
+
var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
|
|
77
|
+
var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
|
|
78
|
+
var SEC_PER_WEEK = 7 * 86400;
|
|
79
|
+
var MS_PER_WEEK = SEC_PER_WEEK * 1e3;
|
|
80
|
+
var F4_DNU = -2e10;
|
|
81
|
+
var U4_DNU = 4294967295;
|
|
82
|
+
function adjustWeek(refWeek, wn, mod) {
|
|
83
|
+
let offset = refWeek % mod - wn;
|
|
84
|
+
if (offset > mod / 2) offset -= mod;
|
|
85
|
+
if (offset < -(mod / 2 - 1)) offset += mod;
|
|
86
|
+
return refWeek - offset;
|
|
87
|
+
}
|
|
88
|
+
var gpsMs = (week, sec) => GPS_EPOCH_MS + week * MS_PER_WEEK + sec * 1e3;
|
|
89
|
+
var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK;
|
|
90
|
+
function decodeGpsQzsNav(view, b, sys) {
|
|
91
|
+
const svid = view.getUint8(b + 14);
|
|
92
|
+
const prn = sys === "G" ? svid : svid - 180;
|
|
93
|
+
const max = sys === "G" ? 32 : 10;
|
|
94
|
+
if (prn < 1 || prn > max) return null;
|
|
95
|
+
const wnc = view.getUint16(b + 12, true);
|
|
96
|
+
const tgdRaw = view.getFloat32(b + 28, true);
|
|
97
|
+
const tocs = view.getUint32(b + 32, true);
|
|
98
|
+
const toes = view.getUint32(b + 88, true);
|
|
99
|
+
const wnToc = adjustWeek(wnc, view.getUint16(b + 136, true), 1024);
|
|
100
|
+
const tocDate = new Date(gpsMs(wnToc, tocs));
|
|
101
|
+
return {
|
|
102
|
+
system: sys,
|
|
103
|
+
prn: `${sys}${String(prn).padStart(2, "0")}`,
|
|
104
|
+
toc: sowOf(tocDate.getTime()),
|
|
105
|
+
tocDate,
|
|
106
|
+
af0: view.getFloat32(b + 44, true),
|
|
107
|
+
af1: view.getFloat32(b + 40, true),
|
|
108
|
+
af2: view.getFloat32(b + 36, true),
|
|
109
|
+
iode: view.getUint8(b + 24),
|
|
110
|
+
crs: view.getFloat32(b + 48, true),
|
|
111
|
+
deltaN: view.getFloat32(b + 52, true) * PI,
|
|
112
|
+
m0: view.getFloat64(b + 56, true) * PI,
|
|
113
|
+
cuc: view.getFloat32(b + 64, true),
|
|
114
|
+
e: view.getFloat64(b + 68, true),
|
|
115
|
+
cus: view.getFloat32(b + 76, true),
|
|
116
|
+
sqrtA: view.getFloat64(b + 80, true),
|
|
117
|
+
toe: toes,
|
|
118
|
+
cic: view.getFloat32(b + 92, true),
|
|
119
|
+
omega0: view.getFloat64(b + 96, true) * PI,
|
|
120
|
+
cis: view.getFloat32(b + 104, true),
|
|
121
|
+
i0: view.getFloat64(b + 108, true) * PI,
|
|
122
|
+
crc: view.getFloat32(b + 116, true),
|
|
123
|
+
omega: view.getFloat64(b + 120, true) * PI,
|
|
124
|
+
omegaDot: view.getFloat32(b + 128, true) * PI,
|
|
125
|
+
idot: view.getFloat32(b + 132, true) * PI,
|
|
126
|
+
// RINEX "GPS week to go with toe": RTKLIB writes WNc for GPS and
|
|
127
|
+
// the week of toc for QZSS — mirrored here so records compare
|
|
128
|
+
// 1:1 against convbin output. wnToe is still used for `toe` above.
|
|
129
|
+
week: sys === "G" ? wnc : wnToc,
|
|
130
|
+
svHealth: view.getUint8(b + 20),
|
|
131
|
+
tgd: tgdRaw !== F4_DNU ? tgdRaw : 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function decodeGalNav(view, b) {
|
|
135
|
+
const prn = view.getUint8(b + 14) - 70;
|
|
136
|
+
if (prn < 1 || prn > 36) return null;
|
|
137
|
+
const source = view.getUint8(b + 15);
|
|
138
|
+
if (source !== 2 && source !== 16) return null;
|
|
139
|
+
const wnc = view.getUint16(b + 12, true);
|
|
140
|
+
const toes = view.getUint32(b + 100, true);
|
|
141
|
+
const tocs = view.getUint32(b + 104, true);
|
|
142
|
+
const wnToc = adjustWeek(wnc, view.getUint16(b + 126, true), 4096);
|
|
143
|
+
const tocDate = new Date(gpsMs(wnToc, tocs));
|
|
144
|
+
const health = view.getUint16(b + 130, true);
|
|
145
|
+
let svh = 0;
|
|
146
|
+
if (health & 1) svh |= health >> 1 & 7;
|
|
147
|
+
if (health & 16) svh |= (health >> 5 & 7) << 6;
|
|
148
|
+
if (health & 256) svh |= (health >> 9 & 7) << 3;
|
|
149
|
+
const bgdE5a = view.getFloat32(b + 136, true);
|
|
150
|
+
return {
|
|
151
|
+
system: "E",
|
|
152
|
+
prn: `E${String(prn).padStart(2, "0")}`,
|
|
153
|
+
toc: sowOf(tocDate.getTime()),
|
|
154
|
+
tocDate,
|
|
155
|
+
af0: view.getFloat64(b + 116, true),
|
|
156
|
+
af1: view.getFloat32(b + 112, true),
|
|
157
|
+
af2: view.getFloat32(b + 108, true),
|
|
158
|
+
iode: view.getUint16(b + 128, true),
|
|
159
|
+
// IODnav
|
|
160
|
+
crs: view.getFloat32(b + 88, true),
|
|
161
|
+
deltaN: view.getFloat32(b + 72, true) * PI,
|
|
162
|
+
m0: view.getFloat64(b + 24, true) * PI,
|
|
163
|
+
cuc: view.getFloat32(b + 76, true),
|
|
164
|
+
e: view.getFloat64(b + 32, true),
|
|
165
|
+
cus: view.getFloat32(b + 80, true),
|
|
166
|
+
sqrtA: view.getFloat64(b + 16, true),
|
|
167
|
+
toe: toes,
|
|
168
|
+
cic: view.getFloat32(b + 92, true),
|
|
169
|
+
omega0: view.getFloat64(b + 56, true) * PI,
|
|
170
|
+
cis: view.getFloat32(b + 96, true),
|
|
171
|
+
i0: view.getFloat64(b + 40, true) * PI,
|
|
172
|
+
crc: view.getFloat32(b + 84, true),
|
|
173
|
+
omega: view.getFloat64(b + 48, true) * PI,
|
|
174
|
+
omegaDot: view.getFloat32(b + 64, true) * PI,
|
|
175
|
+
idot: view.getFloat32(b + 68, true) * PI,
|
|
176
|
+
week: wnc,
|
|
177
|
+
// RINEX GAL week is GPS-aligned and continuous
|
|
178
|
+
svHealth: svh,
|
|
179
|
+
tgd: bgdE5a !== F4_DNU ? bgdE5a : 0
|
|
180
|
+
// BGD E5a/E1 (RINEX slot)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function decodeBdsNav(view, b) {
|
|
184
|
+
const prn = svidToPrn(view.getUint8(b + 14));
|
|
185
|
+
if (!prn || prn[0] !== "C") return null;
|
|
186
|
+
const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
|
|
187
|
+
const tgd1 = view.getFloat32(b + 24, true);
|
|
188
|
+
const tocs = view.getUint32(b + 32, true);
|
|
189
|
+
const toes = view.getUint32(b + 88, true);
|
|
190
|
+
const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
|
|
191
|
+
const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
|
|
192
|
+
const tocDate = new Date(BDT_EPOCH_MS + wnToc * MS_PER_WEEK + tocs * 1e3);
|
|
193
|
+
return {
|
|
194
|
+
system: "C",
|
|
195
|
+
prn,
|
|
196
|
+
toc: sowOf(tocDate.getTime()),
|
|
197
|
+
tocDate,
|
|
198
|
+
af0: view.getFloat32(b + 44, true),
|
|
199
|
+
af1: view.getFloat32(b + 40, true),
|
|
200
|
+
af2: view.getFloat32(b + 36, true),
|
|
201
|
+
iode: view.getUint8(b + 21),
|
|
202
|
+
// AODE
|
|
203
|
+
crs: view.getFloat32(b + 48, true),
|
|
204
|
+
deltaN: view.getFloat32(b + 52, true) * PI,
|
|
205
|
+
m0: view.getFloat64(b + 56, true) * PI,
|
|
206
|
+
cuc: view.getFloat32(b + 64, true),
|
|
207
|
+
e: view.getFloat64(b + 68, true),
|
|
208
|
+
cus: view.getFloat32(b + 76, true),
|
|
209
|
+
sqrtA: view.getFloat64(b + 80, true),
|
|
210
|
+
toe: toes,
|
|
211
|
+
cic: view.getFloat32(b + 92, true),
|
|
212
|
+
omega0: view.getFloat64(b + 96, true) * PI,
|
|
213
|
+
cis: view.getFloat32(b + 104, true),
|
|
214
|
+
i0: view.getFloat64(b + 108, true) * PI,
|
|
215
|
+
crc: view.getFloat32(b + 116, true),
|
|
216
|
+
omega: view.getFloat64(b + 120, true) * PI,
|
|
217
|
+
omegaDot: view.getFloat32(b + 128, true) * PI,
|
|
218
|
+
idot: view.getFloat32(b + 132, true) * PI,
|
|
219
|
+
week: wnToe,
|
|
220
|
+
// RINEX BDS week field is the BDT week
|
|
221
|
+
svHealth: view.getUint8(b + 19),
|
|
222
|
+
// SatH1
|
|
223
|
+
tgd: tgd1 !== F4_DNU ? tgd1 : 0
|
|
224
|
+
// TGD1 B1/B3 (RINEX slot)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function decodeGloNav(view, b) {
|
|
228
|
+
const prn = svidToPrn(view.getUint8(b + 14));
|
|
229
|
+
if (!prn || prn[0] !== "R") return null;
|
|
230
|
+
const wnc = view.getUint16(b + 12, true);
|
|
231
|
+
const toes = view.getUint32(b + 76, true);
|
|
232
|
+
const wnToe = adjustWeek(wnc, view.getUint16(b + 80, true), 1024);
|
|
233
|
+
const toeGpsMs = gpsMs(wnToe, toes);
|
|
234
|
+
const leapMs = getGpsLeap(new Date(toeGpsMs)) * 1e3;
|
|
235
|
+
const tocDate = new Date(toeGpsMs - leapMs);
|
|
236
|
+
const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
|
|
237
|
+
const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
|
|
238
|
+
const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS) / 1e3 % SEC_PER_WEEK;
|
|
239
|
+
return {
|
|
240
|
+
system: "R",
|
|
241
|
+
prn,
|
|
242
|
+
tocDate,
|
|
243
|
+
// RINEX stores −τn as the clock bias; SBF carries τn (ICD sign)
|
|
244
|
+
tauN: -view.getFloat32(b + 68, true),
|
|
245
|
+
gammaN: view.getFloat32(b + 64, true),
|
|
246
|
+
messageFrameTime,
|
|
247
|
+
x: view.getFloat64(b + 16, true),
|
|
248
|
+
// SBF unit is km, like RINEX
|
|
249
|
+
xDot: view.getFloat32(b + 40, true),
|
|
250
|
+
xAcc: view.getFloat32(b + 52, true),
|
|
251
|
+
y: view.getFloat64(b + 24, true),
|
|
252
|
+
yDot: view.getFloat32(b + 44, true),
|
|
253
|
+
yAcc: view.getFloat32(b + 56, true),
|
|
254
|
+
z: view.getFloat64(b + 32, true),
|
|
255
|
+
zDot: view.getFloat32(b + 48, true),
|
|
256
|
+
zAcc: view.getFloat32(b + 60, true),
|
|
257
|
+
// MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
|
|
258
|
+
health: view.getUint8(b + 85) >> 2,
|
|
259
|
+
freqNum: view.getUint8(b + 15) - 8
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function parseSbfNav(data) {
|
|
263
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
264
|
+
const ephemerides = [];
|
|
265
|
+
const badCrc = scanSbfFrames(data, view, (id, b, len) => {
|
|
266
|
+
let eph = null;
|
|
267
|
+
if (id === 5891 && len >= 140) eph = decodeGpsQzsNav(view, b, "G");
|
|
268
|
+
else if (id === 4095 && len >= 140) eph = decodeGpsQzsNav(view, b, "J");
|
|
269
|
+
else if (id === 4002 && len >= 149) eph = decodeGalNav(view, b);
|
|
270
|
+
else if (id === 4081 && len >= 140) eph = decodeBdsNav(view, b);
|
|
271
|
+
else if (id === 4004 && len >= 96) eph = decodeGloNav(view, b);
|
|
272
|
+
if (eph) ephemerides.push(eph);
|
|
273
|
+
});
|
|
274
|
+
return { ephemerides, badCrc };
|
|
275
|
+
}
|
|
276
|
+
var GAL_SQRT_A_NOMINAL = Math.sqrt(296e5);
|
|
277
|
+
var GAL_I_NOMINAL = 56 / 180 * PI;
|
|
278
|
+
var I_REF_03 = 0.3 * PI;
|
|
279
|
+
function decodeGpsGalAlm(view, b, sys) {
|
|
280
|
+
const wnc = view.getUint16(b + 12, true);
|
|
281
|
+
const toa = view.getUint32(b + 20, true);
|
|
282
|
+
if (toa === U4_DNU) return null;
|
|
283
|
+
let prn;
|
|
284
|
+
let weekAlm;
|
|
285
|
+
let i0;
|
|
286
|
+
let sqrtA = view.getFloat32(b + 32, true);
|
|
287
|
+
let health;
|
|
288
|
+
if (sys === "G") {
|
|
289
|
+
prn = view.getUint8(b + 14);
|
|
290
|
+
if (prn < 1 || prn > 32) return null;
|
|
291
|
+
weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 256);
|
|
292
|
+
i0 = I_REF_03 + view.getFloat32(b + 24, true) * PI;
|
|
293
|
+
health = view.getUint8(b + 58);
|
|
294
|
+
} else {
|
|
295
|
+
prn = view.getUint8(b + 57) - 70;
|
|
296
|
+
if (prn < 1 || prn > 36) return null;
|
|
297
|
+
weekAlm = adjustWeek(wnc, view.getUint8(b + 56), 4);
|
|
298
|
+
i0 = GAL_I_NOMINAL + view.getFloat32(b + 24, true) * PI;
|
|
299
|
+
sqrtA += GAL_SQRT_A_NOMINAL;
|
|
300
|
+
health = view.getUint16(b + 58, true);
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
system: sys,
|
|
304
|
+
prn: `${sys}${String(prn).padStart(2, "0")}`,
|
|
305
|
+
weekAlm,
|
|
306
|
+
toaSec: toa,
|
|
307
|
+
sqrtA,
|
|
308
|
+
e: view.getFloat32(b + 16, true),
|
|
309
|
+
i0OrDeltaI: i0,
|
|
310
|
+
omega0: view.getFloat32(b + 36, true) * PI,
|
|
311
|
+
omega: view.getFloat32(b + 40, true) * PI,
|
|
312
|
+
m0: view.getFloat32(b + 44, true) * PI,
|
|
313
|
+
omegaDot: view.getFloat32(b + 28, true) * PI,
|
|
314
|
+
af0: view.getFloat32(b + 52, true),
|
|
315
|
+
af1: view.getFloat32(b + 48, true),
|
|
316
|
+
health
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function decodeBdsAlm(view, b) {
|
|
320
|
+
const prnStr = svidToPrn(view.getUint8(b + 14));
|
|
321
|
+
if (!prnStr || prnStr[0] !== "C") return null;
|
|
322
|
+
const prn = parseInt(prnStr.slice(1), 10);
|
|
323
|
+
const toa = view.getUint32(b + 16, true);
|
|
324
|
+
if (toa === U4_DNU) return null;
|
|
325
|
+
const bdsWeekRef = view.getUint16(b + 12, true) - 1356;
|
|
326
|
+
const geo = prn <= 5 || prn >= 59;
|
|
327
|
+
return {
|
|
328
|
+
system: "C",
|
|
329
|
+
prn: prnStr,
|
|
330
|
+
weekAlm: adjustWeek(bdsWeekRef, view.getUint8(b + 15), 256),
|
|
331
|
+
toaSec: toa,
|
|
332
|
+
sqrtA: view.getFloat32(b + 20, true),
|
|
333
|
+
e: view.getFloat32(b + 24, true),
|
|
334
|
+
i0OrDeltaI: (geo ? 0 : I_REF_03) + view.getFloat32(b + 44, true) * PI,
|
|
335
|
+
omega0: view.getFloat32(b + 36, true) * PI,
|
|
336
|
+
omega: view.getFloat32(b + 28, true) * PI,
|
|
337
|
+
m0: view.getFloat32(b + 32, true) * PI,
|
|
338
|
+
omegaDot: view.getFloat32(b + 40, true) * PI,
|
|
339
|
+
af0: view.getFloat32(b + 48, true),
|
|
340
|
+
af1: view.getFloat32(b + 52, true),
|
|
341
|
+
health: view.getUint16(b + 56, true)
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function decodeGloAlm(view, b) {
|
|
345
|
+
const prn = svidToPrn(view.getUint8(b + 14));
|
|
346
|
+
if (!prn || prn[0] !== "R") return null;
|
|
347
|
+
const wnc = view.getUint16(b + 12, true);
|
|
348
|
+
const toa = view.getUint32(b + 20, true);
|
|
349
|
+
if (toa === U4_DNU) return null;
|
|
350
|
+
return {
|
|
351
|
+
system: "R",
|
|
352
|
+
prn,
|
|
353
|
+
freqNr: view.getUint8(b + 15) - 8,
|
|
354
|
+
weekAlm: adjustWeek(wnc, view.getUint8(b + 52), 256),
|
|
355
|
+
toaSec: toa,
|
|
356
|
+
epsilon: view.getFloat32(b + 16, true),
|
|
357
|
+
deltaI: view.getFloat32(b + 24, true) * PI,
|
|
358
|
+
lambda: view.getFloat32(b + 28, true) * PI,
|
|
359
|
+
tLambda: view.getFloat32(b + 32, true),
|
|
360
|
+
omega: view.getFloat32(b + 36, true) * PI,
|
|
361
|
+
deltaT: view.getFloat32(b + 40, true),
|
|
362
|
+
deltaTDot: view.getFloat32(b + 44, true),
|
|
363
|
+
tau: view.getFloat32(b + 48, true),
|
|
364
|
+
health: view.getUint8(b + 53),
|
|
365
|
+
nDay: view.getUint16(b + 54, true),
|
|
366
|
+
n4: view.getUint8(b + 57)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function parseSbfAlmanac(data) {
|
|
370
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
371
|
+
const almanacs = [];
|
|
372
|
+
const badCrc = scanSbfFrames(data, view, (id, b, len) => {
|
|
373
|
+
let alm = null;
|
|
374
|
+
if (id === 5892 && len >= 60) alm = decodeGpsGalAlm(view, b, "G");
|
|
375
|
+
else if (id === 4003 && len >= 61) alm = decodeGpsGalAlm(view, b, "E");
|
|
376
|
+
else if (id === 4119 && len >= 60) alm = decodeBdsAlm(view, b);
|
|
377
|
+
else if (id === 4005 && len >= 60) alm = decodeGloAlm(view, b);
|
|
378
|
+
if (alm) almanacs.push(alm);
|
|
379
|
+
});
|
|
380
|
+
return { almanacs, badCrc };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/sbf/iono.ts
|
|
384
|
+
var F4_DNU2 = -2e10;
|
|
385
|
+
function parseSbfIonoUtc(data) {
|
|
386
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
387
|
+
const ionoCorrections = {};
|
|
388
|
+
let leapSeconds = null;
|
|
389
|
+
const f4s = (b, n) => {
|
|
390
|
+
const out = [];
|
|
391
|
+
for (let k = 0; k < n; k++) {
|
|
392
|
+
const v = view.getFloat32(b + 4 * k, true);
|
|
393
|
+
if (v === F4_DNU2) return null;
|
|
394
|
+
out.push(v);
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
};
|
|
398
|
+
scanSbfFrames(data, view, (id, b, len) => {
|
|
399
|
+
if ((id === 5893 || id === 4120) && len >= 48) {
|
|
400
|
+
const alpha = f4s(b + 16, 4);
|
|
401
|
+
const beta = f4s(b + 32, 4);
|
|
402
|
+
if (!alpha || !beta) return;
|
|
403
|
+
const sys = id === 5893 ? "GPS" : "BDS";
|
|
404
|
+
ionoCorrections[`${sys}A`] = alpha;
|
|
405
|
+
ionoCorrections[`${sys}B`] = beta;
|
|
406
|
+
} else if (id === 4030 && len >= 28) {
|
|
407
|
+
const ai = f4s(b + 16, 3);
|
|
408
|
+
if (ai) ionoCorrections["GAL"] = ai;
|
|
409
|
+
} else if (id === 5894 && len >= 37) {
|
|
410
|
+
leapSeconds = view.getInt8(b + 33);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
return { ionoCorrections, leapSeconds };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/sbf/rawnav.ts
|
|
417
|
+
var SIGNAL_OF_BLOCK = {
|
|
418
|
+
4018: "L2C",
|
|
419
|
+
4019: "L5"
|
|
420
|
+
};
|
|
421
|
+
function parseSbfCnav(data) {
|
|
422
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
423
|
+
const ephemerides = [];
|
|
424
|
+
const assemblers = {
|
|
425
|
+
L2C: new CnavAssembler(),
|
|
426
|
+
L5: new CnavAssembler()
|
|
427
|
+
};
|
|
428
|
+
let badCrc = 0;
|
|
429
|
+
let messages = 0;
|
|
430
|
+
scanSbfFrames(data, view, (id, b, len) => {
|
|
431
|
+
const signal = SIGNAL_OF_BLOCK[id];
|
|
432
|
+
if (!signal || len < 60) return;
|
|
433
|
+
messages++;
|
|
434
|
+
const msg = new Uint8Array(40);
|
|
435
|
+
for (let k = 0; k < 10; k++) {
|
|
436
|
+
const w = view.getUint32(b + 20 + 4 * k, true);
|
|
437
|
+
msg[4 * k] = w >>> 24;
|
|
438
|
+
msg[4 * k + 1] = w >>> 16 & 255;
|
|
439
|
+
msg[4 * k + 2] = w >>> 8 & 255;
|
|
440
|
+
msg[4 * k + 3] = w & 255;
|
|
441
|
+
}
|
|
442
|
+
if (!cnavCrcOk(msg)) {
|
|
443
|
+
badCrc++;
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const eph = assemblers[signal].push(msg);
|
|
447
|
+
if (eph) ephemerides.push({ ...eph, signal });
|
|
448
|
+
});
|
|
449
|
+
return { ephemerides, badCrc, messages };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/navbits/gal.ts
|
|
453
|
+
var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
|
|
454
|
+
var SEC_PER_WEEK2 = 7 * 86400;
|
|
455
|
+
var HALF_WEEK = 302400;
|
|
456
|
+
var GST_GPS_WEEK_OFFSET = 1024;
|
|
457
|
+
function galInavPageCrcOk(page) {
|
|
458
|
+
if (page.length < 28) return false;
|
|
459
|
+
return crc24q(page, 196) === getBitU(page, 196, 24);
|
|
460
|
+
}
|
|
461
|
+
function galFnavPageCrcOk(page) {
|
|
462
|
+
if (page.length < 30) return false;
|
|
463
|
+
return crc24q(page, 214) === getBitU(page, 214, 24);
|
|
464
|
+
}
|
|
465
|
+
function gpsDate(week, sec) {
|
|
466
|
+
return new Date(GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + sec) * 1e3);
|
|
467
|
+
}
|
|
468
|
+
function weekOfToe(week, tow, toe) {
|
|
469
|
+
if (toe - tow > HALF_WEEK) return week - 1;
|
|
470
|
+
if (toe - tow < -HALF_WEEK) return week + 1;
|
|
471
|
+
return week;
|
|
472
|
+
}
|
|
473
|
+
var two2 = (n) => String(n).padStart(2, "0");
|
|
474
|
+
function decodeGalInavWords(words) {
|
|
475
|
+
if (words.length < 96) return null;
|
|
476
|
+
const b = words;
|
|
477
|
+
let i = 128;
|
|
478
|
+
const type1 = getBitU(b, i, 6);
|
|
479
|
+
i += 6;
|
|
480
|
+
const iodNav1 = getBitU(b, i, 10);
|
|
481
|
+
i += 10;
|
|
482
|
+
const toes = getBitU(b, i, 14) * 60;
|
|
483
|
+
i += 14;
|
|
484
|
+
const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
485
|
+
i += 32;
|
|
486
|
+
const e = getBitU(b, i, 32) * 2 ** -33;
|
|
487
|
+
i += 32;
|
|
488
|
+
const sqrtA = getBitU(b, i, 32) * 2 ** -19;
|
|
489
|
+
i = 128 * 2;
|
|
490
|
+
const type2 = getBitU(b, i, 6);
|
|
491
|
+
i += 6;
|
|
492
|
+
const iodNav2 = getBitU(b, i, 10);
|
|
493
|
+
i += 10;
|
|
494
|
+
const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
495
|
+
i += 32;
|
|
496
|
+
const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
497
|
+
i += 32;
|
|
498
|
+
const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
499
|
+
i += 32;
|
|
500
|
+
const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
|
|
501
|
+
i = 128 * 3;
|
|
502
|
+
const type3 = getBitU(b, i, 6);
|
|
503
|
+
i += 6;
|
|
504
|
+
const iodNav3 = getBitU(b, i, 10);
|
|
505
|
+
i += 10;
|
|
506
|
+
const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
|
|
507
|
+
i += 24;
|
|
508
|
+
const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
|
|
509
|
+
i += 16;
|
|
510
|
+
const cuc = getBitS(b, i, 16) * 2 ** -29;
|
|
511
|
+
i += 16;
|
|
512
|
+
const cus = getBitS(b, i, 16) * 2 ** -29;
|
|
513
|
+
i += 16;
|
|
514
|
+
const crc = getBitS(b, i, 16) * 2 ** -5;
|
|
515
|
+
i += 16;
|
|
516
|
+
const crs = getBitS(b, i, 16) * 2 ** -5;
|
|
517
|
+
i = 128 * 4;
|
|
518
|
+
const type4 = getBitU(b, i, 6);
|
|
519
|
+
i += 6;
|
|
520
|
+
const iodNav4 = getBitU(b, i, 10);
|
|
521
|
+
i += 10;
|
|
522
|
+
const svid = getBitU(b, i, 6);
|
|
523
|
+
i += 6;
|
|
524
|
+
const cic = getBitS(b, i, 16) * 2 ** -29;
|
|
525
|
+
i += 16;
|
|
526
|
+
const cis = getBitS(b, i, 16) * 2 ** -29;
|
|
527
|
+
i += 16;
|
|
528
|
+
const tocs = getBitU(b, i, 14) * 60;
|
|
529
|
+
i += 14;
|
|
530
|
+
const af0 = getBitS(b, i, 31) * 2 ** -34;
|
|
531
|
+
i += 31;
|
|
532
|
+
const af1 = getBitS(b, i, 21) * 2 ** -46;
|
|
533
|
+
i += 21;
|
|
534
|
+
const af2 = getBitS(b, i, 6) * 2 ** -59;
|
|
535
|
+
i = 128 * 5;
|
|
536
|
+
const type5 = getBitU(b, i, 6);
|
|
537
|
+
i += 6 + 11 + 11 + 14 + 5;
|
|
538
|
+
const bgdE5a = getBitS(b, i, 10) * 2 ** -32;
|
|
539
|
+
i += 10;
|
|
540
|
+
i += 10;
|
|
541
|
+
const e5bHs = getBitU(b, i, 2);
|
|
542
|
+
i += 2;
|
|
543
|
+
const e1bHs = getBitU(b, i, 2);
|
|
544
|
+
i += 2;
|
|
545
|
+
const e5bDvs = getBitU(b, i, 1);
|
|
546
|
+
i += 1;
|
|
547
|
+
const e1bDvs = getBitU(b, i, 1);
|
|
548
|
+
i += 1;
|
|
549
|
+
const gstWeek = getBitU(b, i, 12);
|
|
550
|
+
i += 12;
|
|
551
|
+
const tow = getBitU(b, i, 20);
|
|
552
|
+
if (type1 !== 1 || type2 !== 2 || type3 !== 3 || type4 !== 4 || type5 !== 5)
|
|
553
|
+
return null;
|
|
554
|
+
if (iodNav1 !== iodNav2 || iodNav1 !== iodNav3 || iodNav1 !== iodNav4)
|
|
555
|
+
return null;
|
|
556
|
+
if (svid < 1 || svid > 36) return null;
|
|
557
|
+
const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
|
|
558
|
+
const tocDate = gpsDate(week, tocs);
|
|
559
|
+
return {
|
|
560
|
+
system: "E",
|
|
561
|
+
prn: `E${two2(svid)}`,
|
|
562
|
+
toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK2,
|
|
563
|
+
tocDate,
|
|
564
|
+
af0,
|
|
565
|
+
af1,
|
|
566
|
+
af2,
|
|
567
|
+
iode: iodNav1,
|
|
568
|
+
crs,
|
|
569
|
+
deltaN,
|
|
570
|
+
m0,
|
|
571
|
+
cuc,
|
|
572
|
+
e,
|
|
573
|
+
cus,
|
|
574
|
+
sqrtA,
|
|
575
|
+
toe: toes,
|
|
576
|
+
cic,
|
|
577
|
+
omega0,
|
|
578
|
+
cis,
|
|
579
|
+
i0,
|
|
580
|
+
crc,
|
|
581
|
+
omega,
|
|
582
|
+
omegaDot,
|
|
583
|
+
idot,
|
|
584
|
+
week,
|
|
585
|
+
svHealth: e5bHs << 7 | e5bDvs << 6 | e1bHs << 1 | e1bDvs,
|
|
586
|
+
tgd: bgdE5a
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function decodeGalFnavPages(pages) {
|
|
590
|
+
if (pages.length < 124) return null;
|
|
591
|
+
const b = pages;
|
|
592
|
+
let i = 0;
|
|
593
|
+
const type1 = getBitU(b, i, 6);
|
|
594
|
+
i += 6;
|
|
595
|
+
const svid = getBitU(b, i, 6);
|
|
596
|
+
i += 6;
|
|
597
|
+
const iodNav1 = getBitU(b, i, 10);
|
|
598
|
+
i += 10;
|
|
599
|
+
const tocs = getBitU(b, i, 14) * 60;
|
|
600
|
+
i += 14;
|
|
601
|
+
const af0 = getBitS(b, i, 31) * 2 ** -34;
|
|
602
|
+
i += 31;
|
|
603
|
+
const af1 = getBitS(b, i, 21) * 2 ** -46;
|
|
604
|
+
i += 21;
|
|
605
|
+
const af2 = getBitS(b, i, 6) * 2 ** -59;
|
|
606
|
+
i += 6;
|
|
607
|
+
i += 8 + 11 + 11 + 14 + 5;
|
|
608
|
+
const bgdE5a = getBitS(b, i, 10) * 2 ** -32;
|
|
609
|
+
i += 10;
|
|
610
|
+
const e5aHs = getBitU(b, i, 2);
|
|
611
|
+
i += 2;
|
|
612
|
+
const gstWeek = getBitU(b, i, 12);
|
|
613
|
+
i += 12;
|
|
614
|
+
const tow = getBitU(b, i, 20);
|
|
615
|
+
i += 20;
|
|
616
|
+
const e5aDvs = getBitU(b, i, 1);
|
|
617
|
+
i = 31 * 8;
|
|
618
|
+
const type2 = getBitU(b, i, 6);
|
|
619
|
+
i += 6;
|
|
620
|
+
const iodNav2 = getBitU(b, i, 10);
|
|
621
|
+
i += 10;
|
|
622
|
+
const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
623
|
+
i += 32;
|
|
624
|
+
const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
|
|
625
|
+
i += 24;
|
|
626
|
+
const e = getBitU(b, i, 32) * 2 ** -33;
|
|
627
|
+
i += 32;
|
|
628
|
+
const sqrtA = getBitU(b, i, 32) * 2 ** -19;
|
|
629
|
+
i += 32;
|
|
630
|
+
const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
631
|
+
i += 32;
|
|
632
|
+
const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
|
|
633
|
+
i = 62 * 8;
|
|
634
|
+
const type3 = getBitU(b, i, 6);
|
|
635
|
+
i += 6;
|
|
636
|
+
const iodNav3 = getBitU(b, i, 10);
|
|
637
|
+
i += 10;
|
|
638
|
+
const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
639
|
+
i += 32;
|
|
640
|
+
const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
641
|
+
i += 32;
|
|
642
|
+
const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
|
|
643
|
+
i += 16;
|
|
644
|
+
const cuc = getBitS(b, i, 16) * 2 ** -29;
|
|
645
|
+
i += 16;
|
|
646
|
+
const cus = getBitS(b, i, 16) * 2 ** -29;
|
|
647
|
+
i += 16;
|
|
648
|
+
const crc = getBitS(b, i, 16) * 2 ** -5;
|
|
649
|
+
i += 16;
|
|
650
|
+
const crs = getBitS(b, i, 16) * 2 ** -5;
|
|
651
|
+
i += 16;
|
|
652
|
+
const toes = getBitU(b, i, 14) * 60;
|
|
653
|
+
i = 93 * 8;
|
|
654
|
+
const type4 = getBitU(b, i, 6);
|
|
655
|
+
i += 6;
|
|
656
|
+
const iodNav4 = getBitU(b, i, 10);
|
|
657
|
+
i += 10;
|
|
658
|
+
const cic = getBitS(b, i, 16) * 2 ** -29;
|
|
659
|
+
i += 16;
|
|
660
|
+
const cis = getBitS(b, i, 16) * 2 ** -29;
|
|
661
|
+
if (type1 !== 1 || type2 !== 2 || type3 !== 3 || type4 !== 4) return null;
|
|
662
|
+
if (iodNav1 !== iodNav2 || iodNav1 !== iodNav3 || iodNav1 !== iodNav4)
|
|
663
|
+
return null;
|
|
664
|
+
if (svid < 1 || svid > 36) return null;
|
|
665
|
+
const week = weekOfToe(gstWeek, tow, toes) + GST_GPS_WEEK_OFFSET;
|
|
666
|
+
const tocDate = gpsDate(week, tocs);
|
|
667
|
+
return {
|
|
668
|
+
system: "E",
|
|
669
|
+
prn: `E${two2(svid)}`,
|
|
670
|
+
toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK2,
|
|
671
|
+
tocDate,
|
|
672
|
+
af0,
|
|
673
|
+
af1,
|
|
674
|
+
af2,
|
|
675
|
+
iode: iodNav1,
|
|
676
|
+
crs,
|
|
677
|
+
deltaN,
|
|
678
|
+
m0,
|
|
679
|
+
cuc,
|
|
680
|
+
e,
|
|
681
|
+
cus,
|
|
682
|
+
sqrtA,
|
|
683
|
+
toe: toes,
|
|
684
|
+
cic,
|
|
685
|
+
omega0,
|
|
686
|
+
cis,
|
|
687
|
+
i0,
|
|
688
|
+
crc,
|
|
689
|
+
omega,
|
|
690
|
+
omegaDot,
|
|
691
|
+
idot,
|
|
692
|
+
week,
|
|
693
|
+
svHealth: e5aHs << 4 | e5aDvs << 3,
|
|
694
|
+
tgd: bgdE5a
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
var dedupKey = (eph) => `${eph.iode}:${eph.toe}:${eph.tocDate.getTime()}`;
|
|
698
|
+
var GalInavAssembler = class {
|
|
699
|
+
sats = /* @__PURE__ */ new Map();
|
|
700
|
+
/**
|
|
701
|
+
* Push one I/NAV page pair for satellite `prn` (1-36): the 234-bit
|
|
702
|
+
* even+odd concatenation, even part first with its 6 tail bits
|
|
703
|
+
* removed, bit 0 = the even part's Even/Odd bit (≥ 17 bytes).
|
|
704
|
+
* Returns the newly completed ephemeris, or null. Pages that are
|
|
705
|
+
* not an even/odd nominal pair, alert pages and word types outside
|
|
706
|
+
* 1-5 are ignored.
|
|
707
|
+
*/
|
|
708
|
+
push(prn, page) {
|
|
709
|
+
if (prn < 1 || prn > 36 || page.length < 17) return null;
|
|
710
|
+
if (getBitU(page, 0, 1) !== 0 || getBitU(page, 114, 1) !== 1) return null;
|
|
711
|
+
if (getBitU(page, 1, 1) === 1 || getBitU(page, 115, 1) === 1) return null;
|
|
712
|
+
const type = getBitU(page, 2, 6);
|
|
713
|
+
if (type < 1 || type > 5) return null;
|
|
714
|
+
let sat = this.sats.get(prn);
|
|
715
|
+
if (!sat) {
|
|
716
|
+
sat = { words: new Uint8Array(96) };
|
|
717
|
+
this.sats.set(prn, sat);
|
|
718
|
+
}
|
|
719
|
+
for (let k = 0; k < 14; k++)
|
|
720
|
+
sat.words[type * 16 + k] = getBitU(page, 2 + 8 * k, 8);
|
|
721
|
+
sat.words[type * 16 + 14] = getBitU(page, 116, 8);
|
|
722
|
+
sat.words[type * 16 + 15] = getBitU(page, 124, 8);
|
|
723
|
+
if (type !== 5) return null;
|
|
724
|
+
const eph = decodeGalInavWords(sat.words);
|
|
725
|
+
if (!eph || eph.prn !== `E${two2(prn)}`) return null;
|
|
726
|
+
const key = dedupKey(eph);
|
|
727
|
+
if (key === sat.lastKey) return null;
|
|
728
|
+
sat.lastKey = key;
|
|
729
|
+
return eph;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
var GalFnavAssembler = class {
|
|
733
|
+
sats = /* @__PURE__ */ new Map();
|
|
734
|
+
/**
|
|
735
|
+
* Push one F/NAV page for satellite `prn` (1-36): 244 bits with the
|
|
736
|
+
* sync field stripped, bit 0 = first page-type bit (≥ 31 bytes).
|
|
737
|
+
* Returns the newly completed ephemeris, or null. Dummy pages
|
|
738
|
+
* (type 63) and page types outside 1-4 are ignored.
|
|
739
|
+
*/
|
|
740
|
+
push(prn, page) {
|
|
741
|
+
if (prn < 1 || prn > 36 || page.length < 31) return null;
|
|
742
|
+
const type = getBitU(page, 0, 6);
|
|
743
|
+
if (type < 1 || type > 4) return null;
|
|
744
|
+
let sat = this.sats.get(prn);
|
|
745
|
+
if (!sat) {
|
|
746
|
+
sat = { words: new Uint8Array(124) };
|
|
747
|
+
this.sats.set(prn, sat);
|
|
748
|
+
}
|
|
749
|
+
sat.words.set(page.subarray(0, 31), (type - 1) * 31);
|
|
750
|
+
if (type !== 4) return null;
|
|
751
|
+
const eph = decodeGalFnavPages(sat.words);
|
|
752
|
+
if (!eph || eph.prn !== `E${two2(prn)}`) return null;
|
|
753
|
+
const key = dedupKey(eph);
|
|
754
|
+
if (key === sat.lastKey) return null;
|
|
755
|
+
sat.lastKey = key;
|
|
756
|
+
return eph;
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// src/sbf/rawnav-gal.ts
|
|
761
|
+
var INAV_SOURCES = [17, 21, 22];
|
|
762
|
+
var FNAV_SOURCES = [20, 22];
|
|
763
|
+
function parseSbfGalNav(data) {
|
|
764
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
765
|
+
const ephemerides = [];
|
|
766
|
+
const inav = new GalInavAssembler();
|
|
767
|
+
const fnav = new GalFnavAssembler();
|
|
768
|
+
let badCrc = 0;
|
|
769
|
+
let messages = 0;
|
|
770
|
+
scanSbfFrames(data, view, (id, b, len) => {
|
|
771
|
+
if (id !== 4022 && id !== 4023 || len < 52) return;
|
|
772
|
+
messages++;
|
|
773
|
+
const prnStr = svidToPrn(data[b + 14]);
|
|
774
|
+
if (!prnStr || prnStr[0] !== "E") return;
|
|
775
|
+
const prn = parseInt(prnStr.slice(1), 10);
|
|
776
|
+
const source = data[b + 17] & 31;
|
|
777
|
+
const page = new Uint8Array(32);
|
|
778
|
+
for (let k = 0; k < 8; k++) {
|
|
779
|
+
const w = view.getUint32(b + 20 + 4 * k, true);
|
|
780
|
+
page[4 * k] = w >>> 24;
|
|
781
|
+
page[4 * k + 1] = w >>> 16 & 255;
|
|
782
|
+
page[4 * k + 2] = w >>> 8 & 255;
|
|
783
|
+
page[4 * k + 3] = w & 255;
|
|
784
|
+
}
|
|
785
|
+
let eph;
|
|
786
|
+
let src;
|
|
787
|
+
if (id === 4023) {
|
|
788
|
+
src = "inav";
|
|
789
|
+
if (!INAV_SOURCES.includes(source)) return;
|
|
790
|
+
if (getBitU2(page, 0) !== 0 || getBitU2(page, 114) !== 1) return;
|
|
791
|
+
if (!galInavPageCrcOk(page)) {
|
|
792
|
+
badCrc++;
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
eph = inav.push(prn, page);
|
|
796
|
+
} else {
|
|
797
|
+
src = "fnav";
|
|
798
|
+
if (!FNAV_SOURCES.includes(source)) return;
|
|
799
|
+
if (isFnavDummy(page)) return;
|
|
800
|
+
if (!galFnavPageCrcOk(page)) {
|
|
801
|
+
badCrc++;
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
eph = fnav.push(prn, page);
|
|
805
|
+
}
|
|
806
|
+
if (eph) ephemerides.push({ ...eph, source: src });
|
|
807
|
+
});
|
|
808
|
+
return { ephemerides, badCrc, messages };
|
|
809
|
+
}
|
|
810
|
+
function getBitU2(buff, pos) {
|
|
811
|
+
return buff[pos >> 3] >> 7 - (pos & 7) & 1;
|
|
812
|
+
}
|
|
813
|
+
function isFnavDummy(page) {
|
|
814
|
+
return page[0] >> 2 === 63;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/navbits/bds.ts
|
|
818
|
+
var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
|
|
819
|
+
var SEC_PER_WEEK3 = 7 * 86400;
|
|
820
|
+
var MS_PER_WEEK2 = SEC_PER_WEEK3 * 1e3;
|
|
821
|
+
var HALF_WEEK2 = 302400;
|
|
822
|
+
var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK3;
|
|
823
|
+
var BDS_SUBFRAME_BYTES = 38;
|
|
824
|
+
function getBitU22(b, p1, l1, p2, l2) {
|
|
825
|
+
return getBitU(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
|
|
826
|
+
}
|
|
827
|
+
function getBitS2(b, p1, l1, p2, l2) {
|
|
828
|
+
return getBitS(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
|
|
829
|
+
}
|
|
830
|
+
function getBitU3(b, p1, l1, p2, l2, p3, l3) {
|
|
831
|
+
return getBitU(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
|
|
832
|
+
}
|
|
833
|
+
function getBitS3(b, p1, l1, p2, l2, p3, l3) {
|
|
834
|
+
return getBitS(b, p1, l1) * 2 ** (l2 + l3) + getBitU(b, p2, l2) * 2 ** l3 + getBitU(b, p3, l3);
|
|
835
|
+
}
|
|
836
|
+
var mergeS = (hi, lo, n) => hi * 2 ** n + lo;
|
|
837
|
+
function bchOk(cw) {
|
|
838
|
+
for (let i = 14; i >= 4; i--) {
|
|
839
|
+
if (cw & 1 << i) cw ^= 19 << i - 4;
|
|
840
|
+
}
|
|
841
|
+
return cw === 0;
|
|
842
|
+
}
|
|
843
|
+
function bdsSubframeParityOk(subframe) {
|
|
844
|
+
if (subframe.length < BDS_SUBFRAME_BYTES) return false;
|
|
845
|
+
if (!bchOk(getBitU(subframe, 15, 15))) return false;
|
|
846
|
+
for (let w = 1; w < 10; w++) {
|
|
847
|
+
const base = 30 * w;
|
|
848
|
+
const cw1 = getBitU(subframe, base, 11) * 16 + getBitU(subframe, base + 22, 4);
|
|
849
|
+
const cw2 = getBitU(subframe, base + 11, 11) * 16 + getBitU(subframe, base + 26, 4);
|
|
850
|
+
if (!bchOk(cw1) || !bchOk(cw2)) return false;
|
|
851
|
+
}
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
function buildBdsEphemeris(prn, f) {
|
|
855
|
+
let week = f.week;
|
|
856
|
+
if (f.toes < f.sow - HALF_WEEK2) week++;
|
|
857
|
+
else if (f.toes > f.sow + HALF_WEEK2) week--;
|
|
858
|
+
const tocDate = new Date(BDT_EPOCH_MS2 + week * MS_PER_WEEK2 + f.tocSec * 1e3);
|
|
859
|
+
return {
|
|
860
|
+
system: "C",
|
|
861
|
+
prn,
|
|
862
|
+
toc: sowOf2(tocDate.getTime()),
|
|
863
|
+
tocDate,
|
|
864
|
+
af0: f.af0,
|
|
865
|
+
af1: f.af1,
|
|
866
|
+
af2: f.af2,
|
|
867
|
+
// AODE is not in the ephemeris subframes; RTKLIB derives the RINEX
|
|
868
|
+
// IODE/AODE slot from toc per the BDS ICD update schedule.
|
|
869
|
+
iode: Math.floor(f.tocSec / 720) % 240,
|
|
870
|
+
crs: f.crs,
|
|
871
|
+
deltaN: f.deltaN,
|
|
872
|
+
m0: f.m0,
|
|
873
|
+
cuc: f.cuc,
|
|
874
|
+
e: f.e,
|
|
875
|
+
cus: f.cus,
|
|
876
|
+
sqrtA: f.sqrtA,
|
|
877
|
+
toe: f.toes,
|
|
878
|
+
cic: f.cic,
|
|
879
|
+
omega0: f.omega0,
|
|
880
|
+
cis: f.cis,
|
|
881
|
+
i0: f.i0,
|
|
882
|
+
crc: f.crc,
|
|
883
|
+
omega: f.omega,
|
|
884
|
+
omegaDot: f.omegaDot,
|
|
885
|
+
idot: f.idot,
|
|
886
|
+
week,
|
|
887
|
+
// RINEX BDS week field is the BDT week of toe
|
|
888
|
+
svHealth: f.svh,
|
|
889
|
+
// SatH1
|
|
890
|
+
tgd: f.tgd1
|
|
891
|
+
// TGD1 (B1) — RINEX slot
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function decodeBdsD1Frame(subframes, opts = {}) {
|
|
895
|
+
if (subframes.length < 3 * BDS_SUBFRAME_BYTES) return null;
|
|
896
|
+
const b = subframes;
|
|
897
|
+
let i = 8 * 38 * 0;
|
|
898
|
+
const frn1 = getBitU(b, i + 15, 3);
|
|
899
|
+
const sow1 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
900
|
+
const svh = getBitU(b, i + 42, 1);
|
|
901
|
+
const week = getBitU(b, i + 60, 13);
|
|
902
|
+
const tocSec = getBitU22(b, i + 73, 9, i + 90, 8) * 8;
|
|
903
|
+
const tgd1 = getBitS(b, i + 98, 10) * 0.1 * 1e-9;
|
|
904
|
+
const af2 = getBitS(b, i + 214, 11) * 2 ** -66;
|
|
905
|
+
const af0 = getBitS2(b, i + 225, 7, i + 240, 17) * 2 ** -33;
|
|
906
|
+
const af1 = getBitS2(b, i + 257, 5, i + 270, 17) * 2 ** -50;
|
|
907
|
+
i = 8 * 38 * 1;
|
|
908
|
+
const frn2 = getBitU(b, i + 15, 3);
|
|
909
|
+
const sow2 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
910
|
+
const deltaN = getBitS2(b, i + 42, 10, i + 60, 6) * 2 ** -43 * GPS_PI;
|
|
911
|
+
const cuc = getBitS2(b, i + 66, 16, i + 90, 2) * 2 ** -31;
|
|
912
|
+
const m0 = getBitS2(b, i + 92, 20, i + 120, 12) * 2 ** -31 * GPS_PI;
|
|
913
|
+
const e = getBitU22(b, i + 132, 10, i + 150, 22) * 2 ** -33;
|
|
914
|
+
const cus = getBitS(b, i + 180, 18) * 2 ** -31;
|
|
915
|
+
const crc = getBitS2(b, i + 198, 4, i + 210, 14) * 2 ** -6;
|
|
916
|
+
const crs = getBitS2(b, i + 224, 8, i + 240, 10) * 2 ** -6;
|
|
917
|
+
const sqrtA = getBitU22(b, i + 250, 12, i + 270, 20) * 2 ** -19;
|
|
918
|
+
const toe1 = getBitU(b, i + 290, 2);
|
|
919
|
+
i = 8 * 38 * 2;
|
|
920
|
+
const frn3 = getBitU(b, i + 15, 3);
|
|
921
|
+
const sow3 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
922
|
+
const toe2 = getBitU22(b, i + 42, 10, i + 60, 5);
|
|
923
|
+
const i0 = getBitS2(b, i + 65, 17, i + 90, 15) * 2 ** -31 * GPS_PI;
|
|
924
|
+
const cic = getBitS2(b, i + 105, 7, i + 120, 11) * 2 ** -31;
|
|
925
|
+
const omegaDot = getBitS2(b, i + 131, 11, i + 150, 13) * 2 ** -43 * GPS_PI;
|
|
926
|
+
const cis = getBitS2(b, i + 163, 9, i + 180, 9) * 2 ** -31;
|
|
927
|
+
const idot = getBitS2(b, i + 189, 13, i + 210, 1) * 2 ** -43 * GPS_PI;
|
|
928
|
+
const omega0 = getBitS2(b, i + 211, 21, i + 240, 11) * 2 ** -31 * GPS_PI;
|
|
929
|
+
const omega = getBitS2(b, i + 251, 11, i + 270, 21) * 2 ** -31 * GPS_PI;
|
|
930
|
+
const toes = (toe1 * 2 ** 15 + toe2) * 8;
|
|
931
|
+
if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3) return null;
|
|
932
|
+
if (sow2 !== sow1 + 6 || sow3 !== sow2 + 6) return null;
|
|
933
|
+
if (tocSec !== toes) return null;
|
|
934
|
+
return buildBdsEphemeris(opts.prn ?? "C00", {
|
|
935
|
+
week,
|
|
936
|
+
sow: sow1,
|
|
937
|
+
toes,
|
|
938
|
+
tocSec,
|
|
939
|
+
svh,
|
|
940
|
+
tgd1,
|
|
941
|
+
af0,
|
|
942
|
+
af1,
|
|
943
|
+
af2,
|
|
944
|
+
crs,
|
|
945
|
+
deltaN,
|
|
946
|
+
m0,
|
|
947
|
+
cuc,
|
|
948
|
+
e,
|
|
949
|
+
cus,
|
|
950
|
+
sqrtA,
|
|
951
|
+
cic,
|
|
952
|
+
omega0,
|
|
953
|
+
cis,
|
|
954
|
+
i0,
|
|
955
|
+
crc,
|
|
956
|
+
omega,
|
|
957
|
+
omegaDot,
|
|
958
|
+
idot
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
function decodeBdsD2Frame(pages, opts = {}) {
|
|
962
|
+
if (pages.length < 10 * BDS_SUBFRAME_BYTES) return null;
|
|
963
|
+
const b = pages;
|
|
964
|
+
let i = 8 * 38 * 0;
|
|
965
|
+
const pgn1 = getBitU(b, i + 42, 4);
|
|
966
|
+
const sow1 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
967
|
+
const svh = getBitU(b, i + 46, 1);
|
|
968
|
+
const week = getBitU(b, i + 64, 13);
|
|
969
|
+
const tocSec = getBitU22(b, i + 77, 5, i + 90, 12) * 8;
|
|
970
|
+
const tgd1 = getBitS(b, i + 102, 10) * 0.1 * 1e-9;
|
|
971
|
+
i = 8 * 38 * 2;
|
|
972
|
+
const pgn3 = getBitU(b, i + 42, 4);
|
|
973
|
+
const sow3 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
974
|
+
const af0 = getBitS2(b, i + 100, 12, i + 120, 12) * 2 ** -33;
|
|
975
|
+
const af1p3 = getBitS(b, i + 132, 4);
|
|
976
|
+
i = 8 * 38 * 3;
|
|
977
|
+
const pgn4 = getBitU(b, i + 42, 4);
|
|
978
|
+
const sow4 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
979
|
+
const af1p4 = getBitU22(b, i + 46, 6, i + 60, 12);
|
|
980
|
+
const af2 = getBitS2(b, i + 72, 10, i + 90, 1) * 2 ** -66;
|
|
981
|
+
const deltaN = getBitS(b, i + 96, 16) * 2 ** -43 * GPS_PI;
|
|
982
|
+
const cucp4 = getBitS(b, i + 120, 14);
|
|
983
|
+
i = 8 * 38 * 4;
|
|
984
|
+
const pgn5 = getBitU(b, i + 42, 4);
|
|
985
|
+
const sow5 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
986
|
+
const cucp5 = getBitU(b, i + 46, 4);
|
|
987
|
+
const m0 = getBitS3(b, i + 50, 2, i + 60, 22, i + 90, 8) * 2 ** -31 * GPS_PI;
|
|
988
|
+
const cus = getBitS2(b, i + 98, 14, i + 120, 4) * 2 ** -31;
|
|
989
|
+
const ep5 = getBitS(b, i + 124, 10);
|
|
990
|
+
i = 8 * 38 * 5;
|
|
991
|
+
const pgn6 = getBitU(b, i + 42, 4);
|
|
992
|
+
const sow6 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
993
|
+
const ep6 = getBitU22(b, i + 46, 6, i + 60, 16);
|
|
994
|
+
const sqrtA = getBitU3(b, i + 76, 6, i + 90, 22, i + 120, 4) * 2 ** -19;
|
|
995
|
+
const cicp6 = getBitS(b, i + 124, 10);
|
|
996
|
+
i = 8 * 38 * 6;
|
|
997
|
+
const pgn7 = getBitU(b, i + 42, 4);
|
|
998
|
+
const sow7 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
999
|
+
const cicp7 = getBitU22(b, i + 46, 6, i + 60, 2);
|
|
1000
|
+
const cis = getBitS(b, i + 62, 18) * 2 ** -31;
|
|
1001
|
+
const toes = getBitU22(b, i + 80, 2, i + 90, 15) * 8;
|
|
1002
|
+
const i0p7 = getBitS2(b, i + 105, 7, i + 120, 14);
|
|
1003
|
+
i = 8 * 38 * 7;
|
|
1004
|
+
const pgn8 = getBitU(b, i + 42, 4);
|
|
1005
|
+
const sow8 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
1006
|
+
const i0p8 = getBitU22(b, i + 46, 6, i + 60, 5);
|
|
1007
|
+
const crc = getBitS2(b, i + 65, 17, i + 90, 1) * 2 ** -6;
|
|
1008
|
+
const crs = getBitS(b, i + 91, 18) * 2 ** -6;
|
|
1009
|
+
const omegaDotP8 = getBitS2(b, i + 109, 3, i + 120, 16);
|
|
1010
|
+
i = 8 * 38 * 8;
|
|
1011
|
+
const pgn9 = getBitU(b, i + 42, 4);
|
|
1012
|
+
const sow9 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
1013
|
+
const omegaDotP9 = getBitU(b, i + 46, 5);
|
|
1014
|
+
const omega0 = getBitS3(b, i + 51, 1, i + 60, 22, i + 90, 9) * 2 ** -31 * GPS_PI;
|
|
1015
|
+
const omegaP9 = getBitS2(b, i + 99, 13, i + 120, 14);
|
|
1016
|
+
i = 8 * 38 * 9;
|
|
1017
|
+
const pgn10 = getBitU(b, i + 42, 4);
|
|
1018
|
+
const sow10 = getBitU22(b, i + 18, 8, i + 30, 12);
|
|
1019
|
+
const omegaP10 = getBitU(b, i + 46, 5);
|
|
1020
|
+
const idot = getBitS2(b, i + 51, 1, i + 60, 13) * 2 ** -43 * GPS_PI;
|
|
1021
|
+
if (pgn1 !== 1 || pgn3 !== 3 || pgn4 !== 4 || pgn5 !== 5 || pgn6 !== 6 || pgn7 !== 7 || pgn8 !== 8 || pgn9 !== 9 || pgn10 !== 10) {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
if (sow3 !== sow1 + 6 || sow4 !== sow3 + 3 || sow5 !== sow4 + 3 || sow6 !== sow5 + 3 || sow7 !== sow6 + 3 || sow8 !== sow7 + 3 || sow9 !== sow8 + 3 || sow10 !== sow9 + 3) {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
if (tocSec !== toes) return null;
|
|
1028
|
+
return buildBdsEphemeris(opts.prn ?? "C00", {
|
|
1029
|
+
week,
|
|
1030
|
+
sow: sow1,
|
|
1031
|
+
toes,
|
|
1032
|
+
tocSec,
|
|
1033
|
+
svh,
|
|
1034
|
+
tgd1,
|
|
1035
|
+
af0,
|
|
1036
|
+
af1: mergeS(af1p3, af1p4, 18) * 2 ** -50,
|
|
1037
|
+
af2,
|
|
1038
|
+
crs,
|
|
1039
|
+
deltaN,
|
|
1040
|
+
m0,
|
|
1041
|
+
cuc: mergeS(cucp4, cucp5, 4) * 2 ** -31,
|
|
1042
|
+
e: mergeS(ep5, ep6, 22) * 2 ** -33,
|
|
1043
|
+
cus,
|
|
1044
|
+
sqrtA,
|
|
1045
|
+
cic: mergeS(cicp6, cicp7, 8) * 2 ** -31,
|
|
1046
|
+
omega0,
|
|
1047
|
+
cis,
|
|
1048
|
+
i0: mergeS(i0p7, i0p8, 11) * 2 ** -31 * GPS_PI,
|
|
1049
|
+
crc,
|
|
1050
|
+
omega: mergeS(omegaP9, omegaP10, 5) * 2 ** -31 * GPS_PI,
|
|
1051
|
+
omegaDot: mergeS(omegaDotP8, omegaDotP9, 5) * 2 ** -43 * GPS_PI,
|
|
1052
|
+
idot
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
function isBdsGeoPrn(prn) {
|
|
1056
|
+
return prn < 6 || prn > 58;
|
|
1057
|
+
}
|
|
1058
|
+
var BdsAssembler = class {
|
|
1059
|
+
sats = /* @__PURE__ */ new Map();
|
|
1060
|
+
/**
|
|
1061
|
+
* Push one 300-bit subframe (38+ bytes, bit 0 = first bit of the
|
|
1062
|
+
* preamble) for the satellite `prn` ("C06"). Returns the newly
|
|
1063
|
+
* completed ephemeris, or null.
|
|
1064
|
+
*/
|
|
1065
|
+
push(prn, subframe) {
|
|
1066
|
+
if (subframe.length < BDS_SUBFRAME_BYTES) return null;
|
|
1067
|
+
const num = parseInt(prn.slice(1), 10);
|
|
1068
|
+
if (!Number.isFinite(num) || num < 1 || num > 63) return null;
|
|
1069
|
+
const id = getBitU(subframe, 15, 3);
|
|
1070
|
+
if (id < 1 || id > 5) return null;
|
|
1071
|
+
let sat = this.sats.get(prn);
|
|
1072
|
+
if (!sat) {
|
|
1073
|
+
sat = { buf: new Uint8Array(10 * BDS_SUBFRAME_BYTES) };
|
|
1074
|
+
this.sats.set(prn, sat);
|
|
1075
|
+
}
|
|
1076
|
+
let eph = null;
|
|
1077
|
+
if (!isBdsGeoPrn(num)) {
|
|
1078
|
+
sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (id - 1) * 38);
|
|
1079
|
+
if (id === 3) eph = decodeBdsD1Frame(sat.buf, { prn });
|
|
1080
|
+
} else {
|
|
1081
|
+
if (id !== 1) return null;
|
|
1082
|
+
const pgn = getBitU(subframe, 42, 4);
|
|
1083
|
+
if (pgn < 1 || pgn > 10) return null;
|
|
1084
|
+
sat.buf.set(subframe.subarray(0, BDS_SUBFRAME_BYTES), (pgn - 1) * 38);
|
|
1085
|
+
if (pgn === 10) eph = decodeBdsD2Frame(sat.buf, { prn });
|
|
1086
|
+
}
|
|
1087
|
+
if (!eph) return null;
|
|
1088
|
+
const key = `${eph.week}:${eph.toe}`;
|
|
1089
|
+
if (key === sat.lastKey) return null;
|
|
1090
|
+
sat.lastKey = key;
|
|
1091
|
+
return eph;
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// src/navbits/glo.ts
|
|
1096
|
+
var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
|
|
1097
|
+
var SEC_PER_WEEK4 = 7 * 86400;
|
|
1098
|
+
var SEC_PER_DAY = 86400;
|
|
1099
|
+
var GLO_STRING_BYTES = 10;
|
|
1100
|
+
function getBitG(b, pos, len) {
|
|
1101
|
+
const value = getBitU(b, pos + 1, len - 1);
|
|
1102
|
+
return getBitU(b, pos, 1) && value !== 0 ? -value : value;
|
|
1103
|
+
}
|
|
1104
|
+
var HAMMING_MASKS = [
|
|
1105
|
+
[85, 85, 90, 170, 170, 170, 181, 85, 106, 216, 8],
|
|
1106
|
+
[102, 102, 108, 204, 204, 204, 217, 153, 179, 104, 16],
|
|
1107
|
+
[135, 135, 143, 15, 15, 15, 30, 30, 60, 112, 32],
|
|
1108
|
+
[7, 248, 15, 240, 15, 240, 31, 224, 63, 128, 64],
|
|
1109
|
+
[248, 0, 15, 255, 240, 0, 31, 255, 192, 0, 128],
|
|
1110
|
+
[0, 0, 15, 255, 255, 255, 224, 0, 0, 1, 0],
|
|
1111
|
+
[255, 255, 240, 0, 0, 0, 0, 0, 0, 2, 0],
|
|
1112
|
+
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 248]
|
|
1113
|
+
];
|
|
1114
|
+
function testGloString(buff) {
|
|
1115
|
+
if (buff.length < 11) return false;
|
|
1116
|
+
let n = 0;
|
|
1117
|
+
let cs = 0;
|
|
1118
|
+
for (const mask of HAMMING_MASKS) {
|
|
1119
|
+
cs = 0;
|
|
1120
|
+
for (let j = 0; j < 11; j++) {
|
|
1121
|
+
let x = buff[j] & mask[j];
|
|
1122
|
+
x ^= x >> 4;
|
|
1123
|
+
x ^= x >> 2;
|
|
1124
|
+
x ^= x >> 1;
|
|
1125
|
+
cs ^= x & 1;
|
|
1126
|
+
}
|
|
1127
|
+
if (cs) n++;
|
|
1128
|
+
}
|
|
1129
|
+
return n === 0 || n === 2 && cs === 1;
|
|
1130
|
+
}
|
|
1131
|
+
function decodeGloStrings(strings, refDate, opts = {}) {
|
|
1132
|
+
if (strings.length < 4 * GLO_STRING_BYTES) return null;
|
|
1133
|
+
const b = strings;
|
|
1134
|
+
let i = 1;
|
|
1135
|
+
const frn1 = getBitU(b, i, 4);
|
|
1136
|
+
i += 4 + 2;
|
|
1137
|
+
i += 2;
|
|
1138
|
+
const tkH = getBitU(b, i, 5);
|
|
1139
|
+
i += 5;
|
|
1140
|
+
const tkM = getBitU(b, i, 6);
|
|
1141
|
+
i += 6;
|
|
1142
|
+
const tkS = getBitU(b, i, 1) * 30;
|
|
1143
|
+
i += 1;
|
|
1144
|
+
const xDot = getBitG(b, i, 24) * 2 ** -20;
|
|
1145
|
+
i += 24;
|
|
1146
|
+
const xAcc = getBitG(b, i, 5) * 2 ** -30;
|
|
1147
|
+
i += 5;
|
|
1148
|
+
const x = getBitG(b, i, 27) * 2 ** -11;
|
|
1149
|
+
i = 80 + 1;
|
|
1150
|
+
const frn2 = getBitU(b, i, 4);
|
|
1151
|
+
i += 4;
|
|
1152
|
+
const bn = getBitU(b, i, 1);
|
|
1153
|
+
i += 1 + 2;
|
|
1154
|
+
i += 1;
|
|
1155
|
+
const tb = getBitU(b, i, 7);
|
|
1156
|
+
i += 7 + 5;
|
|
1157
|
+
const yDot = getBitG(b, i, 24) * 2 ** -20;
|
|
1158
|
+
i += 24;
|
|
1159
|
+
const yAcc = getBitG(b, i, 5) * 2 ** -30;
|
|
1160
|
+
i += 5;
|
|
1161
|
+
const y = getBitG(b, i, 27) * 2 ** -11;
|
|
1162
|
+
i = 160 + 1;
|
|
1163
|
+
const frn3 = getBitU(b, i, 4);
|
|
1164
|
+
i += 4;
|
|
1165
|
+
i += 1;
|
|
1166
|
+
const gammaN = getBitG(b, i, 11) * 2 ** -40;
|
|
1167
|
+
i += 11 + 1;
|
|
1168
|
+
i += 2;
|
|
1169
|
+
i += 1;
|
|
1170
|
+
const zDot = getBitG(b, i, 24) * 2 ** -20;
|
|
1171
|
+
i += 24;
|
|
1172
|
+
const zAcc = getBitG(b, i, 5) * 2 ** -30;
|
|
1173
|
+
i += 5;
|
|
1174
|
+
const z = getBitG(b, i, 27) * 2 ** -11;
|
|
1175
|
+
i = 240 + 1;
|
|
1176
|
+
const frn4 = getBitU(b, i, 4);
|
|
1177
|
+
i += 4;
|
|
1178
|
+
const tauN = getBitG(b, i, 22) * 2 ** -30;
|
|
1179
|
+
i += 22;
|
|
1180
|
+
i += 5;
|
|
1181
|
+
i += 5;
|
|
1182
|
+
i += 14;
|
|
1183
|
+
i += 1;
|
|
1184
|
+
i += 4;
|
|
1185
|
+
i += 3;
|
|
1186
|
+
i += 11;
|
|
1187
|
+
const slot = getBitU(b, i, 5);
|
|
1188
|
+
if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3 || frn4 !== 4) return null;
|
|
1189
|
+
if (slot < 1 || slot > 27) return null;
|
|
1190
|
+
const utcSec = (getUtcDate(refDate).getTime() - GPS_EPOCH_MS3) / 1e3;
|
|
1191
|
+
const week = Math.floor(utcSec / SEC_PER_WEEK4);
|
|
1192
|
+
let tow = utcSec - week * SEC_PER_WEEK4;
|
|
1193
|
+
const tod = tow % SEC_PER_DAY;
|
|
1194
|
+
tow -= tod;
|
|
1195
|
+
let tof = tkH * 3600 + tkM * 60 + tkS - 10800;
|
|
1196
|
+
if (tof < tod - 43200) tof += SEC_PER_DAY;
|
|
1197
|
+
else if (tof > tod + 43200) tof -= SEC_PER_DAY;
|
|
1198
|
+
let toe = tb * 900 - 10800;
|
|
1199
|
+
if (toe < tod - 43200) toe += SEC_PER_DAY;
|
|
1200
|
+
else if (toe > tod + 43200) toe -= SEC_PER_DAY;
|
|
1201
|
+
return {
|
|
1202
|
+
system: "R",
|
|
1203
|
+
prn: `R${String(slot).padStart(2, "0")}`,
|
|
1204
|
+
// RINEX GLONASS epochs are UTC: build the UTC toe Date directly.
|
|
1205
|
+
tocDate: new Date(GPS_EPOCH_MS3 + (week * SEC_PER_WEEK4 + tow + toe) * 1e3),
|
|
1206
|
+
tauN: -tauN,
|
|
1207
|
+
// RINEX stores −τn; the SIS carries τn (ICD sign)
|
|
1208
|
+
gammaN,
|
|
1209
|
+
// v3 message frame time: seconds of the UTC week (RTKLIB tof).
|
|
1210
|
+
messageFrameTime: ((tow + tof) % SEC_PER_WEEK4 + SEC_PER_WEEK4) % SEC_PER_WEEK4,
|
|
1211
|
+
x,
|
|
1212
|
+
xDot,
|
|
1213
|
+
xAcc,
|
|
1214
|
+
y,
|
|
1215
|
+
yDot,
|
|
1216
|
+
yAcc,
|
|
1217
|
+
z,
|
|
1218
|
+
zDot,
|
|
1219
|
+
zAcc,
|
|
1220
|
+
// MSB of the 3-bit Bn word — the unhealthy flag RINEX carries
|
|
1221
|
+
health: bn,
|
|
1222
|
+
freqNum: opts.freqNum ?? 0
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
var GloStringAssembler = class {
|
|
1226
|
+
sats = /* @__PURE__ */ new Map();
|
|
1227
|
+
/**
|
|
1228
|
+
* Push one navigation string (10+ bytes, bit 0 = string bit 85) for
|
|
1229
|
+
* the satellite `prn` ("R09"), received at the GPS-scale `time`.
|
|
1230
|
+
* Returns the newly completed ephemeris, or null. The decoded
|
|
1231
|
+
* string-4 slot number must match `prn`, or nothing is emitted.
|
|
1232
|
+
*/
|
|
1233
|
+
push(prn, str, time, freqNum = 0) {
|
|
1234
|
+
if (str.length < GLO_STRING_BYTES) return null;
|
|
1235
|
+
const m = getBitU(str, 1, 4);
|
|
1236
|
+
if (m < 1) return null;
|
|
1237
|
+
const sec = Math.floor(time.getTime() / 1e3);
|
|
1238
|
+
let sat = this.sats.get(prn);
|
|
1239
|
+
if (!sat) {
|
|
1240
|
+
sat = { buf: new Uint8Array(4 * GLO_STRING_BYTES), batchSec: sec };
|
|
1241
|
+
this.sats.set(prn, sat);
|
|
1242
|
+
} else if (Math.abs(sec - sat.batchSec) > 30) {
|
|
1243
|
+
sat.buf.fill(0);
|
|
1244
|
+
sat.batchSec = sec;
|
|
1245
|
+
}
|
|
1246
|
+
if (m > 4) return null;
|
|
1247
|
+
sat.buf.set(str.subarray(0, GLO_STRING_BYTES), (m - 1) * 10);
|
|
1248
|
+
if (m !== 4) return null;
|
|
1249
|
+
const eph = decodeGloStrings(sat.buf, time, { freqNum });
|
|
1250
|
+
if (!eph || eph.prn !== prn) return null;
|
|
1251
|
+
const key = `${eph.tocDate.getTime()}:${eph.health}`;
|
|
1252
|
+
if (key === sat.lastKey) return null;
|
|
1253
|
+
sat.lastKey = key;
|
|
1254
|
+
return eph;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
// src/sbf/rawnav-bds.ts
|
|
1259
|
+
var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
|
|
1260
|
+
var MS_PER_WEEK3 = 7 * 86400 * 1e3;
|
|
1261
|
+
var TOW_DNU = 4294967295;
|
|
1262
|
+
var WNC_DNU = 65535;
|
|
1263
|
+
function parseSbfBdsNav(data) {
|
|
1264
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1265
|
+
const ephemerides = [];
|
|
1266
|
+
const assembler = new BdsAssembler();
|
|
1267
|
+
let badCrc = 0;
|
|
1268
|
+
let messages = 0;
|
|
1269
|
+
scanSbfFrames(data, view, (id, b, len) => {
|
|
1270
|
+
if (id !== 4047 || len < 60) return;
|
|
1271
|
+
messages++;
|
|
1272
|
+
const prn = svidToPrn(view.getUint8(b + 14));
|
|
1273
|
+
if (!prn || prn[0] !== "C") return;
|
|
1274
|
+
const sf = new Uint8Array(38);
|
|
1275
|
+
for (let k = 0; k < 10; k++) {
|
|
1276
|
+
const w = view.getUint32(b + 20 + 4 * k, true);
|
|
1277
|
+
sf[4 * k] = w >>> 24;
|
|
1278
|
+
if (k < 9) {
|
|
1279
|
+
sf[4 * k + 1] = w >>> 16 & 255;
|
|
1280
|
+
sf[4 * k + 2] = w >>> 8 & 255;
|
|
1281
|
+
sf[4 * k + 3] = w & 255;
|
|
1282
|
+
} else {
|
|
1283
|
+
sf[37] = w >>> 16 & 240;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (!bdsSubframeParityOk(sf)) {
|
|
1287
|
+
badCrc++;
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const eph = assembler.push(prn, sf);
|
|
1291
|
+
if (eph) ephemerides.push(eph);
|
|
1292
|
+
});
|
|
1293
|
+
return { ephemerides, badCrc, messages };
|
|
1294
|
+
}
|
|
1295
|
+
function parseSbfGloNav(data) {
|
|
1296
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1297
|
+
const ephemerides = [];
|
|
1298
|
+
const assembler = new GloStringAssembler();
|
|
1299
|
+
let badCrc = 0;
|
|
1300
|
+
let messages = 0;
|
|
1301
|
+
scanSbfFrames(data, view, (id, b, len) => {
|
|
1302
|
+
if (id !== 4026 || len < 32) return;
|
|
1303
|
+
messages++;
|
|
1304
|
+
const prn = svidToPrn(view.getUint8(b + 14));
|
|
1305
|
+
if (!prn || prn[0] !== "R") return;
|
|
1306
|
+
const towMs = view.getUint32(b + 8, true);
|
|
1307
|
+
const wnc = view.getUint16(b + 12, true);
|
|
1308
|
+
if (towMs === TOW_DNU || wnc === WNC_DNU) return;
|
|
1309
|
+
const str = new Uint8Array(11);
|
|
1310
|
+
for (let k = 0; k < 3; k++) {
|
|
1311
|
+
const w = view.getUint32(b + 20 + 4 * k, true);
|
|
1312
|
+
str[4 * k] = w >>> 24;
|
|
1313
|
+
if (k < 2) {
|
|
1314
|
+
str[4 * k + 1] = w >>> 16 & 255;
|
|
1315
|
+
str[4 * k + 2] = w >>> 8 & 255;
|
|
1316
|
+
str[4 * k + 3] = w & 255;
|
|
1317
|
+
} else {
|
|
1318
|
+
str[9] = w >>> 16 & 255;
|
|
1319
|
+
str[10] = w >>> 8 & 248;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
if (!testGloString(str)) {
|
|
1323
|
+
badCrc++;
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const eph = assembler.push(
|
|
1327
|
+
prn,
|
|
1328
|
+
str,
|
|
1329
|
+
new Date(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK3 + towMs),
|
|
1330
|
+
view.getUint8(b + 18) - 8
|
|
1331
|
+
// FreqNr, offset by 8
|
|
1332
|
+
);
|
|
1333
|
+
if (eph) ephemerides.push(eph);
|
|
1334
|
+
});
|
|
1335
|
+
return { ephemerides, badCrc, messages };
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/navbits/rs255.ts
|
|
1339
|
+
var RS255_N = 255;
|
|
1340
|
+
var RS255_K = 32;
|
|
1341
|
+
var EXP = new Uint8Array(510);
|
|
1342
|
+
var LOG = new Int16Array(256).fill(-1);
|
|
1343
|
+
{
|
|
1344
|
+
let x = 1;
|
|
1345
|
+
for (let i = 0; i < 255; i++) {
|
|
1346
|
+
EXP[i] = x;
|
|
1347
|
+
LOG[x] = i;
|
|
1348
|
+
x <<= 1;
|
|
1349
|
+
if (x & 256) x ^= 285;
|
|
1350
|
+
}
|
|
1351
|
+
for (let i = 255; i < 510; i++) EXP[i] = EXP[i - 255];
|
|
1352
|
+
}
|
|
1353
|
+
var gfMul = (a, b) => a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]];
|
|
1354
|
+
var gfInv = (a) => EXP[255 - LOG[a]];
|
|
1355
|
+
var GEN_POLY = (() => {
|
|
1356
|
+
let g = new Uint8Array([1]);
|
|
1357
|
+
for (let i = 1; i <= 223; i++) {
|
|
1358
|
+
const root = EXP[i];
|
|
1359
|
+
const next = new Uint8Array(g.length + 1);
|
|
1360
|
+
for (let j = 0; j < g.length; j++) {
|
|
1361
|
+
next[j] ^= g[j];
|
|
1362
|
+
next[j + 1] ^= gfMul(g[j], root);
|
|
1363
|
+
}
|
|
1364
|
+
g = next;
|
|
1365
|
+
}
|
|
1366
|
+
return g;
|
|
1367
|
+
})();
|
|
1368
|
+
function rs255Encode(info) {
|
|
1369
|
+
if (info.length > RS255_K)
|
|
1370
|
+
throw new RangeError(`rs255Encode: info length ${info.length} > 32`);
|
|
1371
|
+
const cw = new Uint8Array(RS255_N);
|
|
1372
|
+
cw.set(info, 0);
|
|
1373
|
+
const work = new Uint8Array(RS255_N);
|
|
1374
|
+
work.set(info, 0);
|
|
1375
|
+
for (let i = 0; i < RS255_K; i++) {
|
|
1376
|
+
const coef = work[i];
|
|
1377
|
+
if (coef === 0) continue;
|
|
1378
|
+
for (let j = 1; j < GEN_POLY.length; j++)
|
|
1379
|
+
work[i + j] ^= gfMul(GEN_POLY[j], coef);
|
|
1380
|
+
}
|
|
1381
|
+
cw.set(work.subarray(RS255_K), RS255_K);
|
|
1382
|
+
return cw;
|
|
1383
|
+
}
|
|
1384
|
+
var genMatrix = null;
|
|
1385
|
+
function generatorMatrix() {
|
|
1386
|
+
if (genMatrix) return genMatrix;
|
|
1387
|
+
const rows = [];
|
|
1388
|
+
for (let r = 0; r < RS255_N; r++) rows.push(new Uint8Array(RS255_K));
|
|
1389
|
+
for (let c = 0; c < RS255_K; c++) {
|
|
1390
|
+
const e = new Uint8Array(RS255_K);
|
|
1391
|
+
e[c] = 1;
|
|
1392
|
+
const cw = rs255Encode(e);
|
|
1393
|
+
for (let r = 0; r < RS255_N; r++) rows[r][c] = cw[r];
|
|
1394
|
+
}
|
|
1395
|
+
genMatrix = rows;
|
|
1396
|
+
return rows;
|
|
1397
|
+
}
|
|
1398
|
+
function rs255DecodeErasures(pages, k) {
|
|
1399
|
+
if (k < 1 || k > RS255_K) return null;
|
|
1400
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1401
|
+
const use = [];
|
|
1402
|
+
for (const p of pages) {
|
|
1403
|
+
if (p.pageId < 1 || p.pageId > RS255_N || seen.has(p.pageId)) continue;
|
|
1404
|
+
seen.add(p.pageId);
|
|
1405
|
+
use.push(p);
|
|
1406
|
+
if (use.length === k) break;
|
|
1407
|
+
}
|
|
1408
|
+
if (use.length < k) return null;
|
|
1409
|
+
const width = use[0].octets.length;
|
|
1410
|
+
if (use.some((p) => p.octets.length !== width)) return null;
|
|
1411
|
+
const G = generatorMatrix();
|
|
1412
|
+
const a = use.map((p) => G[p.pageId - 1].slice(0, k));
|
|
1413
|
+
const inv = use.map((_, r) => {
|
|
1414
|
+
const row = new Uint8Array(k);
|
|
1415
|
+
row[r] = 1;
|
|
1416
|
+
return row;
|
|
1417
|
+
});
|
|
1418
|
+
for (let c = 0; c < k; c++) {
|
|
1419
|
+
let pivot = -1;
|
|
1420
|
+
for (let r = c; r < k; r++)
|
|
1421
|
+
if (a[r][c] !== 0) {
|
|
1422
|
+
pivot = r;
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1425
|
+
if (pivot < 0) return null;
|
|
1426
|
+
[a[c], a[pivot]] = [a[pivot], a[c]];
|
|
1427
|
+
[inv[c], inv[pivot]] = [inv[pivot], inv[c]];
|
|
1428
|
+
const s = gfInv(a[c][c]);
|
|
1429
|
+
for (let j = 0; j < k; j++) {
|
|
1430
|
+
a[c][j] = gfMul(a[c][j], s);
|
|
1431
|
+
inv[c][j] = gfMul(inv[c][j], s);
|
|
1432
|
+
}
|
|
1433
|
+
for (let r = 0; r < k; r++) {
|
|
1434
|
+
if (r === c || a[r][c] === 0) continue;
|
|
1435
|
+
const f = a[r][c];
|
|
1436
|
+
for (let j = 0; j < k; j++) {
|
|
1437
|
+
a[r][j] ^= gfMul(a[c][j], f);
|
|
1438
|
+
inv[r][j] ^= gfMul(inv[c][j], f);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
const out = new Uint8Array(k * width);
|
|
1443
|
+
for (let r = 0; r < k; r++)
|
|
1444
|
+
for (let c = 0; c < width; c++) {
|
|
1445
|
+
let v = 0;
|
|
1446
|
+
for (let j = 0; j < k; j++) v ^= gfMul(inv[r][j], use[j].octets[c]);
|
|
1447
|
+
out[r * width + c] = v;
|
|
1448
|
+
}
|
|
1449
|
+
return out;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// src/navbits/has.ts
|
|
1453
|
+
var HAS_DUMMY_HEADER = 11484099;
|
|
1454
|
+
var HAS_VALIDITY_SECONDS = [
|
|
1455
|
+
5,
|
|
1456
|
+
10,
|
|
1457
|
+
15,
|
|
1458
|
+
20,
|
|
1459
|
+
30,
|
|
1460
|
+
60,
|
|
1461
|
+
90,
|
|
1462
|
+
120,
|
|
1463
|
+
180,
|
|
1464
|
+
240,
|
|
1465
|
+
300,
|
|
1466
|
+
600,
|
|
1467
|
+
900,
|
|
1468
|
+
1800,
|
|
1469
|
+
3600,
|
|
1470
|
+
null
|
|
1471
|
+
];
|
|
1472
|
+
var HAS_GPS_SIGNALS = [
|
|
1473
|
+
"L1 C/A",
|
|
1474
|
+
"Reserved",
|
|
1475
|
+
"Reserved",
|
|
1476
|
+
"L1C(D)",
|
|
1477
|
+
"L1C(P)",
|
|
1478
|
+
"L1C(D+P)",
|
|
1479
|
+
"L2 CM",
|
|
1480
|
+
"L2 CL",
|
|
1481
|
+
"L2 CM+CL",
|
|
1482
|
+
"L2 P",
|
|
1483
|
+
"Reserved",
|
|
1484
|
+
"L5 I",
|
|
1485
|
+
"L5 Q",
|
|
1486
|
+
"L5 I+Q",
|
|
1487
|
+
"Reserved",
|
|
1488
|
+
"Reserved"
|
|
1489
|
+
];
|
|
1490
|
+
var HAS_GALILEO_SIGNALS = [
|
|
1491
|
+
"E1-B",
|
|
1492
|
+
"E1-C",
|
|
1493
|
+
"E1-B+E1-C",
|
|
1494
|
+
"E5a-I",
|
|
1495
|
+
"E5a-Q",
|
|
1496
|
+
"E5a-I+E5a-Q",
|
|
1497
|
+
"E5b-I",
|
|
1498
|
+
"E5b-Q",
|
|
1499
|
+
"E5b-I+E5b-Q",
|
|
1500
|
+
"E5-I",
|
|
1501
|
+
"E5-Q",
|
|
1502
|
+
"E5-I+E5-Q",
|
|
1503
|
+
"E6-B",
|
|
1504
|
+
"E6-C",
|
|
1505
|
+
"E6-B+E6-C",
|
|
1506
|
+
"Reserved"
|
|
1507
|
+
];
|
|
1508
|
+
var SYSTEM_OF_GNSS_ID = { 0: "G", 2: "E" };
|
|
1509
|
+
var IOD_BITS = { G: 8, E: 10 };
|
|
1510
|
+
function hasPageCrcOk(page) {
|
|
1511
|
+
if (page.length < 61) return false;
|
|
1512
|
+
return crc24q(page, 462) === getBitU(page, 462, 24);
|
|
1513
|
+
}
|
|
1514
|
+
function parseHasPage(page) {
|
|
1515
|
+
if (getBitU(page, 14, 24) === HAS_DUMMY_HEADER) return null;
|
|
1516
|
+
const body = new Uint8Array(53);
|
|
1517
|
+
for (let i = 0; i < 53; i++) body[i] = getBitU(page, 38 + 8 * i, 8);
|
|
1518
|
+
return {
|
|
1519
|
+
status: getBitU(page, 14, 2),
|
|
1520
|
+
messageType: getBitU(page, 18, 2),
|
|
1521
|
+
messageId: getBitU(page, 20, 5),
|
|
1522
|
+
messageSize: getBitU(page, 25, 5) + 1,
|
|
1523
|
+
pageId: getBitU(page, 30, 8),
|
|
1524
|
+
body
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
var Cursor = class {
|
|
1528
|
+
constructor(buf, bits) {
|
|
1529
|
+
this.buf = buf;
|
|
1530
|
+
this.bits = bits;
|
|
1531
|
+
}
|
|
1532
|
+
pos = 0;
|
|
1533
|
+
u(len) {
|
|
1534
|
+
if (this.pos + len > this.bits) throw new RangeError("truncated");
|
|
1535
|
+
const v = getBitU(this.buf, this.pos, len);
|
|
1536
|
+
this.pos += len;
|
|
1537
|
+
return v;
|
|
1538
|
+
}
|
|
1539
|
+
s(len) {
|
|
1540
|
+
if (this.pos + len > this.bits) throw new RangeError("truncated");
|
|
1541
|
+
const v = getBitS(this.buf, this.pos, len);
|
|
1542
|
+
this.pos += len;
|
|
1543
|
+
return v;
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
var prnOf = (system, idx) => `${system}${String(idx + 1).padStart(2, "0")}`;
|
|
1547
|
+
function parseMaskBlock(c, maskId) {
|
|
1548
|
+
const nSys = c.u(4);
|
|
1549
|
+
const systems = [];
|
|
1550
|
+
for (let s = 0; s < nSys; s++) {
|
|
1551
|
+
const gnssId = c.u(4);
|
|
1552
|
+
const system = SYSTEM_OF_GNSS_ID[gnssId];
|
|
1553
|
+
if (system === void 0) throw new RangeError(`unknown GNSS ID ${gnssId}`);
|
|
1554
|
+
const prns = [];
|
|
1555
|
+
for (let b = 0; b < 40; b++) if (c.u(1)) prns.push(prnOf(system, b));
|
|
1556
|
+
const signalIndices = [];
|
|
1557
|
+
for (let b = 0; b < 16; b++) if (c.u(1)) signalIndices.push(b);
|
|
1558
|
+
const signalNames = system === "G" ? HAS_GPS_SIGNALS : HAS_GALILEO_SIGNALS;
|
|
1559
|
+
let cellMask = null;
|
|
1560
|
+
if (c.u(1)) {
|
|
1561
|
+
cellMask = prns.map(() => signalIndices.map(() => c.u(1) === 1));
|
|
1562
|
+
}
|
|
1563
|
+
systems.push({
|
|
1564
|
+
gnssId,
|
|
1565
|
+
system,
|
|
1566
|
+
prns,
|
|
1567
|
+
signalIndices,
|
|
1568
|
+
signals: signalIndices.map((i) => signalNames[i]),
|
|
1569
|
+
cellMask,
|
|
1570
|
+
navMessage: c.u(3)
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
c.u(6);
|
|
1574
|
+
return { maskId, systems };
|
|
1575
|
+
}
|
|
1576
|
+
function parseOrbitBlock(c, masks) {
|
|
1577
|
+
const validityIndex = c.u(4);
|
|
1578
|
+
const corrections = [];
|
|
1579
|
+
for (const sys of masks.systems) {
|
|
1580
|
+
for (const prn of sys.prns) {
|
|
1581
|
+
const gnssIod = c.u(IOD_BITS[sys.system]);
|
|
1582
|
+
const rad = c.s(13);
|
|
1583
|
+
const inT = c.s(12);
|
|
1584
|
+
const cross = c.s(12);
|
|
1585
|
+
corrections.push({
|
|
1586
|
+
system: sys.system,
|
|
1587
|
+
prn,
|
|
1588
|
+
gnssIod,
|
|
1589
|
+
deltaRadial: rad === -4096 ? null : rad * 25e-4,
|
|
1590
|
+
deltaInTrack: inT === -2048 ? null : inT * 8e-3,
|
|
1591
|
+
deltaCrossTrack: cross === -2048 ? null : cross * 8e-3
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return {
|
|
1596
|
+
validityIndex,
|
|
1597
|
+
validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
|
|
1598
|
+
corrections
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function clockValue(c, system, prn, multiplier) {
|
|
1602
|
+
const raw = c.s(13);
|
|
1603
|
+
return {
|
|
1604
|
+
system,
|
|
1605
|
+
prn,
|
|
1606
|
+
deltaClock: raw === -4096 || raw === 4095 ? null : raw * 25e-4 * multiplier,
|
|
1607
|
+
notUsable: raw === 4095
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function parseClockFullBlock(c, masks) {
|
|
1611
|
+
const validityIndex = c.u(4);
|
|
1612
|
+
const multipliers = masks.systems.map((sys) => ({
|
|
1613
|
+
system: sys.system,
|
|
1614
|
+
multiplier: c.u(2) + 1
|
|
1615
|
+
}));
|
|
1616
|
+
const corrections = [];
|
|
1617
|
+
for (let s = 0; s < masks.systems.length; s++) {
|
|
1618
|
+
const sys = masks.systems[s];
|
|
1619
|
+
for (const prn of sys.prns)
|
|
1620
|
+
corrections.push(
|
|
1621
|
+
clockValue(c, sys.system, prn, multipliers[s].multiplier)
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
validityIndex,
|
|
1626
|
+
validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
|
|
1627
|
+
multipliers,
|
|
1628
|
+
corrections
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
function parseClockSubsetBlock(c, masks) {
|
|
1632
|
+
const validityIndex = c.u(4);
|
|
1633
|
+
const nSys = c.u(4);
|
|
1634
|
+
const multipliers = [];
|
|
1635
|
+
const corrections = [];
|
|
1636
|
+
for (let s = 0; s < nSys; s++) {
|
|
1637
|
+
const gnssId = c.u(4);
|
|
1638
|
+
const sys = masks.systems.find((m) => m.gnssId === gnssId);
|
|
1639
|
+
if (!sys) throw new RangeError(`subset GNSS ID ${gnssId} not in mask`);
|
|
1640
|
+
const multiplier = c.u(2) + 1;
|
|
1641
|
+
multipliers.push({ system: sys.system, multiplier });
|
|
1642
|
+
const selected = [];
|
|
1643
|
+
for (const prn of sys.prns) if (c.u(1)) selected.push(prn);
|
|
1644
|
+
for (const prn of selected)
|
|
1645
|
+
corrections.push(clockValue(c, sys.system, prn, multiplier));
|
|
1646
|
+
}
|
|
1647
|
+
return {
|
|
1648
|
+
validityIndex,
|
|
1649
|
+
validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
|
|
1650
|
+
multipliers,
|
|
1651
|
+
corrections
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
function parseBiasBlock(c, masks, kind) {
|
|
1655
|
+
const validityIndex = c.u(4);
|
|
1656
|
+
const biases = [];
|
|
1657
|
+
for (const sys of masks.systems) {
|
|
1658
|
+
for (let s = 0; s < sys.prns.length; s++) {
|
|
1659
|
+
for (let g = 0; g < sys.signalIndices.length; g++) {
|
|
1660
|
+
if (sys.cellMask && !sys.cellMask[s][g]) continue;
|
|
1661
|
+
const raw = c.s(11);
|
|
1662
|
+
const entry = {
|
|
1663
|
+
system: sys.system,
|
|
1664
|
+
prn: sys.prns[s],
|
|
1665
|
+
signalIndex: sys.signalIndices[g],
|
|
1666
|
+
signal: sys.signals[g],
|
|
1667
|
+
bias: raw === -1024 ? null : raw * (kind === "code" ? 0.02 : 0.01)
|
|
1668
|
+
};
|
|
1669
|
+
if (kind === "phase") entry.discontinuity = c.u(2);
|
|
1670
|
+
biases.push(entry);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
validityIndex,
|
|
1676
|
+
validitySeconds: HAS_VALIDITY_SECONDS[validityIndex],
|
|
1677
|
+
biases
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
function parseHasMt1(payload, header, cachedMasks) {
|
|
1681
|
+
const c = new Cursor(payload, payload.length * 8);
|
|
1682
|
+
const toh = c.u(12);
|
|
1683
|
+
const flags = {
|
|
1684
|
+
mask: c.u(1) === 1,
|
|
1685
|
+
orbit: c.u(1) === 1,
|
|
1686
|
+
clockFullSet: c.u(1) === 1,
|
|
1687
|
+
clockSubset: c.u(1) === 1,
|
|
1688
|
+
codeBias: c.u(1) === 1,
|
|
1689
|
+
phaseBias: c.u(1) === 1
|
|
1690
|
+
};
|
|
1691
|
+
c.u(4);
|
|
1692
|
+
const maskId = c.u(5);
|
|
1693
|
+
const iodSetId = c.u(5);
|
|
1694
|
+
const msg = {
|
|
1695
|
+
...header,
|
|
1696
|
+
toh,
|
|
1697
|
+
maskId,
|
|
1698
|
+
iodSetId,
|
|
1699
|
+
flags,
|
|
1700
|
+
maskFromCache: false,
|
|
1701
|
+
parseError: null
|
|
1702
|
+
};
|
|
1703
|
+
try {
|
|
1704
|
+
let masks;
|
|
1705
|
+
if (flags.mask) {
|
|
1706
|
+
masks = parseMaskBlock(c, maskId);
|
|
1707
|
+
} else if (cachedMasks) {
|
|
1708
|
+
masks = cachedMasks;
|
|
1709
|
+
msg.maskFromCache = true;
|
|
1710
|
+
} else {
|
|
1711
|
+
msg.parseError = "mask-unavailable";
|
|
1712
|
+
return msg;
|
|
1713
|
+
}
|
|
1714
|
+
msg.masks = masks;
|
|
1715
|
+
if (flags.orbit) msg.orbit = parseOrbitBlock(c, masks);
|
|
1716
|
+
if (flags.clockFullSet) msg.clockFullSet = parseClockFullBlock(c, masks);
|
|
1717
|
+
if (flags.clockSubset) msg.clockSubset = parseClockSubsetBlock(c, masks);
|
|
1718
|
+
if (flags.codeBias) msg.codeBias = parseBiasBlock(c, masks, "code");
|
|
1719
|
+
if (flags.phaseBias) msg.phaseBias = parseBiasBlock(c, masks, "phase");
|
|
1720
|
+
} catch (e) {
|
|
1721
|
+
msg.parseError = e instanceof Error ? e.message : String(e);
|
|
1722
|
+
}
|
|
1723
|
+
return msg;
|
|
1724
|
+
}
|
|
1725
|
+
var HasAssembler = class {
|
|
1726
|
+
slots = /* @__PURE__ */ new Map();
|
|
1727
|
+
lastMessageId = -1;
|
|
1728
|
+
maskCache = /* @__PURE__ */ new Map();
|
|
1729
|
+
/** Pages a message may span before its slot is restarted (s). */
|
|
1730
|
+
timeoutSec;
|
|
1731
|
+
stats = {
|
|
1732
|
+
pages: 0,
|
|
1733
|
+
dummyPages: 0,
|
|
1734
|
+
skippedPages: 0,
|
|
1735
|
+
duplicatePages: 0,
|
|
1736
|
+
resets: 0,
|
|
1737
|
+
messages: 0,
|
|
1738
|
+
parseErrors: 0
|
|
1739
|
+
};
|
|
1740
|
+
constructor(opts = {}) {
|
|
1741
|
+
this.timeoutSec = opts.timeoutSec ?? 30;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Push one 492-bit C/NAV page (≥ 61 bytes; check the CRC first with
|
|
1745
|
+
* `hasPageCrcOk`). `tow` — a receiver time of week in seconds, any
|
|
1746
|
+
* origin as long as it is common to all pages — enables the slot
|
|
1747
|
+
* timeout and is stamped on completed messages.
|
|
1748
|
+
*/
|
|
1749
|
+
push(page, tow) {
|
|
1750
|
+
const p = parseHasPage(page);
|
|
1751
|
+
if (!p) {
|
|
1752
|
+
this.stats.dummyPages++;
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
this.stats.pages++;
|
|
1756
|
+
if (p.messageType !== 1 || p.status > 1 || p.pageId === 0) {
|
|
1757
|
+
this.stats.skippedPages++;
|
|
1758
|
+
return null;
|
|
1759
|
+
}
|
|
1760
|
+
if (p.messageId === this.lastMessageId) return null;
|
|
1761
|
+
let slot = this.slots.get(p.messageId);
|
|
1762
|
+
const timedOut = slot?.firstTow !== void 0 && tow !== void 0 && tow - slot.firstTow > this.timeoutSec;
|
|
1763
|
+
if (slot && (slot.messageSize !== p.messageSize || timedOut)) {
|
|
1764
|
+
this.stats.resets++;
|
|
1765
|
+
slot = void 0;
|
|
1766
|
+
}
|
|
1767
|
+
if (!slot) {
|
|
1768
|
+
slot = {
|
|
1769
|
+
messageSize: p.messageSize,
|
|
1770
|
+
status: p.status,
|
|
1771
|
+
pages: [],
|
|
1772
|
+
bodies: /* @__PURE__ */ new Map(),
|
|
1773
|
+
firstTow: tow
|
|
1774
|
+
};
|
|
1775
|
+
this.slots.set(p.messageId, slot);
|
|
1776
|
+
}
|
|
1777
|
+
const bodyKey = Array.from(
|
|
1778
|
+
p.body,
|
|
1779
|
+
(b) => b.toString(16).padStart(2, "0")
|
|
1780
|
+
).join("");
|
|
1781
|
+
const existing = slot.bodies.get(p.pageId);
|
|
1782
|
+
if (existing !== void 0) {
|
|
1783
|
+
if (existing === bodyKey) {
|
|
1784
|
+
this.stats.duplicatePages++;
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
this.stats.resets++;
|
|
1788
|
+
slot.pages = [];
|
|
1789
|
+
slot.bodies.clear();
|
|
1790
|
+
slot.firstTow = tow;
|
|
1791
|
+
}
|
|
1792
|
+
slot.bodies.set(p.pageId, bodyKey);
|
|
1793
|
+
slot.pages.push({ pageId: p.pageId, octets: p.body });
|
|
1794
|
+
if (slot.pages.length < slot.messageSize) return null;
|
|
1795
|
+
const payload = rs255DecodeErasures(slot.pages, slot.messageSize);
|
|
1796
|
+
if (!payload) return null;
|
|
1797
|
+
this.slots.delete(p.messageId);
|
|
1798
|
+
this.lastMessageId = p.messageId;
|
|
1799
|
+
const msg = parseHasMt1(
|
|
1800
|
+
payload,
|
|
1801
|
+
{
|
|
1802
|
+
status: slot.status,
|
|
1803
|
+
messageId: p.messageId,
|
|
1804
|
+
messageSize: slot.messageSize
|
|
1805
|
+
},
|
|
1806
|
+
void 0
|
|
1807
|
+
);
|
|
1808
|
+
const resolved = msg.parseError === "mask-unavailable" && this.maskCache.has(msg.maskId) ? parseHasMt1(
|
|
1809
|
+
payload,
|
|
1810
|
+
{
|
|
1811
|
+
status: slot.status,
|
|
1812
|
+
messageId: p.messageId,
|
|
1813
|
+
messageSize: slot.messageSize
|
|
1814
|
+
},
|
|
1815
|
+
this.maskCache.get(msg.maskId)
|
|
1816
|
+
) : msg;
|
|
1817
|
+
if (resolved.flags.mask && resolved.masks)
|
|
1818
|
+
this.maskCache.set(resolved.maskId, resolved.masks);
|
|
1819
|
+
if (tow !== void 0) resolved.tow = tow;
|
|
1820
|
+
this.stats.messages++;
|
|
1821
|
+
if (resolved.parseError !== null) this.stats.parseErrors++;
|
|
1822
|
+
return resolved;
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
// src/sbf/rawnav-has.ts
|
|
1827
|
+
function parseSbfHas(data) {
|
|
1828
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1829
|
+
const assembler = new HasAssembler();
|
|
1830
|
+
const messages = [];
|
|
1831
|
+
let pagesSeen = 0;
|
|
1832
|
+
let pagesBadCrc = 0;
|
|
1833
|
+
scanSbfFrames(data, view, (id, b, len) => {
|
|
1834
|
+
if (id !== 4024 || len < 84) return;
|
|
1835
|
+
pagesSeen++;
|
|
1836
|
+
const page = new Uint8Array(64);
|
|
1837
|
+
for (let k = 0; k < 16; k++) {
|
|
1838
|
+
const w = view.getUint32(b + 20 + 4 * k, true);
|
|
1839
|
+
page[4 * k] = w >>> 24;
|
|
1840
|
+
page[4 * k + 1] = w >>> 16 & 255;
|
|
1841
|
+
page[4 * k + 2] = w >>> 8 & 255;
|
|
1842
|
+
page[4 * k + 3] = w & 255;
|
|
1843
|
+
}
|
|
1844
|
+
if (!hasPageCrcOk(page)) {
|
|
1845
|
+
pagesBadCrc++;
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
const tow = view.getUint32(b + 8, true) / 1e3;
|
|
1849
|
+
const msg = assembler.push(page, tow);
|
|
1850
|
+
if (msg) messages.push({ ...msg, tow, wnc: view.getUint16(b + 12, true) });
|
|
1851
|
+
});
|
|
1852
|
+
return {
|
|
1853
|
+
messages,
|
|
1854
|
+
pagesSeen,
|
|
1855
|
+
pagesBadCrc,
|
|
1856
|
+
pagesDummy: assembler.stats.dummyPages,
|
|
1857
|
+
pagesHas: assembler.stats.pages
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/sbf/index.ts
|
|
1862
|
+
var CLIGHT = 299792458;
|
|
1863
|
+
var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
|
|
1864
|
+
var MS_PER_WEEK4 = 7 * 864e5;
|
|
1865
|
+
var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
|
|
1866
|
+
var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
|
|
1867
|
+
var M3_INTERVALS = [
|
|
1868
|
+
1,
|
|
1869
|
+
500,
|
|
1870
|
+
1e3,
|
|
1871
|
+
2e3,
|
|
1872
|
+
5e3,
|
|
1873
|
+
1e4,
|
|
1874
|
+
15e3,
|
|
1875
|
+
3e4,
|
|
1876
|
+
6e4,
|
|
1877
|
+
12e4,
|
|
1878
|
+
1,
|
|
1879
|
+
1,
|
|
1880
|
+
1,
|
|
1881
|
+
1,
|
|
1882
|
+
1,
|
|
1883
|
+
1
|
|
1884
|
+
];
|
|
1885
|
+
var M3_LOCK_TIME = [
|
|
1886
|
+
0,
|
|
1887
|
+
6e4,
|
|
1888
|
+
3e4,
|
|
1889
|
+
15e3,
|
|
1890
|
+
1e4,
|
|
1891
|
+
5e3,
|
|
1892
|
+
2e3,
|
|
1893
|
+
1e3,
|
|
1894
|
+
500,
|
|
1895
|
+
200,
|
|
1896
|
+
100,
|
|
1897
|
+
50,
|
|
1898
|
+
40,
|
|
1899
|
+
20,
|
|
1900
|
+
10,
|
|
1901
|
+
0
|
|
1902
|
+
];
|
|
1903
|
+
var M3_SIG_DEFAULT = [
|
|
1904
|
+
["1C", "2L", "5Q", "1W", "2W", "1L"],
|
|
1905
|
+
// GPS
|
|
1906
|
+
["1C", "2C", "1P", "2P", "3Q"],
|
|
1907
|
+
// GLONASS
|
|
1908
|
+
["1C", "5Q", "7Q", "6C", "8Q"],
|
|
1909
|
+
// Galileo
|
|
1910
|
+
["2I", "7I", "6I", "1P", "5P", "7D"],
|
|
1911
|
+
// BeiDou
|
|
1912
|
+
["1C", "5I"],
|
|
1913
|
+
// SBAS
|
|
1914
|
+
["1C", "2L", "5Q", "6L", "1L", "1Z", "5P", "1E"],
|
|
1915
|
+
// QZSS
|
|
1916
|
+
["5A", "1E", "9A"]
|
|
1917
|
+
// NavIC
|
|
1918
|
+
];
|
|
1919
|
+
var MEAS2_SIG = [
|
|
1920
|
+
["G", "1C"],
|
|
1921
|
+
["G", "1W"],
|
|
1922
|
+
["G", "2W"],
|
|
1923
|
+
["G", "2L"],
|
|
1924
|
+
["G", "5Q"],
|
|
1925
|
+
["G", "1L"],
|
|
1926
|
+
["J", "1C"],
|
|
1927
|
+
["J", "2L"],
|
|
1928
|
+
["R", "1C"],
|
|
1929
|
+
["R", "1P"],
|
|
1930
|
+
["R", "2P"],
|
|
1931
|
+
["R", "2C"],
|
|
1932
|
+
["R", "3Q"],
|
|
1933
|
+
["C", "1P"],
|
|
1934
|
+
["C", "5P"],
|
|
1935
|
+
["I", "5A"],
|
|
1936
|
+
null,
|
|
1937
|
+
["E", "1C"],
|
|
1938
|
+
null,
|
|
1939
|
+
["E", "6C"],
|
|
1940
|
+
["E", "5Q"],
|
|
1941
|
+
["E", "7Q"],
|
|
1942
|
+
["E", "8Q"],
|
|
1943
|
+
null,
|
|
1944
|
+
// L-band
|
|
1945
|
+
["S", "1C"],
|
|
1946
|
+
["S", "5I"],
|
|
1947
|
+
["J", "5Q"],
|
|
1948
|
+
["J", "6L"],
|
|
1949
|
+
["C", "2I"],
|
|
1950
|
+
["C", "7I"],
|
|
1951
|
+
["C", "6I"],
|
|
1952
|
+
null,
|
|
1953
|
+
["J", "1L"],
|
|
1954
|
+
["J", "1Z"],
|
|
1955
|
+
["C", "7D"],
|
|
1956
|
+
null,
|
|
1957
|
+
["I", "9A"],
|
|
1958
|
+
null,
|
|
1959
|
+
// NavIC L1 (no obs code assigned)
|
|
1960
|
+
["J", "1E"],
|
|
1961
|
+
["J", "5P"]
|
|
1962
|
+
];
|
|
1963
|
+
var two3 = (n) => String(n).padStart(2, "0");
|
|
1964
|
+
function m3Prn(navsys, svid) {
|
|
1965
|
+
switch (navsys) {
|
|
1966
|
+
case 0:
|
|
1967
|
+
return svid < 32 ? `G${two3(svid + 1)}` : null;
|
|
1968
|
+
case 1:
|
|
1969
|
+
return svid < 27 ? `R${two3(svid + 1)}` : null;
|
|
1970
|
+
case 2:
|
|
1971
|
+
return svid < 36 ? `E${two3(svid + 1)}` : null;
|
|
1972
|
+
case 3:
|
|
1973
|
+
return svid < 50 ? `C${two3(svid + 1)}` : null;
|
|
1974
|
+
case 4:
|
|
1975
|
+
return svid <= 38 ? `S${two3(svid + 20)}` : null;
|
|
1976
|
+
case 5:
|
|
1977
|
+
return svid < 10 ? `J${two3(svid + 1)}` : null;
|
|
1978
|
+
case 6:
|
|
1979
|
+
return svid < 14 ? `I${two3(svid + 1)}` : null;
|
|
1980
|
+
default:
|
|
1981
|
+
return null;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
function carrierFreq(sys, code, fcn) {
|
|
1985
|
+
const band = code.charCodeAt(0) - 48;
|
|
1986
|
+
switch (sys) {
|
|
1987
|
+
case "G":
|
|
1988
|
+
case "J":
|
|
1989
|
+
if (band === 1) return 157542e4;
|
|
1990
|
+
if (band === 2) return 12276e5;
|
|
1991
|
+
if (band === 5) return 117645e4;
|
|
1992
|
+
if (band === 6) return 127875e4;
|
|
1993
|
+
return 0;
|
|
1994
|
+
case "R":
|
|
1995
|
+
if (band === 1) return 1602e6 + fcn * 562500;
|
|
1996
|
+
if (band === 2) return 1246e6 + fcn * 437500;
|
|
1997
|
+
if (band === 3) return 1202025e3;
|
|
1998
|
+
return 0;
|
|
1999
|
+
case "E":
|
|
2000
|
+
if (band === 1) return 157542e4;
|
|
2001
|
+
if (band === 5) return 117645e4;
|
|
2002
|
+
if (band === 6) return 127875e4;
|
|
2003
|
+
if (band === 7) return 120714e4;
|
|
2004
|
+
if (band === 8) return 1191795e3;
|
|
2005
|
+
return 0;
|
|
2006
|
+
case "C":
|
|
2007
|
+
if (band === 1) return 157542e4;
|
|
2008
|
+
if (band === 2) return 1561098e3;
|
|
2009
|
+
if (band === 5) return 117645e4;
|
|
2010
|
+
if (band === 6) return 126852e4;
|
|
2011
|
+
if (band === 7) return 120714e4;
|
|
2012
|
+
if (band === 8) return 1191795e3;
|
|
2013
|
+
return 0;
|
|
2014
|
+
case "S":
|
|
2015
|
+
if (band === 1) return 157542e4;
|
|
2016
|
+
if (band === 5) return 117645e4;
|
|
2017
|
+
return 0;
|
|
2018
|
+
case "I":
|
|
2019
|
+
if (band === 1) return 157542e4;
|
|
2020
|
+
if (band === 5) return 117645e4;
|
|
2021
|
+
if (band === 9) return 2492028e3;
|
|
2022
|
+
return 0;
|
|
2023
|
+
default:
|
|
2024
|
+
return 0;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
var ZERO_REF_SLOT = {
|
|
2028
|
+
sigIdx: 0,
|
|
2029
|
+
pr: 0,
|
|
2030
|
+
cp: 0,
|
|
2031
|
+
cn0: 0,
|
|
2032
|
+
lockTimeMs: null
|
|
2033
|
+
};
|
|
2034
|
+
function freshRefEpoch(tow) {
|
|
2035
|
+
return {
|
|
2036
|
+
tow,
|
|
2037
|
+
headers: new Array(7).fill(null),
|
|
2038
|
+
sats: Array.from({ length: 7 }, () => new Array(64))
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
function parseSbfMeas(data) {
|
|
2042
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
2043
|
+
const epochs = [];
|
|
2044
|
+
const messageCounts = {};
|
|
2045
|
+
const obsCodes = {};
|
|
2046
|
+
let ref = freshRefEpoch(-1);
|
|
2047
|
+
let curTime = NaN;
|
|
2048
|
+
let curMeas = [];
|
|
2049
|
+
let curSats = [];
|
|
2050
|
+
const flush = () => {
|
|
2051
|
+
if (curMeas.length > 0) {
|
|
2052
|
+
const meas = curMeas.map((m) => {
|
|
2053
|
+
const codes = obsCodes[m.sys] ??= [];
|
|
2054
|
+
if (!codes.includes(m.code)) codes.push(m.code);
|
|
2055
|
+
return {
|
|
2056
|
+
prn: m.prn,
|
|
2057
|
+
code: m.code,
|
|
2058
|
+
pr: m.pr !== 0 ? m.pr : null,
|
|
2059
|
+
cp: m.cp !== 0 ? m.cp : null,
|
|
2060
|
+
doppler: m.doppler,
|
|
2061
|
+
cn0: m.cn0,
|
|
2062
|
+
lockTimeMs: m.lockTimeMs
|
|
2063
|
+
};
|
|
2064
|
+
});
|
|
2065
|
+
epochs.push({ timeMs: curTime, meas });
|
|
2066
|
+
}
|
|
2067
|
+
curMeas = [];
|
|
2068
|
+
curSats = [];
|
|
2069
|
+
};
|
|
2070
|
+
const ensureEpoch = (timeMs) => {
|
|
2071
|
+
if (timeMs !== curTime) {
|
|
2072
|
+
flush();
|
|
2073
|
+
curTime = timeMs;
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
const decodeMeas3Ranges = (i, tow) => {
|
|
2077
|
+
const o = i + 8;
|
|
2078
|
+
const constellations = view.getUint16(o + 8, true) & 127;
|
|
2079
|
+
const misc = data[o + 10];
|
|
2080
|
+
const version = data[o + 11];
|
|
2081
|
+
if (version > 31) return;
|
|
2082
|
+
if ((misc & 7) !== 0) return;
|
|
2083
|
+
const prrAvailable = (misc & 8) !== 0;
|
|
2084
|
+
const interval = M3_INTERVALS[misc >> 4];
|
|
2085
|
+
const isRef = tow % interval === 0;
|
|
2086
|
+
const dtow = tow % interval;
|
|
2087
|
+
if (isRef) {
|
|
2088
|
+
ref = freshRefEpoch(tow);
|
|
2089
|
+
} else if (ref.tow !== Math.floor(tow / interval) * interval) {
|
|
2090
|
+
curMeas = [];
|
|
2091
|
+
curSats = [];
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
let idx = o + 12;
|
|
2095
|
+
for (let navsys = 0; navsys < 7; navsys++) {
|
|
2096
|
+
if ((constellations & 1 << navsys) === 0) continue;
|
|
2097
|
+
const sys = M3_SYS[navsys];
|
|
2098
|
+
const useRefHeader = data[idx] === 0;
|
|
2099
|
+
const hdr = useRefHeader ? ref.headers[navsys] ?? new Uint8Array(0) : data.subarray(idx, idx + 32);
|
|
2100
|
+
const BF1 = hdr[0] ?? 0;
|
|
2101
|
+
let nB = BF1 & 7;
|
|
2102
|
+
if (nB === 7) nB = 8;
|
|
2103
|
+
const sigIdxMasterShort = BF1 >> 3 & 15;
|
|
2104
|
+
const sigExclPresent = BF1 >> 7 !== 0;
|
|
2105
|
+
let h = 1;
|
|
2106
|
+
const maskBytes = hdr.subarray(h, h + nB);
|
|
2107
|
+
let nSats = 0;
|
|
2108
|
+
for (const b of maskBytes)
|
|
2109
|
+
for (let k = 0; k < 8; k++) nSats += b >> k & 1;
|
|
2110
|
+
h += nB;
|
|
2111
|
+
let gloFncs = new Uint8Array(0);
|
|
2112
|
+
let bdsLongRange = 0;
|
|
2113
|
+
if (navsys === 1) {
|
|
2114
|
+
gloFncs = hdr.subarray(h, h + (nSats + 1 >> 1));
|
|
2115
|
+
h += nSats + 1 >> 1;
|
|
2116
|
+
} else if (navsys === 3) {
|
|
2117
|
+
bdsLongRange = (hdr[h] ?? 0) | (hdr[h + 1] ?? 0) << 8;
|
|
2118
|
+
h += 2;
|
|
2119
|
+
}
|
|
2120
|
+
const sigExcluded = sigExclPresent ? hdr[h++] ?? 0 : 0;
|
|
2121
|
+
if (isRef) ref.headers[navsys] = data.slice(idx, idx + h);
|
|
2122
|
+
idx += useRefHeader ? 1 : h;
|
|
2123
|
+
const sigTable = [];
|
|
2124
|
+
for (let s = 0; s < 16; s++)
|
|
2125
|
+
if ((sigExcluded & 1 << s) === 0)
|
|
2126
|
+
sigTable.push(M3_SIG_DEFAULT[navsys][s] ?? null);
|
|
2127
|
+
while (sigTable.length < 16) sigTable.push(null);
|
|
2128
|
+
let satCnt = 0;
|
|
2129
|
+
for (let svid = 0; svid < 64 && satCnt < nSats; svid++) {
|
|
2130
|
+
if (((maskBytes[svid >> 3] ?? 0) >> (svid & 7) & 1) === 0) continue;
|
|
2131
|
+
const glofnc = navsys === 1 ? ((gloFncs[satCnt >> 1] ?? 0) >> 4 * (satCnt % 2) & 15) - 8 : 0;
|
|
2132
|
+
const prn = m3Prn(navsys, svid);
|
|
2133
|
+
const prbase = navsys === 3 && (bdsLongRange & 1 << satCnt) !== 0 ? 34e6 : M3_PR_BASE[navsys];
|
|
2134
|
+
const refSat = ref.sats[navsys][svid];
|
|
2135
|
+
const refSlot = (k) => refSat?.slots[k] ?? ZERO_REF_SLOT;
|
|
2136
|
+
const entry = { navsys, svid, prn, slots: [] };
|
|
2137
|
+
const bt = data[idx];
|
|
2138
|
+
let masterSigIdx;
|
|
2139
|
+
let slaveMask;
|
|
2140
|
+
let prRate = 0;
|
|
2141
|
+
let mPr = 0;
|
|
2142
|
+
let mCp = 0;
|
|
2143
|
+
let mCn0 = 0;
|
|
2144
|
+
let mLock = null;
|
|
2145
|
+
let masterCode;
|
|
2146
|
+
let mFreq = 0;
|
|
2147
|
+
if ((bt & 1) === 1) {
|
|
2148
|
+
const BF1m = view.getUint32(idx, true);
|
|
2149
|
+
const prLsb = view.getUint32(idx + 4, true);
|
|
2150
|
+
const cmc = BF1m >>> 1 & 262143;
|
|
2151
|
+
const prMsb = BF1m >>> 19 & 1;
|
|
2152
|
+
const lti3 = BF1m >>> 20 & 7;
|
|
2153
|
+
const cn0 = BF1m >>> 23 & 31;
|
|
2154
|
+
const signalList = BF1m >>> 28 & 15;
|
|
2155
|
+
masterSigIdx = sigIdxMasterShort;
|
|
2156
|
+
slaveMask = signalList << masterSigIdx + 1;
|
|
2157
|
+
masterCode = sigTable[masterSigIdx] ?? null;
|
|
2158
|
+
if (masterCode) {
|
|
2159
|
+
mFreq = carrierFreq(sys, masterCode, glofnc);
|
|
2160
|
+
mPr = prbase + (prLsb + 4294967296 * prMsb) * 1e-3;
|
|
2161
|
+
mCn0 = cn0 + 24;
|
|
2162
|
+
if (cmc !== 0) mCp = mPr / (CLIGHT / mFreq) - 131.072 + cmc * 1e-3;
|
|
2163
|
+
mLock = M3_LOCK_TIME[lti3];
|
|
2164
|
+
}
|
|
2165
|
+
prRate = prrAvailable ? view.getInt16(idx + 8, true) : 0;
|
|
2166
|
+
idx += prrAvailable ? 10 : 8;
|
|
2167
|
+
} else if ((bt & 3) === 0) {
|
|
2168
|
+
const BF1m = view.getUint32(idx, true);
|
|
2169
|
+
const prLsb = view.getUint32(idx + 4, true);
|
|
2170
|
+
const BF2 = view.getUint16(idx + 8, true);
|
|
2171
|
+
const BF3 = data[idx + 10];
|
|
2172
|
+
const prMsb = BF1m >>> 2 & 15;
|
|
2173
|
+
const cmc = BF1m >>> 6 & 4194303;
|
|
2174
|
+
const lti4 = BF1m >>> 28 & 15;
|
|
2175
|
+
const cn0 = BF2 & 63;
|
|
2176
|
+
let signalMask = BF2 >>> 6 & 511;
|
|
2177
|
+
const cont = BF2 >>> 15 & 1;
|
|
2178
|
+
if (cont !== 0) signalMask |= (BF3 & 127) << 9;
|
|
2179
|
+
masterSigIdx = 0;
|
|
2180
|
+
while (masterSigIdx < 32 && (signalMask >> masterSigIdx & 1) === 0)
|
|
2181
|
+
masterSigIdx++;
|
|
2182
|
+
slaveMask = signalMask !== 0 ? signalMask ^ 1 << masterSigIdx : 0;
|
|
2183
|
+
masterCode = sigTable[masterSigIdx] ?? null;
|
|
2184
|
+
if (masterCode) {
|
|
2185
|
+
mFreq = carrierFreq(sys, masterCode, glofnc);
|
|
2186
|
+
const isGpsPCode = navsys === 0 && (masterCode === "1W" || masterCode === "2W");
|
|
2187
|
+
mPr = (prLsb + 4294967296 * prMsb) * 1e-3;
|
|
2188
|
+
mCn0 = isGpsPCode ? cn0 : cn0 + 10;
|
|
2189
|
+
if (cmc !== 0)
|
|
2190
|
+
mCp = mPr / (CLIGHT / mFreq) - 2097.152 + cmc * 1e-3;
|
|
2191
|
+
mLock = M3_LOCK_TIME[lti4];
|
|
2192
|
+
}
|
|
2193
|
+
prRate = prrAvailable ? view.getInt16(idx + 10, true) : 0;
|
|
2194
|
+
idx += (prrAvailable ? 12 : 10) + cont;
|
|
2195
|
+
} else if ((bt & 12) === 12) {
|
|
2196
|
+
const BF1m = data[idx];
|
|
2197
|
+
const BF2 = view.getUint32(idx + 1, true);
|
|
2198
|
+
const prDelta = BF1m >> 4 << 13 | BF2 & 8191;
|
|
2199
|
+
const cn0Delta = BF2 >>> 13 & 7;
|
|
2200
|
+
const cmc = BF2 >>> 16;
|
|
2201
|
+
masterSigIdx = refSlot(0).sigIdx;
|
|
2202
|
+
slaveMask = refSat?.slaveMask ?? 0;
|
|
2203
|
+
masterCode = sigTable[masterSigIdx] ?? null;
|
|
2204
|
+
if (masterCode) {
|
|
2205
|
+
const r = refSlot(0);
|
|
2206
|
+
mFreq = carrierFreq(sys, masterCode, glofnc);
|
|
2207
|
+
mPr = r.pr + Math.trunc((refSat?.prRate ?? 0) * 64 * dtow / 1e3) * 1e-3 + prDelta * 1e-3 - 65.536;
|
|
2208
|
+
mCn0 = r.cn0 - 4 + cn0Delta;
|
|
2209
|
+
if (cmc !== 0)
|
|
2210
|
+
mCp = (mPr - r.pr) / (CLIGHT / mFreq) + r.cp - 32.768 + cmc * 1e-3;
|
|
2211
|
+
mLock = r.lockTimeMs;
|
|
2212
|
+
}
|
|
2213
|
+
idx += 5;
|
|
2214
|
+
} else {
|
|
2215
|
+
const BF1m = view.getUint32(idx, true);
|
|
2216
|
+
const prDelta = BF1m >>> 4 & 16383;
|
|
2217
|
+
const cmc = BF1m >>> 18 & 16383;
|
|
2218
|
+
const cn0Delta = BF1m >>> 2 & 3;
|
|
2219
|
+
masterSigIdx = refSlot(0).sigIdx;
|
|
2220
|
+
slaveMask = refSat?.slaveMask ?? 0;
|
|
2221
|
+
masterCode = sigTable[masterSigIdx] ?? null;
|
|
2222
|
+
if (masterCode) {
|
|
2223
|
+
const r = refSlot(0);
|
|
2224
|
+
mFreq = carrierFreq(sys, masterCode, glofnc);
|
|
2225
|
+
mPr = r.pr + Math.trunc((refSat?.prRate ?? 0) * 64 * dtow / 1e3) * 1e-3 + prDelta * 1e-3 - 8.192;
|
|
2226
|
+
mCn0 = r.cn0 - 1 + cn0Delta;
|
|
2227
|
+
if (cmc !== 0)
|
|
2228
|
+
mCp = (mPr - r.pr) / (CLIGHT / mFreq) + r.cp - 8.192 + cmc * 1e-3;
|
|
2229
|
+
mLock = r.lockTimeMs;
|
|
2230
|
+
}
|
|
2231
|
+
idx += 4;
|
|
2232
|
+
}
|
|
2233
|
+
let masterMeas = null;
|
|
2234
|
+
if (masterCode && prn) {
|
|
2235
|
+
masterMeas = {
|
|
2236
|
+
prn,
|
|
2237
|
+
sys,
|
|
2238
|
+
code: masterCode,
|
|
2239
|
+
freq: mFreq,
|
|
2240
|
+
pr: mPr,
|
|
2241
|
+
cp: mCp,
|
|
2242
|
+
cn0: mCn0,
|
|
2243
|
+
doppler: null,
|
|
2244
|
+
lockTimeMs: mLock
|
|
2245
|
+
};
|
|
2246
|
+
curMeas.push(masterMeas);
|
|
2247
|
+
}
|
|
2248
|
+
entry.slots.push(masterMeas);
|
|
2249
|
+
let newRefSat = null;
|
|
2250
|
+
if (isRef && prn) {
|
|
2251
|
+
newRefSat = {
|
|
2252
|
+
slaveMask,
|
|
2253
|
+
prRate,
|
|
2254
|
+
slots: [
|
|
2255
|
+
{
|
|
2256
|
+
sigIdx: masterSigIdx,
|
|
2257
|
+
pr: mPr,
|
|
2258
|
+
cp: mCp,
|
|
2259
|
+
cn0: mCn0,
|
|
2260
|
+
lockTimeMs: mLock
|
|
2261
|
+
}
|
|
2262
|
+
]
|
|
2263
|
+
};
|
|
2264
|
+
ref.sats[navsys][svid] = newRefSat;
|
|
2265
|
+
}
|
|
2266
|
+
let slaveCnt = 0;
|
|
2267
|
+
let mask = slaveMask;
|
|
2268
|
+
for (let s = 1; s < 16 && mask !== 0; s++) {
|
|
2269
|
+
if ((mask & 1 << s) === 0) continue;
|
|
2270
|
+
const bt2 = data[idx];
|
|
2271
|
+
const code = sigTable[s] ?? null;
|
|
2272
|
+
const freq = code ? carrierFreq(sys, code, glofnc) : 0;
|
|
2273
|
+
let sPr = 0;
|
|
2274
|
+
let sCp = 0;
|
|
2275
|
+
let sCn0 = 0;
|
|
2276
|
+
let sLock = null;
|
|
2277
|
+
if ((bt2 & 1) === 1) {
|
|
2278
|
+
const BF1s = view.getUint32(idx, true);
|
|
2279
|
+
const BF2s = data[idx + 4];
|
|
2280
|
+
const cmcRes = BF1s >>> 1 & 65535;
|
|
2281
|
+
const prRel = BF1s >>> 17;
|
|
2282
|
+
const lti3 = BF2s & 7;
|
|
2283
|
+
const cn0 = BF2s >> 3;
|
|
2284
|
+
if (code) {
|
|
2285
|
+
sPr = mFreq > freq ? mPr + prRel * 1e-3 - 10 : mPr - prRel * 1e-3 + 10;
|
|
2286
|
+
if (cmcRes !== 0)
|
|
2287
|
+
sCp = sPr / (CLIGHT / freq) + (mCp - mPr / (CLIGHT / mFreq)) * (mFreq / freq) - 32.768 + cmcRes * 1e-3;
|
|
2288
|
+
sCn0 = navsys === 0 && (code === "1W" || code === "2W") ? mCn0 - 3 - cn0 : cn0 + 24;
|
|
2289
|
+
sLock = M3_LOCK_TIME[lti3];
|
|
2290
|
+
}
|
|
2291
|
+
idx += 5;
|
|
2292
|
+
} else if ((bt2 & 3) === 0) {
|
|
2293
|
+
const BF1s = view.getUint32(idx, true);
|
|
2294
|
+
const prLsbRel = view.getUint16(idx + 4, true);
|
|
2295
|
+
const BF3s = data[idx + 6];
|
|
2296
|
+
const cmc = BF1s >>> 2 & 4194303;
|
|
2297
|
+
const lti4 = BF1s >>> 24 & 15;
|
|
2298
|
+
const prMsbRel = BF1s >>> 28 & 7;
|
|
2299
|
+
const cn0 = BF3s & 63;
|
|
2300
|
+
if (code) {
|
|
2301
|
+
sPr = mPr + (prMsbRel * 65536 + prLsbRel) * 1e-3 - 262.144;
|
|
2302
|
+
if (cmc !== 0)
|
|
2303
|
+
sCp = sPr / (CLIGHT / freq) - 2097.152 + cmc * 1e-3;
|
|
2304
|
+
sCn0 = navsys === 0 && (code === "1W" || code === "2W") ? cn0 : cn0 + 10;
|
|
2305
|
+
sLock = M3_LOCK_TIME[lti4];
|
|
2306
|
+
}
|
|
2307
|
+
idx += 7;
|
|
2308
|
+
} else {
|
|
2309
|
+
const BF1s = view.getUint16(idx, true);
|
|
2310
|
+
const dC = data[idx + 2];
|
|
2311
|
+
const dPr = BF1s >>> 2 & 4095;
|
|
2312
|
+
const cn0Delta = BF1s >>> 14;
|
|
2313
|
+
if (code) {
|
|
2314
|
+
const rS = refSlot(slaveCnt + 1);
|
|
2315
|
+
const rM = refSlot(0);
|
|
2316
|
+
sCp = rS.cp + (mCp - rM.cp) * (freq / mFreq) - 0.128 + dC * 1e-3;
|
|
2317
|
+
sPr = rS.pr + (sCp - rS.cp) * (CLIGHT / freq) - 2.048 + dPr * 1e-3;
|
|
2318
|
+
sCn0 = rS.cn0 - 2 + cn0Delta;
|
|
2319
|
+
sLock = rS.lockTimeMs;
|
|
2320
|
+
}
|
|
2321
|
+
idx += 3;
|
|
2322
|
+
}
|
|
2323
|
+
let slaveMeas = null;
|
|
2324
|
+
if (code && prn) {
|
|
2325
|
+
slaveMeas = {
|
|
2326
|
+
prn,
|
|
2327
|
+
sys,
|
|
2328
|
+
code,
|
|
2329
|
+
freq,
|
|
2330
|
+
pr: sPr,
|
|
2331
|
+
cp: sCp,
|
|
2332
|
+
cn0: sCn0,
|
|
2333
|
+
doppler: null,
|
|
2334
|
+
lockTimeMs: sLock
|
|
2335
|
+
};
|
|
2336
|
+
curMeas.push(slaveMeas);
|
|
2337
|
+
}
|
|
2338
|
+
entry.slots.push(slaveMeas);
|
|
2339
|
+
if (newRefSat)
|
|
2340
|
+
newRefSat.slots[slaveCnt + 1] = {
|
|
2341
|
+
sigIdx: s,
|
|
2342
|
+
pr: sPr,
|
|
2343
|
+
cp: sCp,
|
|
2344
|
+
cn0: sCn0,
|
|
2345
|
+
lockTimeMs: sLock
|
|
2346
|
+
};
|
|
2347
|
+
slaveCnt++;
|
|
2348
|
+
mask &= ~(1 << s);
|
|
2349
|
+
}
|
|
2350
|
+
curSats.push(entry);
|
|
2351
|
+
satCnt++;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2355
|
+
const decodeMeas3CN0 = (i, len) => {
|
|
2356
|
+
const flags = view.getUint16(i + 14, true);
|
|
2357
|
+
if ((flags & 7) !== 0) return;
|
|
2358
|
+
const maxNibbles = (len - 16) * 2;
|
|
2359
|
+
let off = 0;
|
|
2360
|
+
for (const e of curSats) {
|
|
2361
|
+
if (off >= maxNibbles) break;
|
|
2362
|
+
if (!e.prn) continue;
|
|
2363
|
+
for (let k = 0; k < e.slots.length; k++) {
|
|
2364
|
+
const slot = e.slots[k];
|
|
2365
|
+
if (k > 0 && !slot) break;
|
|
2366
|
+
const nib = (data[i + 16 + (off >> 1)] ?? 0) >> (off & 1) * 4 & 15;
|
|
2367
|
+
if (slot && slot.cn0 !== null) slot.cn0 += nib * 0.0625 - 0.5;
|
|
2368
|
+
off++;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
const readPrRate = (base, off) => {
|
|
2373
|
+
const byteAt = (k) => data[base + off + k] ?? 0;
|
|
2374
|
+
const v = (byteAt(0) | byteAt(1) << 8 | byteAt(2) << 16 | byteAt(3) << 24) >>> 0;
|
|
2375
|
+
let value;
|
|
2376
|
+
let size;
|
|
2377
|
+
if ((v & 2) === 0) {
|
|
2378
|
+
value = (v & 255) >>> 2;
|
|
2379
|
+
size = 1;
|
|
2380
|
+
} else if ((v & 6) === 2) {
|
|
2381
|
+
value = (v & 65535) >>> 3;
|
|
2382
|
+
size = 2;
|
|
2383
|
+
} else if ((v & 14) === 6) {
|
|
2384
|
+
value = (v & 16777215) >>> 4;
|
|
2385
|
+
size = 3;
|
|
2386
|
+
} else {
|
|
2387
|
+
value = v >>> 4;
|
|
2388
|
+
size = 4;
|
|
2389
|
+
}
|
|
2390
|
+
if ((v & 1) === 1) value = -value;
|
|
2391
|
+
return { value, size };
|
|
2392
|
+
};
|
|
2393
|
+
const decodeMeas3Doppler = (i, len) => {
|
|
2394
|
+
const flags = view.getUint16(i + 14, true);
|
|
2395
|
+
if ((flags & 7) !== 0) return;
|
|
2396
|
+
let off = 0;
|
|
2397
|
+
for (const e of curSats) {
|
|
2398
|
+
if (off + 16 >= len) break;
|
|
2399
|
+
if (!e.prn) continue;
|
|
2400
|
+
const master = readPrRate(i + 16, off);
|
|
2401
|
+
off += master.size;
|
|
2402
|
+
if (master.value === -268435455) continue;
|
|
2403
|
+
const refPrRate = ref.sats[e.navsys][e.svid]?.prRate ?? 0;
|
|
2404
|
+
const m = e.slots[0];
|
|
2405
|
+
const mD = m ? -(master.value + refPrRate * 64) * 1e-3 / (CLIGHT / m.freq) : 0;
|
|
2406
|
+
if (m) m.doppler = mD;
|
|
2407
|
+
for (let k = 1; k < e.slots.length; k++) {
|
|
2408
|
+
const slot = e.slots[k];
|
|
2409
|
+
if (!slot) break;
|
|
2410
|
+
const slave = readPrRate(i + 16, off);
|
|
2411
|
+
off += slave.size;
|
|
2412
|
+
if (m)
|
|
2413
|
+
slot.doppler = (mD * (CLIGHT / m.freq) * 1e3 - slave.value) * 1e-3 / (CLIGHT / slot.freq);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
const decodeMeasEpoch = (i, len) => {
|
|
2418
|
+
const n1 = data[i + 14];
|
|
2419
|
+
const len1 = data[i + 15];
|
|
2420
|
+
const len2 = data[i + 16];
|
|
2421
|
+
if ((data[i + 17] & 128) !== 0) return;
|
|
2422
|
+
const end = i + len;
|
|
2423
|
+
let p = i + 20;
|
|
2424
|
+
for (let b1 = 0; b1 < n1 && p + 20 <= end; b1++) {
|
|
2425
|
+
const ant = data[p + 1] >> 5;
|
|
2426
|
+
let sig = data[p + 1] & 31;
|
|
2427
|
+
const svid = data[p + 2];
|
|
2428
|
+
const misc = data[p + 3];
|
|
2429
|
+
const info = data[p + 18];
|
|
2430
|
+
const n2 = data[p + 19];
|
|
2431
|
+
let fcn = 0;
|
|
2432
|
+
if (sig === 31) sig = (info >> 3) + 32;
|
|
2433
|
+
else if (sig >= 8 && sig <= 11) fcn = (info >> 3) - 8;
|
|
2434
|
+
const prn = svidToPrn(svid);
|
|
2435
|
+
const sigDef = MEAS2_SIG[sig] ?? null;
|
|
2436
|
+
if (ant !== 0 || !prn || !sigDef || sigDef[0] !== prn[0]) {
|
|
2437
|
+
p += len1 + len2 * n2;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
const [sys, code] = sigDef;
|
|
2441
|
+
const freq1 = carrierFreq(sys, code, fcn);
|
|
2442
|
+
const codeLsb = view.getUint32(p + 4, true);
|
|
2443
|
+
const dopplerRaw = view.getInt32(p + 8, true);
|
|
2444
|
+
const carrierLsb = view.getUint16(p + 12, true);
|
|
2445
|
+
const carrierMsb = view.getInt8(p + 14);
|
|
2446
|
+
const cn0Raw = data[p + 15];
|
|
2447
|
+
const lock = view.getUint16(p + 16, true);
|
|
2448
|
+
let p1 = 0;
|
|
2449
|
+
if ((misc & 15) !== 0 || codeLsb !== 0)
|
|
2450
|
+
p1 = (misc & 15) * 4294967296e-3 + codeLsb * 1e-3;
|
|
2451
|
+
const d1 = dopplerRaw !== -2147483648 ? dopplerRaw * 1e-4 : null;
|
|
2452
|
+
let l1 = 0;
|
|
2453
|
+
if (p1 !== 0 && freq1 > 0 && lock !== 65535 && (carrierMsb !== -128 || carrierLsb !== 0))
|
|
2454
|
+
l1 = p1 * freq1 / CLIGHT + carrierMsb * 65.536 + carrierLsb * 1e-3;
|
|
2455
|
+
const cn0 = cn0Raw !== 255 ? cn0Raw * 0.25 + (sig === 1 || sig === 2 ? 0 : 10) : null;
|
|
2456
|
+
curMeas.push({
|
|
2457
|
+
prn,
|
|
2458
|
+
sys,
|
|
2459
|
+
code,
|
|
2460
|
+
freq: freq1,
|
|
2461
|
+
pr: p1,
|
|
2462
|
+
cp: l1,
|
|
2463
|
+
cn0,
|
|
2464
|
+
doppler: d1,
|
|
2465
|
+
lockTimeMs: lock !== 65535 ? lock * 1e3 : null
|
|
2466
|
+
});
|
|
2467
|
+
p += len1;
|
|
2468
|
+
for (let b2 = 0; b2 < n2 && p + 12 <= end; b2++, p += len2) {
|
|
2469
|
+
let sig2 = data[p] & 31;
|
|
2470
|
+
const ant2 = data[p] >> 5;
|
|
2471
|
+
const lock2 = data[p + 1];
|
|
2472
|
+
const cn0Raw2 = data[p + 2];
|
|
2473
|
+
const offsetsMsb = data[p + 3];
|
|
2474
|
+
const carrierMsb2 = view.getInt8(p + 4);
|
|
2475
|
+
const info2 = data[p + 5];
|
|
2476
|
+
if (sig2 === 31) sig2 = (info2 >> 3) + 32;
|
|
2477
|
+
const sigDef2 = MEAS2_SIG[sig2] ?? null;
|
|
2478
|
+
if (ant2 !== 0 || !sigDef2 || sigDef2[0] !== prn[0]) continue;
|
|
2479
|
+
const code2 = sigDef2[1];
|
|
2480
|
+
const freq2 = carrierFreq(sys, code2, fcn);
|
|
2481
|
+
const codeOffMsb = (offsetsMsb & 7) << 29 >> 29;
|
|
2482
|
+
const dopplerOffMsb = offsetsMsb >> 3 << 27 >> 27;
|
|
2483
|
+
const codeOffLsb = view.getUint16(p + 6, true);
|
|
2484
|
+
const carrierLsb2 = view.getUint16(p + 8, true);
|
|
2485
|
+
const dopplerOffLsb = view.getUint16(p + 10, true);
|
|
2486
|
+
let p2 = 0;
|
|
2487
|
+
if (p1 !== 0 && (codeOffMsb !== -4 || codeOffLsb !== 0))
|
|
2488
|
+
p2 = p1 + codeOffMsb * 65.536 + codeOffLsb * 1e-3;
|
|
2489
|
+
let l2 = 0;
|
|
2490
|
+
if (p2 !== 0 && freq2 > 0 && (carrierMsb2 !== -128 || carrierLsb2 !== 0))
|
|
2491
|
+
l2 = p2 * freq2 / CLIGHT + carrierMsb2 * 65.536 + carrierLsb2 * 1e-3;
|
|
2492
|
+
let d2 = null;
|
|
2493
|
+
if (d1 !== null && freq1 > 0 && freq2 > 0 && (dopplerOffMsb !== -16 || dopplerOffLsb !== 0))
|
|
2494
|
+
d2 = d1 * freq2 / freq1 + dopplerOffMsb * 6.5536 + dopplerOffLsb * 1e-4;
|
|
2495
|
+
const cn02 = cn0Raw2 !== 255 ? cn0Raw2 * 0.25 + (sig2 === 1 || sig2 === 2 ? 0 : 10) : null;
|
|
2496
|
+
curMeas.push({
|
|
2497
|
+
prn,
|
|
2498
|
+
sys,
|
|
2499
|
+
code: code2,
|
|
2500
|
+
freq: freq2,
|
|
2501
|
+
pr: p2,
|
|
2502
|
+
cp: l2,
|
|
2503
|
+
cn0: cn02,
|
|
2504
|
+
doppler: d2,
|
|
2505
|
+
lockTimeMs: lock2 !== 255 ? lock2 * 1e3 : null
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
};
|
|
2510
|
+
const badCrc = scanSbfFrames(data, view, (id, i, len) => {
|
|
2511
|
+
const key = String(id);
|
|
2512
|
+
messageCounts[key] = (messageCounts[key] ?? 0) + 1;
|
|
2513
|
+
if ((id === 4027 || id === 4109 || id === 4110 || id === 4111) && len >= 16) {
|
|
2514
|
+
const tow = view.getUint32(i + 8, true);
|
|
2515
|
+
const wnc = view.getUint16(i + 12, true);
|
|
2516
|
+
if (tow !== 4294967295 && wnc !== 65535) {
|
|
2517
|
+
ensureEpoch(GPS_EPOCH_MS5 + wnc * MS_PER_WEEK4 + tow);
|
|
2518
|
+
if (id === 4109) decodeMeas3Ranges(i, tow);
|
|
2519
|
+
else if (id === 4110) decodeMeas3CN0(i, len);
|
|
2520
|
+
else if (id === 4111) decodeMeas3Doppler(i, len);
|
|
2521
|
+
else decodeMeasEpoch(i, len);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2525
|
+
flush();
|
|
2526
|
+
return { epochs, messageCounts, obsCodes, badCrc };
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
export {
|
|
2530
|
+
crc16,
|
|
2531
|
+
scanSbfFrames,
|
|
2532
|
+
parseSbfNav,
|
|
2533
|
+
parseSbfAlmanac,
|
|
2534
|
+
parseSbfIonoUtc,
|
|
2535
|
+
parseSbfCnav,
|
|
2536
|
+
parseSbfGalNav,
|
|
2537
|
+
parseSbfBdsNav,
|
|
2538
|
+
parseSbfGloNav,
|
|
2539
|
+
parseSbfHas,
|
|
2540
|
+
parseSbfMeas
|
|
2541
|
+
};
|