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.
@@ -1,3 +1,6 @@
1
+ import {
2
+ decodeGpsLnavFrame
3
+ } from "./chunk-REYOYF7O.js";
1
4
  import {
2
5
  getUtcDate
3
6
  } from "./chunk-HVXYFUCB.js";
@@ -46,142 +49,27 @@ function* oem4Frames(data, view, stats) {
46
49
  }
47
50
  }
48
51
 
49
- // src/navbits/index.ts
50
- var GPS_PI = 3.1415926535898;
51
- var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
52
- var SEC_PER_WEEK = 7 * 86400;
53
- function getBitU(buff, pos, len) {
54
- let bits = 0;
55
- for (let i = pos; i < pos + len; i++) {
56
- bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
57
- }
58
- return bits;
59
- }
60
- function getBitS(buff, pos, len) {
61
- const bits = getBitU(buff, pos, len);
62
- if (len <= 0 || bits < 2 ** (len - 1)) return bits;
63
- return bits - 2 ** len;
64
- }
65
- function decodeGpsLnavFrame(subframes, opts = {}) {
66
- if (subframes.length < 90) return null;
67
- const b = subframes;
68
- let i = 24;
69
- const tow1 = getBitU(b, i, 17) * 6;
70
- i += 17 + 2;
71
- const id1 = getBitU(b, i, 3);
72
- i += 3 + 2;
73
- const week10 = getBitU(b, i, 10);
74
- i += 10;
75
- i += 2;
76
- i += 4;
77
- const svHealth = getBitU(b, i, 6);
78
- i += 6;
79
- const iodc0 = getBitU(b, i, 2);
80
- i += 2;
81
- i += 1 + 87;
82
- const tgdRaw = getBitS(b, i, 8);
83
- i += 8;
84
- const iodc1 = getBitU(b, i, 8);
85
- i += 8;
86
- const tocSec = getBitU(b, i, 16) * 16;
87
- i += 16;
88
- const af2 = getBitS(b, i, 8) * 2 ** -55;
89
- i += 8;
90
- const af1 = getBitS(b, i, 16) * 2 ** -43;
91
- i += 16;
92
- const af0 = getBitS(b, i, 22) * 2 ** -31;
93
- i = 240 + 24;
94
- i += 17 + 2;
95
- const id2 = getBitU(b, i, 3);
96
- i += 3 + 2;
97
- const iode = getBitU(b, i, 8);
98
- i += 8;
99
- const crs = getBitS(b, i, 16) * 2 ** -5;
100
- i += 16;
101
- const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
102
- i += 16;
103
- const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
104
- i += 32;
105
- const cuc = getBitS(b, i, 16) * 2 ** -29;
106
- i += 16;
107
- const e = getBitU(b, i, 32) * 2 ** -33;
108
- i += 32;
109
- const cus = getBitS(b, i, 16) * 2 ** -29;
110
- i += 16;
111
- const sqrtA = getBitU(b, i, 32) * 2 ** -19;
112
- i += 32;
113
- const toes = getBitU(b, i, 16) * 16;
114
- i = 480 + 24;
115
- i += 17 + 2;
116
- const id3 = getBitU(b, i, 3);
117
- i += 3 + 2;
118
- const cic = getBitS(b, i, 16) * 2 ** -29;
119
- i += 16;
120
- const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
121
- i += 32;
122
- const cis = getBitS(b, i, 16) * 2 ** -29;
123
- i += 16;
124
- const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
125
- i += 32;
126
- const crc = getBitS(b, i, 16) * 2 ** -5;
127
- i += 16;
128
- const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
129
- i += 32;
130
- const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
131
- i += 24;
132
- const iode3 = getBitU(b, i, 8);
133
- i += 8;
134
- const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
135
- const iodc = iodc0 * 256 + iodc1;
136
- if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
137
- if (iode3 !== iode || iode !== (iodc & 255)) return null;
138
- const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
139
- let week = week10 + 1024 * Math.round((ref - week10) / 1024);
140
- if (toes < tow1 - 302400) week++;
141
- else if (toes > tow1 + 302400) week--;
142
- const prn = opts.prn ?? "G00";
143
- const tocDate = new Date(
144
- GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
145
- );
146
- return {
147
- system: prn[0] === "J" ? "J" : "G",
148
- prn,
149
- tocDate,
150
- // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
151
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
152
- af0,
153
- af1,
154
- af2,
155
- iode,
156
- crs,
157
- deltaN,
158
- m0,
159
- cuc,
160
- e,
161
- cus,
162
- sqrtA,
163
- toe: toes,
164
- cic,
165
- omega0,
166
- cis,
167
- i0,
168
- crc,
169
- omega,
170
- omegaDot,
171
- idot,
172
- week,
173
- svHealth,
174
- tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
175
- // IS-GPS-200: -128 reserved
176
- };
177
- }
178
-
179
52
  // src/novatel/nav.ts
53
+ var ID_IONUTC = 8;
180
54
  var ID_RAWEPHEM = 41;
181
55
  var ID_GLOEPHEMERIS = 723;
182
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
183
- var SEC_PER_WEEK2 = 7 * 86400;
56
+ var ID_GALEPHEMERIS = 1122;
57
+ var ID_QZSSEPHEMERIS = 1336;
58
+ var ID_BDSEPHEMERIS = 1696;
59
+ var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
60
+ var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
61
+ var SEC_PER_WEEK = 7 * 86400;
184
62
  var SEC_PER_DAY = 86400;
63
+ var MS_PER_WEEK = SEC_PER_WEEK * 1e3;
64
+ var gpsMs = (week, sec) => GPS_EPOCH_MS + week * MS_PER_WEEK + sec * 1e3;
65
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK;
66
+ function nearWeekMs(refMs, sow) {
67
+ const refWeek = Math.floor((refMs - GPS_EPOCH_MS) / MS_PER_WEEK);
68
+ let ms = gpsMs(refWeek, sow);
69
+ if (ms < refMs - MS_PER_WEEK / 2) ms += MS_PER_WEEK;
70
+ else if (ms > refMs + MS_PER_WEEK / 2) ms -= MS_PER_WEEK;
71
+ return ms;
72
+ }
185
73
  function decodeRawEphem(data, view, p) {
186
74
  const prn = view.getUint32(p, true);
187
75
  if (prn < 1 || prn > 32) return null;
@@ -203,9 +91,9 @@ function decodeGloEphemeris(view, p) {
203
91
  tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
204
92
  if (tof < tow - 43200) tof += SEC_PER_DAY;
205
93
  else if (tof > tow + 43200) tof -= SEC_PER_DAY;
206
- const toeGpsMs = GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + tow) * 1e3;
207
- const tofGpsMs = GPS_EPOCH_MS2 + (week * SEC_PER_WEEK2 + tof) * 1e3;
208
- const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS2) / 1e3;
94
+ const toeGpsMs = GPS_EPOCH_MS + (week * SEC_PER_WEEK + tow) * 1e3;
95
+ const tofGpsMs = GPS_EPOCH_MS + (week * SEC_PER_WEEK + tof) * 1e3;
96
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS) / 1e3;
209
97
  return {
210
98
  system: "R",
211
99
  prn: `R${String(slot).padStart(2, "0")}`,
@@ -213,7 +101,7 @@ function decodeGloEphemeris(view, p) {
213
101
  tauN: -view.getFloat64(p + 100, true),
214
102
  // RINEX stores −τ_n
215
103
  gammaN: view.getFloat64(p + 116, true),
216
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK2 + SEC_PER_WEEK2) % SEC_PER_WEEK2,
104
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK + SEC_PER_WEEK) % SEC_PER_WEEK,
217
105
  x: view.getFloat64(p + 28, true) / 1e3,
218
106
  y: view.getFloat64(p + 36, true) / 1e3,
219
107
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -227,23 +115,184 @@ function decodeGloEphemeris(view, p) {
227
115
  freqNum
228
116
  };
229
117
  }
118
+ function decodeGalEphemeris(view, p, headerMs) {
119
+ const prn = view.getUint32(p, true);
120
+ if (prn < 1 || prn > 36) return null;
121
+ const fnav = (view.getUint32(p + 4, true) & 1) !== 0;
122
+ const svhE1b = view.getUint8(p + 12) & 3;
123
+ const svhE5a = view.getUint8(p + 13) & 3;
124
+ const svhE5b = view.getUint8(p + 14) & 3;
125
+ const dvsE1b = view.getUint8(p + 15) & 1;
126
+ const dvsE5a = view.getUint8(p + 16) & 1;
127
+ const dvsE5b = view.getUint8(p + 17) & 1;
128
+ const iodNav = view.getUint32(p + 20, true);
129
+ const toes = view.getUint32(p + 24, true);
130
+ const c = fnav ? p + 148 : p + 176;
131
+ const tocs = view.getUint32(c, true);
132
+ const toeMs = nearWeekMs(headerMs, toes);
133
+ const tocDate = new Date(nearWeekMs(headerMs, tocs));
134
+ return {
135
+ fnav,
136
+ eph: {
137
+ system: "E",
138
+ prn: `E${String(prn).padStart(2, "0")}`,
139
+ toc: sowOf(tocDate.getTime()),
140
+ tocDate,
141
+ af0: view.getFloat64(c + 4, true),
142
+ af1: view.getFloat64(c + 12, true),
143
+ af2: view.getFloat64(c + 20, true),
144
+ iode: iodNav,
145
+ crs: view.getFloat64(p + 92, true),
146
+ deltaN: view.getFloat64(p + 36, true),
147
+ m0: view.getFloat64(p + 44, true),
148
+ cuc: view.getFloat64(p + 68, true),
149
+ e: view.getFloat64(p + 52, true),
150
+ cus: view.getFloat64(p + 76, true),
151
+ sqrtA: view.getFloat64(p + 28, true),
152
+ toe: toes,
153
+ cic: view.getFloat64(p + 100, true),
154
+ omega0: view.getFloat64(p + 132, true),
155
+ cis: view.getFloat64(p + 108, true),
156
+ i0: view.getFloat64(p + 116, true),
157
+ crc: view.getFloat64(p + 84, true),
158
+ omega: view.getFloat64(p + 60, true),
159
+ omegaDot: view.getFloat64(p + 140, true),
160
+ idot: view.getFloat64(p + 124, true),
161
+ week: Math.floor((toeMs - GPS_EPOCH_MS) / MS_PER_WEEK),
162
+ // RINEX Galileo SVH bit layout (E1B DVS/HS in bits 0-2, E5a in
163
+ // 3-5, E5b in 6-8) — same packing as RTKLIB.
164
+ svHealth: svhE5b << 7 | dvsE5b << 6 | svhE5a << 4 | dvsE5a << 3 | svhE1b << 1 | dvsE1b,
165
+ tgd: view.getFloat64(p + 204, true)
166
+ // BGD E5a/E1 (RINEX slot)
167
+ }
168
+ };
169
+ }
170
+ function decodeBdsEphemeris(view, p) {
171
+ const prn = view.getUint32(p, true);
172
+ if (prn < 1 || prn > 63) return null;
173
+ const week = view.getUint32(p + 4, true);
174
+ const health = view.getUint32(p + 16, true) & 1;
175
+ const tgd1 = view.getFloat64(p + 20, true);
176
+ const tocs = view.getUint32(p + 40, true);
177
+ const toes = view.getUint32(p + 72, true);
178
+ const tocDate = new Date(BDT_EPOCH_MS + week * MS_PER_WEEK + tocs * 1e3);
179
+ return {
180
+ system: "C",
181
+ prn: `C${String(prn).padStart(2, "0")}`,
182
+ toc: sowOf(tocDate.getTime()),
183
+ tocDate,
184
+ af0: view.getFloat64(p + 44, true),
185
+ af1: view.getFloat64(p + 52, true),
186
+ af2: view.getFloat64(p + 60, true),
187
+ iode: view.getUint32(p + 68, true),
188
+ // AODE
189
+ crs: view.getFloat64(p + 172, true),
190
+ deltaN: view.getFloat64(p + 100, true),
191
+ m0: view.getFloat64(p + 108, true),
192
+ cuc: view.getFloat64(p + 148, true),
193
+ e: view.getFloat64(p + 84, true),
194
+ cus: view.getFloat64(p + 156, true),
195
+ sqrtA: view.getFloat64(p + 76, true),
196
+ toe: toes,
197
+ cic: view.getFloat64(p + 180, true),
198
+ omega0: view.getFloat64(p + 116, true),
199
+ cis: view.getFloat64(p + 188, true),
200
+ i0: view.getFloat64(p + 132, true),
201
+ crc: view.getFloat64(p + 164, true),
202
+ omega: view.getFloat64(p + 92, true),
203
+ omegaDot: view.getFloat64(p + 124, true),
204
+ idot: view.getFloat64(p + 140, true),
205
+ week,
206
+ // RINEX BDS week field is the BDT week
207
+ svHealth: health,
208
+ tgd: tgd1
209
+ // TGD1 (B1) — RINEX slot
210
+ };
211
+ }
212
+ function decodeQzssEphemeris(view, p) {
213
+ const prn = view.getUint32(p, true);
214
+ if (prn < 193 || prn > 202) return null;
215
+ const health = view.getUint32(p + 12, true) & 63;
216
+ const iode1 = view.getUint32(p + 16, true);
217
+ const week = view.getUint32(p + 24, true);
218
+ const toes = view.getFloat64(p + 32, true);
219
+ const toeMs = gpsMs(week, toes);
220
+ const tocs = view.getFloat64(p + 164, true);
221
+ const tocDate = new Date(nearWeekMs(toeMs, tocs));
222
+ return {
223
+ system: "J",
224
+ prn: `J${String(prn - 192).padStart(2, "0")}`,
225
+ toc: sowOf(tocDate.getTime()),
226
+ tocDate,
227
+ af0: view.getFloat64(p + 180, true),
228
+ af1: view.getFloat64(p + 188, true),
229
+ af2: view.getFloat64(p + 196, true),
230
+ iode: iode1,
231
+ crs: view.getFloat64(p + 104, true),
232
+ deltaN: view.getFloat64(p + 48, true),
233
+ m0: view.getFloat64(p + 56, true),
234
+ cuc: view.getFloat64(p + 80, true),
235
+ e: view.getFloat64(p + 64, true),
236
+ cus: view.getFloat64(p + 88, true),
237
+ sqrtA: Math.sqrt(view.getFloat64(p + 40, true)),
238
+ toe: toes,
239
+ cic: view.getFloat64(p + 112, true),
240
+ omega0: view.getFloat64(p + 144, true),
241
+ cis: view.getFloat64(p + 120, true),
242
+ i0: view.getFloat64(p + 128, true),
243
+ crc: view.getFloat64(p + 96, true),
244
+ omega: view.getFloat64(p + 72, true),
245
+ omegaDot: view.getFloat64(p + 152, true),
246
+ idot: view.getFloat64(p + 136, true),
247
+ week,
248
+ svHealth: health,
249
+ tgd: view.getFloat64(p + 172, true)
250
+ };
251
+ }
230
252
  function parseNovatelNav(data) {
231
253
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
232
254
  const stats = { badCrc: 0 };
233
255
  const ephemerides = [];
234
- const lastGps = /* @__PURE__ */ new Map();
256
+ const ionoCorrections = {};
257
+ let leapSeconds = null;
258
+ const lastKep = /* @__PURE__ */ new Map();
235
259
  const lastGlo = /* @__PURE__ */ new Map();
260
+ const pushKepler = (key, eph) => {
261
+ const prev = lastKep.get(key);
262
+ if (prev && prev.iode === eph.iode && prev.toe === eph.toe && prev.tocDate.getTime() === eph.tocDate.getTime()) {
263
+ return;
264
+ }
265
+ lastKep.set(key, eph);
266
+ ephemerides.push(eph);
267
+ };
236
268
  for (const frame of oem4Frames(data, view, stats)) {
237
269
  if (!frame.binary) continue;
238
270
  if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
239
271
  const eph = decodeRawEphem(data, view, frame.payload);
240
- if (!eph) continue;
241
- const prev = lastGps.get(eph.prn);
242
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
243
- continue;
272
+ if (eph) pushKepler(eph.prn, eph);
273
+ } else if (frame.id === ID_GALEPHEMERIS && frame.msgLen >= 220) {
274
+ if (frame.week === 0) continue;
275
+ const headerMs = gpsMs(frame.week, frame.towMs / 1e3);
276
+ const dec = decodeGalEphemeris(view, frame.payload, headerMs);
277
+ if (dec)
278
+ pushKepler(`${dec.eph.prn}:${dec.fnav ? "fnav" : "inav"}`, dec.eph);
279
+ } else if (frame.id === ID_BDSEPHEMERIS && frame.msgLen >= 196) {
280
+ const eph = decodeBdsEphemeris(view, frame.payload);
281
+ if (eph) pushKepler(eph.prn, eph);
282
+ } else if (frame.id === ID_QZSSEPHEMERIS && frame.msgLen >= 204) {
283
+ const eph = decodeQzssEphemeris(view, frame.payload);
284
+ if (eph) pushKepler(eph.prn, eph);
285
+ } else if (frame.id === ID_IONUTC && frame.msgLen >= 108) {
286
+ const p = frame.payload;
287
+ const alpha = [];
288
+ const beta = [];
289
+ for (let i = 0; i < 4; i++) {
290
+ alpha.push(view.getFloat64(p + i * 8, true));
291
+ beta.push(view.getFloat64(p + 32 + i * 8, true));
244
292
  }
245
- lastGps.set(eph.prn, eph);
246
- ephemerides.push(eph);
293
+ ionoCorrections["GPSA"] = alpha;
294
+ ionoCorrections["GPSB"] = beta;
295
+ leapSeconds = view.getInt32(p + 96, true);
247
296
  } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
248
297
  const eph = decodeGloEphemeris(view, frame.payload);
249
298
  if (!eph) continue;
@@ -255,12 +304,12 @@ function parseNovatelNav(data) {
255
304
  ephemerides.push(eph);
256
305
  }
257
306
  }
258
- return { ephemerides, badCrc: stats.badCrc };
307
+ return { ephemerides, ionoCorrections, leapSeconds, badCrc: stats.badCrc };
259
308
  }
260
309
 
261
310
  // src/novatel/index.ts
262
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
263
- var MS_PER_WEEK = 7 * 864e5;
311
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
312
+ var MS_PER_WEEK2 = 7 * 864e5;
264
313
  var HDR = 28;
265
314
  var ID_RANGE = 43;
266
315
  var ID_RANGECMP = 140;
@@ -429,7 +478,7 @@ function parseNovatelRange(data) {
429
478
  if (!codes.includes(code)) codes.push(code);
430
479
  }
431
480
  epochs.push({
432
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK + towMs,
481
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + towMs,
433
482
  meas
434
483
  });
435
484
  }
@@ -481,7 +530,7 @@ function parseNovatelRange(data) {
481
530
  if (!codes.includes(code)) codes.push(code);
482
531
  }
483
532
  epochs.push({
484
- timeMs: GPS_EPOCH_MS3 + week * MS_PER_WEEK + towMs,
533
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + towMs,
485
534
  meas
486
535
  });
487
536
  }
@@ -0,0 +1,245 @@
1
+ import {
2
+ decodeGpsLnavFrame,
3
+ getBitS,
4
+ getBitU,
5
+ setBitU
6
+ } from "./chunk-REYOYF7O.js";
7
+
8
+ // src/ubx/frame.ts
9
+ function* ubxFrames(data, stats = { badChecksums: 0 }) {
10
+ let i = 0;
11
+ while (i + 8 <= data.length) {
12
+ if (data[i] !== 181 || data[i + 1] !== 98) {
13
+ i++;
14
+ continue;
15
+ }
16
+ const len = data[i + 4] | data[i + 5] << 8;
17
+ const end = i + 6 + len + 2;
18
+ if (end > data.length) break;
19
+ let ckA = 0;
20
+ let ckB = 0;
21
+ for (let j = i + 2; j < i + 6 + len; j++) {
22
+ ckA = ckA + data[j] & 255;
23
+ ckB = ckB + ckA & 255;
24
+ }
25
+ if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
26
+ stats.badChecksums++;
27
+ i++;
28
+ continue;
29
+ }
30
+ yield {
31
+ msgClass: data[i + 2],
32
+ msgId: data[i + 3],
33
+ payload: data.subarray(i + 6, i + 6 + len),
34
+ payloadStart: i + 6
35
+ };
36
+ i = end;
37
+ }
38
+ }
39
+
40
+ // src/ubx/nav.ts
41
+ var PREAMB_CNAV = 139;
42
+ function readLnavSubframe(view, f) {
43
+ const p = f.payload;
44
+ if (p.length < 8) return null;
45
+ const gnssId = p[0];
46
+ const svId = p[1];
47
+ let prn;
48
+ if (gnssId === 0 && svId >= 1 && svId <= 32) {
49
+ prn = `G${String(svId).padStart(2, "0")}`;
50
+ } else if (gnssId === 5 && svId >= 1 && svId <= 10) {
51
+ if (p.length === 44) return null;
52
+ prn = `J${String(svId).padStart(2, "0")}`;
53
+ } else {
54
+ return null;
55
+ }
56
+ if (p.length < 8 + 40) return null;
57
+ const base = f.payloadStart + 8;
58
+ if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) return null;
59
+ const buff = new Uint8Array(30);
60
+ for (let k = 0; k < 10; k++) {
61
+ const dwrd = view.getUint32(base + 4 * k, true);
62
+ setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
63
+ }
64
+ return { prn, gnssId, svId, buff, id: getBitU(buff, 43, 3) };
65
+ }
66
+ function parseUbxNav(data, opts = {}) {
67
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
68
+ const ephemerides = [];
69
+ let badParity = 0;
70
+ let refWeek = opts.refWeek;
71
+ if (refWeek === void 0) {
72
+ for (const f of ubxFrames(data)) {
73
+ if (f.msgClass === 2 && f.msgId === 21 && f.payload.length >= 16) {
74
+ const week = view.getUint16(f.payloadStart + 8, true);
75
+ if (week > 0) {
76
+ refWeek = week;
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ }
82
+ if (refWeek === void 0) return { ephemerides, badParity };
83
+ const subframes = /* @__PURE__ */ new Map();
84
+ const last = /* @__PURE__ */ new Map();
85
+ for (const f of ubxFrames(data)) {
86
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
87
+ const lnav = readLnavSubframe(view, f);
88
+ if (!lnav) continue;
89
+ const { prn, gnssId, svId, buff, id } = lnav;
90
+ if (id < 1 || id > 5) {
91
+ badParity++;
92
+ continue;
93
+ }
94
+ if (id > 3) continue;
95
+ const key = gnssId * 256 + svId;
96
+ let sf = subframes.get(key);
97
+ if (!sf) {
98
+ sf = { buf: new Uint8Array(90), have: 0 };
99
+ subframes.set(key, sf);
100
+ }
101
+ sf.buf.set(buff, (id - 1) * 30);
102
+ sf.have |= 1 << id - 1;
103
+ if (id !== 3 || sf.have !== 7) continue;
104
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
105
+ if (!eph) {
106
+ badParity++;
107
+ continue;
108
+ }
109
+ const prev = last.get(prn);
110
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
111
+ continue;
112
+ }
113
+ last.set(prn, eph);
114
+ ephemerides.push(eph);
115
+ }
116
+ return { ephemerides, badParity };
117
+ }
118
+
119
+ // src/ubx/iono.ts
120
+ function parseUbxIonoUtc(data) {
121
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
122
+ const ionoCorrections = {};
123
+ let leapSeconds = null;
124
+ for (const f of ubxFrames(data)) {
125
+ if (f.msgClass !== 2 || f.msgId !== 19) continue;
126
+ const sf = readLnavSubframe(view, f);
127
+ if (!sf || sf.gnssId !== 0 || sf.id !== 4) continue;
128
+ const b = sf.buff;
129
+ if (getBitU(b, 48, 2) !== 1 || getBitU(b, 50, 6) !== 56) continue;
130
+ ionoCorrections["GPSA"] = [
131
+ getBitS(b, 56, 8) * 2 ** -30,
132
+ getBitS(b, 64, 8) * 2 ** -27,
133
+ getBitS(b, 72, 8) * 2 ** -24,
134
+ getBitS(b, 80, 8) * 2 ** -24
135
+ ];
136
+ ionoCorrections["GPSB"] = [
137
+ getBitS(b, 88, 8) * 2 ** 11,
138
+ getBitS(b, 96, 8) * 2 ** 14,
139
+ getBitS(b, 104, 8) * 2 ** 16,
140
+ getBitS(b, 112, 8) * 2 ** 16
141
+ ];
142
+ leapSeconds = getBitS(b, 192, 8);
143
+ }
144
+ return { ionoCorrections, leapSeconds };
145
+ }
146
+
147
+ // src/ubx/index.ts
148
+ var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
149
+ var MS_PER_WEEK = 7 * 864e5;
150
+ var SIGNALS = {
151
+ 0: { 0: ["G", "1C"], 3: ["G", "2X"], 4: ["G", "2X"] },
152
+ // GPS L1C/A, L2CL, L2CM
153
+ 1: { 0: ["S", "1C"] },
154
+ // SBAS L1C/A
155
+ 2: { 0: ["E", "1X"], 1: ["E", "1X"], 5: ["E", "7X"], 6: ["E", "7X"] },
156
+ // E1C/B, E5bI/Q
157
+ 3: { 0: ["C", "2I"], 1: ["C", "2I"], 2: ["C", "7I"], 3: ["C", "7I"] },
158
+ // B1I D1/D2, B2I D1/D2
159
+ 5: { 0: ["J", "1C"], 4: ["J", "2X"], 5: ["J", "2X"] },
160
+ // QZSS L1C/A, L2CM/CL
161
+ 6: { 0: ["R", "1C"], 2: ["R", "2C"] }
162
+ // GLONASS L1OF, L2OF
163
+ };
164
+ function prnFor(gnssId, svId) {
165
+ const two = (n) => String(n).padStart(2, "0");
166
+ switch (gnssId) {
167
+ case 0:
168
+ return svId >= 1 && svId <= 32 ? `G${two(svId)}` : null;
169
+ case 1:
170
+ return svId >= 120 && svId <= 158 ? `S${two(svId - 100)}` : null;
171
+ case 2:
172
+ return svId >= 1 && svId <= 36 ? `E${two(svId)}` : null;
173
+ case 3:
174
+ return svId >= 1 && svId <= 63 ? `C${two(svId)}` : null;
175
+ case 5:
176
+ return svId >= 1 && svId <= 10 ? `J${two(svId)}` : null;
177
+ case 6:
178
+ return svId >= 1 && svId <= 32 ? `R${two(svId)}` : null;
179
+ default:
180
+ return null;
181
+ }
182
+ }
183
+ function parseUbxRawx(data) {
184
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
185
+ const epochs = [];
186
+ const messageCounts = {};
187
+ const obsCodes = {};
188
+ const stats = { badChecksums: 0 };
189
+ for (const frame of ubxFrames(data, stats)) {
190
+ const { msgClass: cls, msgId: id } = frame;
191
+ const len = frame.payload.length;
192
+ const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
193
+ messageCounts[key] = (messageCounts[key] ?? 0) + 1;
194
+ if (cls === 2 && id === 21 && len >= 16) {
195
+ const p = frame.payloadStart;
196
+ const rcvTow = view.getFloat64(p, true);
197
+ const week = view.getUint16(p + 8, true);
198
+ const leapS = view.getInt8(p + 10);
199
+ const numMeas = data[p + 11];
200
+ const recStat = data[p + 12];
201
+ const leapValid = (recStat & 1) !== 0;
202
+ const meas = [];
203
+ for (let k = 0; k < numMeas; k++) {
204
+ const off = p + 16 + 32 * k;
205
+ if (off + 32 > p + len) break;
206
+ const gnssId = data[off + 20];
207
+ const svId = data[off + 21];
208
+ const sigId = data[off + 22];
209
+ const trkStat = data[off + 30];
210
+ const prn = prnFor(gnssId, svId);
211
+ const sig = SIGNALS[gnssId]?.[sigId];
212
+ if (!prn || !sig) continue;
213
+ const prValid = (trkStat & 1) !== 0;
214
+ const cpValid = (trkStat & 2) !== 0;
215
+ const cp = view.getFloat64(off + 8, true);
216
+ meas.push({
217
+ prn,
218
+ code: sig[1],
219
+ pr: prValid ? view.getFloat64(off, true) : null,
220
+ cp: cpValid && cp !== 0 ? cp : null,
221
+ doppler: view.getFloat32(off + 16, true),
222
+ cn0: data[off + 26],
223
+ halfCycleAmbiguous: (trkStat & 4) !== 0,
224
+ lockTimeMs: view.getUint16(off + 24, true)
225
+ });
226
+ const sys = sig[0];
227
+ const codes = obsCodes[sys] ??= [];
228
+ if (!codes.includes(sig[1])) codes.push(sig[1]);
229
+ }
230
+ epochs.push({
231
+ timeMs: GPS_EPOCH_MS + week * MS_PER_WEEK + Math.round(rcvTow * 1e3),
232
+ leapS: leapValid ? leapS : null,
233
+ meas
234
+ });
235
+ }
236
+ }
237
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
238
+ }
239
+
240
+ export {
241
+ ubxFrames,
242
+ parseUbxNav,
243
+ parseUbxIonoUtc,
244
+ parseUbxRawx
245
+ };