gnss-js 1.15.0 → 1.17.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-I4UV45NT.js → chunk-7QR2KPDU.js} +197 -148
- package/dist/chunk-IG5CDNDS.js +245 -0
- package/dist/chunk-REYOYF7O.js +147 -0
- package/dist/{chunk-BEQSEJMU.js → chunk-ZNCQNHNI.js} +36 -0
- package/dist/index.cjs +532 -181
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -5
- package/dist/novatel.cjs +187 -11
- package/dist/novatel.d.cts +36 -13
- package/dist/novatel.d.ts +36 -13
- package/dist/novatel.js +2 -1
- package/dist/sbf.cjs +41 -2
- package/dist/sbf.d.cts +58 -1
- package/dist/sbf.d.ts +58 -1
- package/dist/sbf.js +9 -3
- package/dist/ubx.cjs +297 -29
- package/dist/ubx.d.cts +121 -1
- package/dist/ubx.d.ts +121 -1
- package/dist/ubx.js +10 -3
- package/package.json +1 -1
- package/dist/chunk-5IXK25MX.js +0 -116
package/dist/sbf.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { E as Ephemeris } from './nav-BAI1a9vK.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Septentrio SBF framing shared by the measurement and navigation
|
|
5
|
+
* decoders: 0x24 0x40 sync, CRC16-CCITT (poly 0x1021, init 0) as U2,
|
|
6
|
+
* block ID U2 (number in bits 0..12, revision in 13..15), total length
|
|
7
|
+
* U2 (multiple of 4, includes the 8-byte header). The CRC covers block
|
|
8
|
+
* ID through payload end.
|
|
9
|
+
*/
|
|
10
|
+
declare function crc16(data: Uint8Array, start: number, end: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* Scan a byte stream for valid SBF frames and invoke `onBlock` for each
|
|
13
|
+
* one, passing the block number (revision bits stripped), the frame
|
|
14
|
+
* start offset and the total frame length. Frames with bad CRC are
|
|
15
|
+
* counted and a resync continues at the next byte; the count of bad
|
|
16
|
+
* frames is returned.
|
|
17
|
+
*/
|
|
18
|
+
declare function scanSbfFrames(data: Uint8Array, view: DataView, onBlock: (id: number, offset: number, len: number) => void): number;
|
|
19
|
+
|
|
3
20
|
/**
|
|
4
21
|
* Septentrio SBF decoded navigation and almanac blocks.
|
|
5
22
|
*
|
|
@@ -134,6 +151,46 @@ interface SbfAlmanacResult {
|
|
|
134
151
|
*/
|
|
135
152
|
declare function parseSbfAlmanac(data: Uint8Array): SbfAlmanacResult;
|
|
136
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Septentrio SBF decoded ionosphere / UTC blocks.
|
|
156
|
+
*
|
|
157
|
+
* GPSIon (5893) and BDSIon (4120) carry the eight Klobuchar vertical
|
|
158
|
+
* delay coefficients, GALIon (4030) the three NeQuick-G effective
|
|
159
|
+
* ionisation level coefficients, and GPSUtc (5894) the GPS-UTC
|
|
160
|
+
* parameters from LNAV subframe 4 page 18 (only ΔtLS, the current
|
|
161
|
+
* leap-second count, is extracted here).
|
|
162
|
+
*
|
|
163
|
+
* Units: SBF stores the coefficients as f4 floats already in the
|
|
164
|
+
* semicircle-based SI units of the broadcast messages — alpha_n in
|
|
165
|
+
* s/semicircle^n, beta_n in s/semicircle^n, ai_n in sfu/deg^n — which
|
|
166
|
+
* are exactly the units a RINEX nav header prints. Values are passed
|
|
167
|
+
* through unscaled, so the output matches what `parseNavFile` reads
|
|
168
|
+
* from a converted nav header to float32 precision.
|
|
169
|
+
*
|
|
170
|
+
* Ported from RTKLIB demo5 (rtklibexplorer), src/rcv/septentrio.c
|
|
171
|
+
* (decode_gpsion / decode_galion / decode_cmpion / decode_gpsutc),
|
|
172
|
+
* BSD-2-Clause, and cross-checked against the Septentrio mosaic-X5
|
|
173
|
+
* reference guide (which documents all four block layouts, including
|
|
174
|
+
* the do-not-use markers RTKLIB does not check).
|
|
175
|
+
*/
|
|
176
|
+
interface SbfIonoUtcResult {
|
|
177
|
+
/**
|
|
178
|
+
* Iono coefficient sets keyed like `NavHeader.ionoCorrections`:
|
|
179
|
+
* `GPSA`/`GPSB` (Klobuchar alpha/beta), `GAL` ([ai0, ai1, ai2],
|
|
180
|
+
* RINEX GAL header convention), `BDSA`/`BDSB`. The blocks repeat;
|
|
181
|
+
* the last valid block of each type in the stream wins.
|
|
182
|
+
*/
|
|
183
|
+
ionoCorrections: Record<string, number[]>;
|
|
184
|
+
/** GPS-UTC ΔtLS from the last GPSUtc block, if any. */
|
|
185
|
+
leapSeconds: number | null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Decode every GPSIon/GALIon/BDSIon/GPSUtc block in an SBF byte
|
|
189
|
+
* stream. Blocks with do-not-use coefficients are skipped; other block
|
|
190
|
+
* types are skipped silently.
|
|
191
|
+
*/
|
|
192
|
+
declare function parseSbfIonoUtc(data: Uint8Array): SbfIonoUtcResult;
|
|
193
|
+
|
|
137
194
|
/**
|
|
138
195
|
* Septentrio SBF raw-measurement decoding (MeasEpoch + Meas3) — the
|
|
139
196
|
* path from a receiver log to RINEX-grade observables.
|
|
@@ -197,4 +254,4 @@ interface SbfParseResult {
|
|
|
197
254
|
*/
|
|
198
255
|
declare function parseSbfMeas(data: Uint8Array): SbfParseResult;
|
|
199
256
|
|
|
200
|
-
export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfMeas, parseSbfNav };
|
|
257
|
+
export { type SbfAlmanac, type SbfAlmanacResult, type SbfGlonassAlmanac, type SbfIonoUtcResult, type SbfKeplerAlmanac, type SbfMeasEpoch, type SbfMeasurement, type SbfNavResult, type SbfParseResult, parseSbfAlmanac, parseSbfIonoUtc, parseSbfMeas, parseSbfNav, crc16 as sbfCrc16, scanSbfFrames };
|
package/dist/sbf.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
crc16,
|
|
2
3
|
parseSbfAlmanac,
|
|
4
|
+
parseSbfIonoUtc,
|
|
3
5
|
parseSbfMeas,
|
|
4
|
-
parseSbfNav
|
|
5
|
-
|
|
6
|
+
parseSbfNav,
|
|
7
|
+
scanSbfFrames
|
|
8
|
+
} from "./chunk-ZNCQNHNI.js";
|
|
6
9
|
import "./chunk-HVXYFUCB.js";
|
|
7
10
|
import "./chunk-LEEU5OIO.js";
|
|
8
11
|
export {
|
|
9
12
|
parseSbfAlmanac,
|
|
13
|
+
parseSbfIonoUtc,
|
|
10
14
|
parseSbfMeas,
|
|
11
|
-
parseSbfNav
|
|
15
|
+
parseSbfNav,
|
|
16
|
+
crc16 as sbfCrc16,
|
|
17
|
+
scanSbfFrames
|
|
12
18
|
};
|
package/dist/ubx.cjs
CHANGED
|
@@ -20,10 +20,295 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/ubx/index.ts
|
|
21
21
|
var ubx_exports = {};
|
|
22
22
|
__export(ubx_exports, {
|
|
23
|
-
|
|
23
|
+
parseUbxIonoUtc: () => parseUbxIonoUtc,
|
|
24
|
+
parseUbxNav: () => parseUbxNav,
|
|
25
|
+
parseUbxRawx: () => parseUbxRawx,
|
|
26
|
+
ubxFrames: () => ubxFrames
|
|
24
27
|
});
|
|
25
28
|
module.exports = __toCommonJS(ubx_exports);
|
|
29
|
+
|
|
30
|
+
// src/ubx/frame.ts
|
|
31
|
+
function* ubxFrames(data, stats = { badChecksums: 0 }) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i + 8 <= data.length) {
|
|
34
|
+
if (data[i] !== 181 || data[i + 1] !== 98) {
|
|
35
|
+
i++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const len = data[i + 4] | data[i + 5] << 8;
|
|
39
|
+
const end = i + 6 + len + 2;
|
|
40
|
+
if (end > data.length) break;
|
|
41
|
+
let ckA = 0;
|
|
42
|
+
let ckB = 0;
|
|
43
|
+
for (let j = i + 2; j < i + 6 + len; j++) {
|
|
44
|
+
ckA = ckA + data[j] & 255;
|
|
45
|
+
ckB = ckB + ckA & 255;
|
|
46
|
+
}
|
|
47
|
+
if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
|
|
48
|
+
stats.badChecksums++;
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
yield {
|
|
53
|
+
msgClass: data[i + 2],
|
|
54
|
+
msgId: data[i + 3],
|
|
55
|
+
payload: data.subarray(i + 6, i + 6 + len),
|
|
56
|
+
payloadStart: i + 6
|
|
57
|
+
};
|
|
58
|
+
i = end;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/navbits/index.ts
|
|
63
|
+
var GPS_PI = 3.1415926535898;
|
|
26
64
|
var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
|
|
65
|
+
var SEC_PER_WEEK = 7 * 86400;
|
|
66
|
+
function getBitU(buff, pos, len) {
|
|
67
|
+
let bits = 0;
|
|
68
|
+
for (let i = pos; i < pos + len; i++) {
|
|
69
|
+
bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
|
|
70
|
+
}
|
|
71
|
+
return bits;
|
|
72
|
+
}
|
|
73
|
+
function getBitS(buff, pos, len) {
|
|
74
|
+
const bits = getBitU(buff, pos, len);
|
|
75
|
+
if (len <= 0 || bits < 2 ** (len - 1)) return bits;
|
|
76
|
+
return bits - 2 ** len;
|
|
77
|
+
}
|
|
78
|
+
function setBitU(buff, pos, len, data) {
|
|
79
|
+
let mask = 2 ** (len - 1);
|
|
80
|
+
for (let i = pos; i < pos + len; i++, mask /= 2) {
|
|
81
|
+
if (data >= mask) {
|
|
82
|
+
buff[i >> 3] |= 1 << 7 - (i & 7);
|
|
83
|
+
data -= mask;
|
|
84
|
+
} else {
|
|
85
|
+
buff[i >> 3] &= ~(1 << 7 - (i & 7));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function decodeGpsLnavFrame(subframes, opts = {}) {
|
|
90
|
+
if (subframes.length < 90) return null;
|
|
91
|
+
const b = subframes;
|
|
92
|
+
let i = 24;
|
|
93
|
+
const tow1 = getBitU(b, i, 17) * 6;
|
|
94
|
+
i += 17 + 2;
|
|
95
|
+
const id1 = getBitU(b, i, 3);
|
|
96
|
+
i += 3 + 2;
|
|
97
|
+
const week10 = getBitU(b, i, 10);
|
|
98
|
+
i += 10;
|
|
99
|
+
i += 2;
|
|
100
|
+
i += 4;
|
|
101
|
+
const svHealth = getBitU(b, i, 6);
|
|
102
|
+
i += 6;
|
|
103
|
+
const iodc0 = getBitU(b, i, 2);
|
|
104
|
+
i += 2;
|
|
105
|
+
i += 1 + 87;
|
|
106
|
+
const tgdRaw = getBitS(b, i, 8);
|
|
107
|
+
i += 8;
|
|
108
|
+
const iodc1 = getBitU(b, i, 8);
|
|
109
|
+
i += 8;
|
|
110
|
+
const tocSec = getBitU(b, i, 16) * 16;
|
|
111
|
+
i += 16;
|
|
112
|
+
const af2 = getBitS(b, i, 8) * 2 ** -55;
|
|
113
|
+
i += 8;
|
|
114
|
+
const af1 = getBitS(b, i, 16) * 2 ** -43;
|
|
115
|
+
i += 16;
|
|
116
|
+
const af0 = getBitS(b, i, 22) * 2 ** -31;
|
|
117
|
+
i = 240 + 24;
|
|
118
|
+
i += 17 + 2;
|
|
119
|
+
const id2 = getBitU(b, i, 3);
|
|
120
|
+
i += 3 + 2;
|
|
121
|
+
const iode = getBitU(b, i, 8);
|
|
122
|
+
i += 8;
|
|
123
|
+
const crs = getBitS(b, i, 16) * 2 ** -5;
|
|
124
|
+
i += 16;
|
|
125
|
+
const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
|
|
126
|
+
i += 16;
|
|
127
|
+
const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
128
|
+
i += 32;
|
|
129
|
+
const cuc = getBitS(b, i, 16) * 2 ** -29;
|
|
130
|
+
i += 16;
|
|
131
|
+
const e = getBitU(b, i, 32) * 2 ** -33;
|
|
132
|
+
i += 32;
|
|
133
|
+
const cus = getBitS(b, i, 16) * 2 ** -29;
|
|
134
|
+
i += 16;
|
|
135
|
+
const sqrtA = getBitU(b, i, 32) * 2 ** -19;
|
|
136
|
+
i += 32;
|
|
137
|
+
const toes = getBitU(b, i, 16) * 16;
|
|
138
|
+
i = 480 + 24;
|
|
139
|
+
i += 17 + 2;
|
|
140
|
+
const id3 = getBitU(b, i, 3);
|
|
141
|
+
i += 3 + 2;
|
|
142
|
+
const cic = getBitS(b, i, 16) * 2 ** -29;
|
|
143
|
+
i += 16;
|
|
144
|
+
const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
145
|
+
i += 32;
|
|
146
|
+
const cis = getBitS(b, i, 16) * 2 ** -29;
|
|
147
|
+
i += 16;
|
|
148
|
+
const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
149
|
+
i += 32;
|
|
150
|
+
const crc = getBitS(b, i, 16) * 2 ** -5;
|
|
151
|
+
i += 16;
|
|
152
|
+
const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
|
|
153
|
+
i += 32;
|
|
154
|
+
const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
|
|
155
|
+
i += 24;
|
|
156
|
+
const iode3 = getBitU(b, i, 8);
|
|
157
|
+
i += 8;
|
|
158
|
+
const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
|
|
159
|
+
const iodc = iodc0 * 256 + iodc1;
|
|
160
|
+
if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
|
|
161
|
+
if (iode3 !== iode || iode !== (iodc & 255)) return null;
|
|
162
|
+
const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
|
|
163
|
+
let week = week10 + 1024 * Math.round((ref - week10) / 1024);
|
|
164
|
+
if (toes < tow1 - 302400) week++;
|
|
165
|
+
else if (toes > tow1 + 302400) week--;
|
|
166
|
+
const prn = opts.prn ?? "G00";
|
|
167
|
+
const tocDate = new Date(
|
|
168
|
+
GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
|
|
169
|
+
);
|
|
170
|
+
return {
|
|
171
|
+
system: prn[0] === "J" ? "J" : "G",
|
|
172
|
+
prn,
|
|
173
|
+
tocDate,
|
|
174
|
+
// Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
|
|
175
|
+
toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
|
|
176
|
+
af0,
|
|
177
|
+
af1,
|
|
178
|
+
af2,
|
|
179
|
+
iode,
|
|
180
|
+
crs,
|
|
181
|
+
deltaN,
|
|
182
|
+
m0,
|
|
183
|
+
cuc,
|
|
184
|
+
e,
|
|
185
|
+
cus,
|
|
186
|
+
sqrtA,
|
|
187
|
+
toe: toes,
|
|
188
|
+
cic,
|
|
189
|
+
omega0,
|
|
190
|
+
cis,
|
|
191
|
+
i0,
|
|
192
|
+
crc,
|
|
193
|
+
omega,
|
|
194
|
+
omegaDot,
|
|
195
|
+
idot,
|
|
196
|
+
week,
|
|
197
|
+
svHealth,
|
|
198
|
+
tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
|
|
199
|
+
// IS-GPS-200: -128 reserved
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/ubx/nav.ts
|
|
204
|
+
var PREAMB_CNAV = 139;
|
|
205
|
+
function readLnavSubframe(view, f) {
|
|
206
|
+
const p = f.payload;
|
|
207
|
+
if (p.length < 8) return null;
|
|
208
|
+
const gnssId = p[0];
|
|
209
|
+
const svId = p[1];
|
|
210
|
+
let prn;
|
|
211
|
+
if (gnssId === 0 && svId >= 1 && svId <= 32) {
|
|
212
|
+
prn = `G${String(svId).padStart(2, "0")}`;
|
|
213
|
+
} else if (gnssId === 5 && svId >= 1 && svId <= 10) {
|
|
214
|
+
if (p.length === 44) return null;
|
|
215
|
+
prn = `J${String(svId).padStart(2, "0")}`;
|
|
216
|
+
} else {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
if (p.length < 8 + 40) return null;
|
|
220
|
+
const base = f.payloadStart + 8;
|
|
221
|
+
if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) return null;
|
|
222
|
+
const buff = new Uint8Array(30);
|
|
223
|
+
for (let k = 0; k < 10; k++) {
|
|
224
|
+
const dwrd = view.getUint32(base + 4 * k, true);
|
|
225
|
+
setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
|
|
226
|
+
}
|
|
227
|
+
return { prn, gnssId, svId, buff, id: getBitU(buff, 43, 3) };
|
|
228
|
+
}
|
|
229
|
+
function parseUbxNav(data, opts = {}) {
|
|
230
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
231
|
+
const ephemerides = [];
|
|
232
|
+
let badParity = 0;
|
|
233
|
+
let refWeek = opts.refWeek;
|
|
234
|
+
if (refWeek === void 0) {
|
|
235
|
+
for (const f of ubxFrames(data)) {
|
|
236
|
+
if (f.msgClass === 2 && f.msgId === 21 && f.payload.length >= 16) {
|
|
237
|
+
const week = view.getUint16(f.payloadStart + 8, true);
|
|
238
|
+
if (week > 0) {
|
|
239
|
+
refWeek = week;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (refWeek === void 0) return { ephemerides, badParity };
|
|
246
|
+
const subframes = /* @__PURE__ */ new Map();
|
|
247
|
+
const last = /* @__PURE__ */ new Map();
|
|
248
|
+
for (const f of ubxFrames(data)) {
|
|
249
|
+
if (f.msgClass !== 2 || f.msgId !== 19) continue;
|
|
250
|
+
const lnav = readLnavSubframe(view, f);
|
|
251
|
+
if (!lnav) continue;
|
|
252
|
+
const { prn, gnssId, svId, buff, id } = lnav;
|
|
253
|
+
if (id < 1 || id > 5) {
|
|
254
|
+
badParity++;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (id > 3) continue;
|
|
258
|
+
const key = gnssId * 256 + svId;
|
|
259
|
+
let sf = subframes.get(key);
|
|
260
|
+
if (!sf) {
|
|
261
|
+
sf = { buf: new Uint8Array(90), have: 0 };
|
|
262
|
+
subframes.set(key, sf);
|
|
263
|
+
}
|
|
264
|
+
sf.buf.set(buff, (id - 1) * 30);
|
|
265
|
+
sf.have |= 1 << id - 1;
|
|
266
|
+
if (id !== 3 || sf.have !== 7) continue;
|
|
267
|
+
const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
|
|
268
|
+
if (!eph) {
|
|
269
|
+
badParity++;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const prev = last.get(prn);
|
|
273
|
+
if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
last.set(prn, eph);
|
|
277
|
+
ephemerides.push(eph);
|
|
278
|
+
}
|
|
279
|
+
return { ephemerides, badParity };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/ubx/iono.ts
|
|
283
|
+
function parseUbxIonoUtc(data) {
|
|
284
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
285
|
+
const ionoCorrections = {};
|
|
286
|
+
let leapSeconds = null;
|
|
287
|
+
for (const f of ubxFrames(data)) {
|
|
288
|
+
if (f.msgClass !== 2 || f.msgId !== 19) continue;
|
|
289
|
+
const sf = readLnavSubframe(view, f);
|
|
290
|
+
if (!sf || sf.gnssId !== 0 || sf.id !== 4) continue;
|
|
291
|
+
const b = sf.buff;
|
|
292
|
+
if (getBitU(b, 48, 2) !== 1 || getBitU(b, 50, 6) !== 56) continue;
|
|
293
|
+
ionoCorrections["GPSA"] = [
|
|
294
|
+
getBitS(b, 56, 8) * 2 ** -30,
|
|
295
|
+
getBitS(b, 64, 8) * 2 ** -27,
|
|
296
|
+
getBitS(b, 72, 8) * 2 ** -24,
|
|
297
|
+
getBitS(b, 80, 8) * 2 ** -24
|
|
298
|
+
];
|
|
299
|
+
ionoCorrections["GPSB"] = [
|
|
300
|
+
getBitS(b, 88, 8) * 2 ** 11,
|
|
301
|
+
getBitS(b, 96, 8) * 2 ** 14,
|
|
302
|
+
getBitS(b, 104, 8) * 2 ** 16,
|
|
303
|
+
getBitS(b, 112, 8) * 2 ** 16
|
|
304
|
+
];
|
|
305
|
+
leapSeconds = getBitS(b, 192, 8);
|
|
306
|
+
}
|
|
307
|
+
return { ionoCorrections, leapSeconds };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/ubx/index.ts
|
|
311
|
+
var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
|
|
27
312
|
var MS_PER_WEEK = 7 * 864e5;
|
|
28
313
|
var SIGNALS = {
|
|
29
314
|
0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
|
|
@@ -63,33 +348,14 @@ function parseUbxRawx(data) {
|
|
|
63
348
|
const epochs = [];
|
|
64
349
|
const messageCounts = {};
|
|
65
350
|
const obsCodes = {};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
i++;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const cls = data[i + 2];
|
|
74
|
-
const id = data[i + 3];
|
|
75
|
-
const len = data[i + 4] | data[i + 5] << 8;
|
|
76
|
-
const end = i + 6 + len + 2;
|
|
77
|
-
if (end > data.length) break;
|
|
78
|
-
let ckA = 0;
|
|
79
|
-
let ckB = 0;
|
|
80
|
-
for (let j = i + 2; j < i + 6 + len; j++) {
|
|
81
|
-
ckA = ckA + data[j] & 255;
|
|
82
|
-
ckB = ckB + ckA & 255;
|
|
83
|
-
}
|
|
84
|
-
if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
|
|
85
|
-
badChecksums++;
|
|
86
|
-
i++;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
351
|
+
const stats = { badChecksums: 0 };
|
|
352
|
+
for (const frame of ubxFrames(data, stats)) {
|
|
353
|
+
const { msgClass: cls, msgId: id } = frame;
|
|
354
|
+
const len = frame.payload.length;
|
|
89
355
|
const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
|
|
90
356
|
messageCounts[key] = (messageCounts[key] ?? 0) + 1;
|
|
91
357
|
if (cls === 2 && id === 21 && len >= 16) {
|
|
92
|
-
const p =
|
|
358
|
+
const p = frame.payloadStart;
|
|
93
359
|
const rcvTow = view.getFloat64(p, true);
|
|
94
360
|
const week = view.getUint16(p + 8, true);
|
|
95
361
|
const leapS = view.getInt8(p + 10);
|
|
@@ -125,16 +391,18 @@ function parseUbxRawx(data) {
|
|
|
125
391
|
if (!codes.includes(sig[1])) codes.push(sig[1]);
|
|
126
392
|
}
|
|
127
393
|
epochs.push({
|
|
128
|
-
timeMs:
|
|
394
|
+
timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + Math.round(rcvTow * 1e3),
|
|
129
395
|
leapS: leapValid ? leapS : null,
|
|
130
396
|
meas
|
|
131
397
|
});
|
|
132
398
|
}
|
|
133
|
-
i = end;
|
|
134
399
|
}
|
|
135
|
-
return { epochs, messageCounts, obsCodes, badChecksums };
|
|
400
|
+
return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
|
|
136
401
|
}
|
|
137
402
|
// Annotate the CommonJS export names for ESM import in node:
|
|
138
403
|
0 && (module.exports = {
|
|
139
|
-
|
|
404
|
+
parseUbxIonoUtc,
|
|
405
|
+
parseUbxNav,
|
|
406
|
+
parseUbxRawx,
|
|
407
|
+
ubxFrames
|
|
140
408
|
});
|
package/dist/ubx.d.cts
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
+
import { E as Ephemeris } from './nav-BAI1a9vK.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* u-blox UBX framing shared by the measurement and navigation decoders:
|
|
5
|
+
* 0xB5 0x62 sync, message class and ID, little-endian payload length,
|
|
6
|
+
* payload, and an 8-bit Fletcher checksum over class..payload.
|
|
7
|
+
*/
|
|
8
|
+
/** One checksum-valid UBX frame located in a byte stream. */
|
|
9
|
+
interface UbxFrame {
|
|
10
|
+
/** Message class, e.g. 0x02 for RXM. */
|
|
11
|
+
msgClass: number;
|
|
12
|
+
/** Message ID within the class, e.g. 0x15 for RXM-RAWX. */
|
|
13
|
+
msgId: number;
|
|
14
|
+
/** Payload bytes (a view into the input buffer, not a copy). */
|
|
15
|
+
payload: Uint8Array;
|
|
16
|
+
/** Byte offset of the payload within the input buffer. */
|
|
17
|
+
payloadStart: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Iterate every checksum-valid UBX frame in `data`. Bad checksums are
|
|
21
|
+
* counted in `stats.badChecksums` and a resync continues at the next
|
|
22
|
+
* byte; a frame candidate that overruns the buffer ends the scan
|
|
23
|
+
* (truncated capture).
|
|
24
|
+
*/
|
|
25
|
+
declare function ubxFrames(data: Uint8Array, stats?: {
|
|
26
|
+
badChecksums: number;
|
|
27
|
+
}): Generator<UbxFrame>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* u-blox UBX navigation-message decoding: GPS/QZSS LNAV broadcast
|
|
31
|
+
* ephemerides from RXM-SFRBX (class 0x02, id 0x13).
|
|
32
|
+
*
|
|
33
|
+
* Ported from RTKLIB demo5 (rtklibexplorer fork, src/rcv/ublox.c:
|
|
34
|
+
* decode_rxmsfrbx / decode_nav / decode_eph, Copyright (c) 2007-2020
|
|
35
|
+
* T. Takasu, BSD-2-Clause); the subframe fields themselves are decoded
|
|
36
|
+
* by `decodeGpsLnavFrame` (the decode_frame port in src/navbits).
|
|
37
|
+
*
|
|
38
|
+
* On D30* polarity: the receiver has already checked parity and
|
|
39
|
+
* resolved the polarity inversion of the data bits before writing the
|
|
40
|
+
* 30-bit words into the 30 LSBs of each `dwrd`, so — exactly like
|
|
41
|
+
* RTKLIB's decode_nav (`U4(p)>>6`, "24 x 10 bits w/o parity") — the
|
|
42
|
+
* conversion to parity-stripped 24-bit words is a plain 6-bit shift
|
|
43
|
+
* with NO D30* re-inversion. (Verified against the TLM preamble 0x8B
|
|
44
|
+
* at bits 29..22 of word 1 in every GPS/QZSS LNAV message of the
|
|
45
|
+
* reference F9P capture.)
|
|
46
|
+
*
|
|
47
|
+
* Other constellations in RXM-SFRBX (Galileo I/NAV, BDS D1/D2, GLONASS
|
|
48
|
+
* strings, SBAS) and GPS/QZSS CNAV are out of scope and skipped
|
|
49
|
+
* silently. LNAV subframes 4/5 are skipped here except that
|
|
50
|
+
* `parseUbxIonoUtc` (./iono.ts) decodes the subframe-4 page-18
|
|
51
|
+
* iono/UTC parameters through the shared `readLnavSubframe` helper.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
interface UbxNavOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Full GPS week used to resolve the 10-bit broadcast week. When
|
|
57
|
+
* omitted, the week is harvested from the first RXM-RAWX message in
|
|
58
|
+
* the same stream (u-blox raw logs carry both); a stream with
|
|
59
|
+
* neither yields no ephemerides — the system clock is never used.
|
|
60
|
+
*/
|
|
61
|
+
refWeek?: number;
|
|
62
|
+
}
|
|
63
|
+
interface UbxNavResult {
|
|
64
|
+
/** Broadcast ephemerides in stream order, duplicates suppressed. */
|
|
65
|
+
ephemerides: Ephemeris[];
|
|
66
|
+
/**
|
|
67
|
+
* Subframes rejected for inconsistency: an out-of-range subframe ID
|
|
68
|
+
* in the HOW word, or a complete subframe-1/2/3 set whose
|
|
69
|
+
* IODC/IODE cross-check failed (mixed issues of data).
|
|
70
|
+
*/
|
|
71
|
+
badParity: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Decode every GPS/QZSS LNAV ephemeris broadcast in a UBX byte stream
|
|
75
|
+
* (RXM-SFRBX). Subframes 1–3 are assembled per satellite; a frame is
|
|
76
|
+
* decoded when subframe 3 arrives with 1 and 2 buffered (RTKLIB
|
|
77
|
+
* decode_nav), and repeated broadcasts of an unchanged ephemeris are
|
|
78
|
+
* suppressed by issue of data and clock epoch like `parseNovatelNav`.
|
|
79
|
+
*/
|
|
80
|
+
declare function parseUbxNav(data: Uint8Array, opts?: UbxNavOptions): UbxNavResult;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* u-blox UBX ionosphere / UTC decoding: the GPS LNAV subframe 4
|
|
84
|
+
* page 18 broadcast (Klobuchar coefficients + GPS-UTC parameters)
|
|
85
|
+
* from RXM-SFRBX.
|
|
86
|
+
*
|
|
87
|
+
* Ported from RTKLIB demo5 (rtklibexplorer fork, src/rcvraw.c:
|
|
88
|
+
* decode_frame_ion / decode_frame_utc, Copyright (c) 2007-2020
|
|
89
|
+
* T. Takasu, BSD-2-Clause); frame extraction is shared with
|
|
90
|
+
* `parseUbxNav` through `readLnavSubframe` (./nav.ts).
|
|
91
|
+
*
|
|
92
|
+
* Scope is GPS satellites (gnssId 0) only: QZSS also broadcasts
|
|
93
|
+
* page-18-format iono parameters, but those are the separate
|
|
94
|
+
* wide-area/Japan-area QZSS coefficient set, not the GPS one a RINEX
|
|
95
|
+
* `GPSA`/`GPSB` header carries. Scale factors follow IS-GPS-200
|
|
96
|
+
* §20.3.3.5.1 (alpha_n in s/semicircle^n, beta_n in s/semicircle^n) —
|
|
97
|
+
* the same semicircle-based units the RINEX nav header prints, so the
|
|
98
|
+
* output matches `parseNavFile` on a converted header. Like
|
|
99
|
+
* `parseUbxNav`, the decoder never consults the system clock (nothing
|
|
100
|
+
* here needs a week reference).
|
|
101
|
+
*/
|
|
102
|
+
interface UbxIonoUtcResult {
|
|
103
|
+
/**
|
|
104
|
+
* Iono coefficient sets keyed like `NavHeader.ionoCorrections`:
|
|
105
|
+
* `GPSA` (alpha_0..3) and `GPSB` (beta_0..3). The page repeats
|
|
106
|
+
* (nominally every 12.5 min per satellite); the last broadcast in
|
|
107
|
+
* the stream wins.
|
|
108
|
+
*/
|
|
109
|
+
ionoCorrections: Record<string, number[]>;
|
|
110
|
+
/** GPS-UTC ΔtLS from the last page-18 broadcast, if any. */
|
|
111
|
+
leapSeconds: number | null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Decode every GPS LNAV subframe 4 page 18 (data ID 1, SV ID 56) in a
|
|
115
|
+
* UBX byte stream (RXM-SFRBX) into Klobuchar iono coefficients and the
|
|
116
|
+
* current leap-second count.
|
|
117
|
+
*/
|
|
118
|
+
declare function parseUbxIonoUtc(data: Uint8Array): UbxIonoUtcResult;
|
|
119
|
+
|
|
1
120
|
/**
|
|
2
121
|
* u-blox UBX raw-measurement decoding (RXM-RAWX) — the path from a
|
|
3
122
|
* receiver log to RINEX-grade observables.
|
|
@@ -12,6 +131,7 @@
|
|
|
12
131
|
* RINEX attributes chosen to match RTKLIB's u-blox conversion (the de
|
|
13
132
|
* facto reference: GPS L2 CL/CM as 2X, Galileo E1 as 1X, E5b as 7X).
|
|
14
133
|
*/
|
|
134
|
+
|
|
15
135
|
interface UbxMeasurement {
|
|
16
136
|
/** RINEX PRN, e.g. "G04", "R11", "S23". */
|
|
17
137
|
prn: string;
|
|
@@ -53,4 +173,4 @@ interface UbxParseResult {
|
|
|
53
173
|
*/
|
|
54
174
|
declare function parseUbxRawx(data: Uint8Array): UbxParseResult;
|
|
55
175
|
|
|
56
|
-
export { type UbxMeasurement, type UbxParseResult, type UbxRawxEpoch, parseUbxRawx };
|
|
176
|
+
export { type UbxFrame, type UbxIonoUtcResult, type UbxMeasurement, type UbxNavOptions, type UbxNavResult, type UbxParseResult, type UbxRawxEpoch, parseUbxIonoUtc, parseUbxNav, parseUbxRawx, ubxFrames };
|