gnss-js 1.16.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,6 +1,6 @@
1
1
  import {
2
2
  decodeGpsLnavFrame
3
- } from "./chunk-4LVD4DYL.js";
3
+ } from "./chunk-REYOYF7O.js";
4
4
  import {
5
5
  getUtcDate
6
6
  } from "./chunk-HVXYFUCB.js";
@@ -50,11 +50,26 @@ function* oem4Frames(data, view, stats) {
50
50
  }
51
51
 
52
52
  // src/novatel/nav.ts
53
+ var ID_IONUTC = 8;
53
54
  var ID_RAWEPHEM = 41;
54
55
  var ID_GLOEPHEMERIS = 723;
56
+ var ID_GALEPHEMERIS = 1122;
57
+ var ID_QZSSEPHEMERIS = 1336;
58
+ var ID_BDSEPHEMERIS = 1696;
55
59
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
60
+ var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
56
61
  var SEC_PER_WEEK = 7 * 86400;
57
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
+ }
58
73
  function decodeRawEphem(data, view, p) {
59
74
  const prn = view.getUint32(p, true);
60
75
  if (prn < 1 || prn > 32) return null;
@@ -100,23 +115,184 @@ function decodeGloEphemeris(view, p) {
100
115
  freqNum
101
116
  };
102
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
+ }
103
252
  function parseNovatelNav(data) {
104
253
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
105
254
  const stats = { badCrc: 0 };
106
255
  const ephemerides = [];
107
- const lastGps = /* @__PURE__ */ new Map();
256
+ const ionoCorrections = {};
257
+ let leapSeconds = null;
258
+ const lastKep = /* @__PURE__ */ new Map();
108
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
+ };
109
268
  for (const frame of oem4Frames(data, view, stats)) {
110
269
  if (!frame.binary) continue;
111
270
  if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
112
271
  const eph = decodeRawEphem(data, view, frame.payload);
113
- if (!eph) continue;
114
- const prev = lastGps.get(eph.prn);
115
- if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
116
- 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));
117
292
  }
118
- lastGps.set(eph.prn, eph);
119
- ephemerides.push(eph);
293
+ ionoCorrections["GPSA"] = alpha;
294
+ ionoCorrections["GPSB"] = beta;
295
+ leapSeconds = view.getInt32(p + 96, true);
120
296
  } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
121
297
  const eph = decodeGloEphemeris(view, frame.payload);
122
298
  if (!eph) continue;
@@ -128,12 +304,12 @@ function parseNovatelNav(data) {
128
304
  ephemerides.push(eph);
129
305
  }
130
306
  }
131
- return { ephemerides, badCrc: stats.badCrc };
307
+ return { ephemerides, ionoCorrections, leapSeconds, badCrc: stats.badCrc };
132
308
  }
133
309
 
134
310
  // src/novatel/index.ts
135
311
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
136
- var MS_PER_WEEK = 7 * 864e5;
312
+ var MS_PER_WEEK2 = 7 * 864e5;
137
313
  var HDR = 28;
138
314
  var ID_RANGE = 43;
139
315
  var ID_RANGECMP = 140;
@@ -302,7 +478,7 @@ function parseNovatelRange(data) {
302
478
  if (!codes.includes(code)) codes.push(code);
303
479
  }
304
480
  epochs.push({
305
- timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + towMs,
481
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + towMs,
306
482
  meas
307
483
  });
308
484
  }
@@ -354,7 +530,7 @@ function parseNovatelRange(data) {
354
530
  if (!codes.includes(code)) codes.push(code);
355
531
  }
356
532
  epochs.push({
357
- timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + towMs,
533
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK2 + towMs,
358
534
  meas
359
535
  });
360
536
  }
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  decodeGpsLnavFrame,
3
+ getBitS,
3
4
  getBitU,
4
5
  setBitU
5
- } from "./chunk-4LVD4DYL.js";
6
+ } from "./chunk-REYOYF7O.js";
6
7
 
7
8
  // src/ubx/frame.ts
8
9
  function* ubxFrames(data, stats = { badChecksums: 0 }) {
@@ -38,6 +39,30 @@ function* ubxFrames(data, stats = { badChecksums: 0 }) {
38
39
 
39
40
  // src/ubx/nav.ts
40
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
+ }
41
66
  function parseUbxNav(data, opts = {}) {
42
67
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
43
68
  const ephemerides = [];
@@ -59,28 +84,9 @@ function parseUbxNav(data, opts = {}) {
59
84
  const last = /* @__PURE__ */ new Map();
60
85
  for (const f of ubxFrames(data)) {
61
86
  if (f.msgClass !== 2 || f.msgId !== 19) continue;
62
- const p = f.payload;
63
- if (p.length < 8) continue;
64
- const gnssId = p[0];
65
- const svId = p[1];
66
- let prn;
67
- if (gnssId === 0 && svId >= 1 && svId <= 32) {
68
- prn = `G${String(svId).padStart(2, "0")}`;
69
- } else if (gnssId === 5 && svId >= 1 && svId <= 10) {
70
- if (p.length === 44) continue;
71
- prn = `J${String(svId).padStart(2, "0")}`;
72
- } else {
73
- continue;
74
- }
75
- if (p.length < 8 + 40) continue;
76
- const base = f.payloadStart + 8;
77
- if (view.getUint32(base, true) >>> 24 === PREAMB_CNAV) continue;
78
- const buff = new Uint8Array(30);
79
- for (let k = 0; k < 10; k++) {
80
- const dwrd = view.getUint32(base + 4 * k, true);
81
- setBitU(buff, 24 * k, 24, dwrd >>> 6 & 16777215);
82
- }
83
- const id = getBitU(buff, 43, 3);
87
+ const lnav = readLnavSubframe(view, f);
88
+ if (!lnav) continue;
89
+ const { prn, gnssId, svId, buff, id } = lnav;
84
90
  if (id < 1 || id > 5) {
85
91
  badParity++;
86
92
  continue;
@@ -110,6 +116,34 @@ function parseUbxNav(data, opts = {}) {
110
116
  return { ephemerides, badParity };
111
117
  }
112
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
+
113
147
  // src/ubx/index.ts
114
148
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
115
149
  var MS_PER_WEEK = 7 * 864e5;
@@ -206,5 +240,6 @@ function parseUbxRawx(data) {
206
240
  export {
207
241
  ubxFrames,
208
242
  parseUbxNav,
243
+ parseUbxIonoUtc,
209
244
  parseUbxRawx
210
245
  };
@@ -141,6 +141,7 @@ function decodeGpsLnavFrame(subframes, opts = {}) {
141
141
 
142
142
  export {
143
143
  getBitU,
144
+ getBitS,
144
145
  setBitU,
145
146
  decodeGpsLnavFrame
146
147
  };
@@ -371,6 +371,39 @@ function parseSbfAlmanac(data) {
371
371
  return { almanacs, badCrc };
372
372
  }
373
373
 
374
+ // src/sbf/iono.ts
375
+ var F4_DNU2 = -2e10;
376
+ function parseSbfIonoUtc(data) {
377
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
378
+ const ionoCorrections = {};
379
+ let leapSeconds = null;
380
+ const f4s = (b, n) => {
381
+ const out = [];
382
+ for (let k = 0; k < n; k++) {
383
+ const v = view.getFloat32(b + 4 * k, true);
384
+ if (v === F4_DNU2) return null;
385
+ out.push(v);
386
+ }
387
+ return out;
388
+ };
389
+ scanSbfFrames(data, view, (id, b, len) => {
390
+ if ((id === 5893 || id === 4120) && len >= 48) {
391
+ const alpha = f4s(b + 16, 4);
392
+ const beta = f4s(b + 32, 4);
393
+ if (!alpha || !beta) return;
394
+ const sys = id === 5893 ? "GPS" : "BDS";
395
+ ionoCorrections[`${sys}A`] = alpha;
396
+ ionoCorrections[`${sys}B`] = beta;
397
+ } else if (id === 4030 && len >= 28) {
398
+ const ai = f4s(b + 16, 3);
399
+ if (ai) ionoCorrections["GAL"] = ai;
400
+ } else if (id === 5894 && len >= 37) {
401
+ leapSeconds = view.getInt8(b + 33);
402
+ }
403
+ });
404
+ return { ionoCorrections, leapSeconds };
405
+ }
406
+
374
407
  // src/sbf/index.ts
375
408
  var CLIGHT = 299792458;
376
409
  var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
@@ -1044,5 +1077,6 @@ export {
1044
1077
  scanSbfFrames,
1045
1078
  parseSbfNav,
1046
1079
  parseSbfAlmanac,
1080
+ parseSbfIonoUtc,
1047
1081
  parseSbfMeas
1048
1082
  };