gnss-js 1.14.1 → 1.16.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.
@@ -0,0 +1,146 @@
1
+ // src/navbits/index.ts
2
+ var GPS_PI = 3.1415926535898;
3
+ var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
4
+ var SEC_PER_WEEK = 7 * 86400;
5
+ function getBitU(buff, pos, len) {
6
+ let bits = 0;
7
+ for (let i = pos; i < pos + len; i++) {
8
+ bits = bits * 2 + (buff[i >> 3] >> 7 - (i & 7) & 1);
9
+ }
10
+ return bits;
11
+ }
12
+ function getBitS(buff, pos, len) {
13
+ const bits = getBitU(buff, pos, len);
14
+ if (len <= 0 || bits < 2 ** (len - 1)) return bits;
15
+ return bits - 2 ** len;
16
+ }
17
+ function setBitU(buff, pos, len, data) {
18
+ let mask = 2 ** (len - 1);
19
+ for (let i = pos; i < pos + len; i++, mask /= 2) {
20
+ if (data >= mask) {
21
+ buff[i >> 3] |= 1 << 7 - (i & 7);
22
+ data -= mask;
23
+ } else {
24
+ buff[i >> 3] &= ~(1 << 7 - (i & 7));
25
+ }
26
+ }
27
+ }
28
+ function decodeGpsLnavFrame(subframes, opts = {}) {
29
+ if (subframes.length < 90) return null;
30
+ const b = subframes;
31
+ let i = 24;
32
+ const tow1 = getBitU(b, i, 17) * 6;
33
+ i += 17 + 2;
34
+ const id1 = getBitU(b, i, 3);
35
+ i += 3 + 2;
36
+ const week10 = getBitU(b, i, 10);
37
+ i += 10;
38
+ i += 2;
39
+ i += 4;
40
+ const svHealth = getBitU(b, i, 6);
41
+ i += 6;
42
+ const iodc0 = getBitU(b, i, 2);
43
+ i += 2;
44
+ i += 1 + 87;
45
+ const tgdRaw = getBitS(b, i, 8);
46
+ i += 8;
47
+ const iodc1 = getBitU(b, i, 8);
48
+ i += 8;
49
+ const tocSec = getBitU(b, i, 16) * 16;
50
+ i += 16;
51
+ const af2 = getBitS(b, i, 8) * 2 ** -55;
52
+ i += 8;
53
+ const af1 = getBitS(b, i, 16) * 2 ** -43;
54
+ i += 16;
55
+ const af0 = getBitS(b, i, 22) * 2 ** -31;
56
+ i = 240 + 24;
57
+ i += 17 + 2;
58
+ const id2 = getBitU(b, i, 3);
59
+ i += 3 + 2;
60
+ const iode = getBitU(b, i, 8);
61
+ i += 8;
62
+ const crs = getBitS(b, i, 16) * 2 ** -5;
63
+ i += 16;
64
+ const deltaN = getBitS(b, i, 16) * 2 ** -43 * GPS_PI;
65
+ i += 16;
66
+ const m0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
67
+ i += 32;
68
+ const cuc = getBitS(b, i, 16) * 2 ** -29;
69
+ i += 16;
70
+ const e = getBitU(b, i, 32) * 2 ** -33;
71
+ i += 32;
72
+ const cus = getBitS(b, i, 16) * 2 ** -29;
73
+ i += 16;
74
+ const sqrtA = getBitU(b, i, 32) * 2 ** -19;
75
+ i += 32;
76
+ const toes = getBitU(b, i, 16) * 16;
77
+ i = 480 + 24;
78
+ i += 17 + 2;
79
+ const id3 = getBitU(b, i, 3);
80
+ i += 3 + 2;
81
+ const cic = getBitS(b, i, 16) * 2 ** -29;
82
+ i += 16;
83
+ const omega0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
84
+ i += 32;
85
+ const cis = getBitS(b, i, 16) * 2 ** -29;
86
+ i += 16;
87
+ const i0 = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
88
+ i += 32;
89
+ const crc = getBitS(b, i, 16) * 2 ** -5;
90
+ i += 16;
91
+ const omega = getBitS(b, i, 32) * 2 ** -31 * GPS_PI;
92
+ i += 32;
93
+ const omegaDot = getBitS(b, i, 24) * 2 ** -43 * GPS_PI;
94
+ i += 24;
95
+ const iode3 = getBitU(b, i, 8);
96
+ i += 8;
97
+ const idot = getBitS(b, i, 14) * 2 ** -43 * GPS_PI;
98
+ const iodc = iodc0 * 256 + iodc1;
99
+ if (id1 !== 1 || id2 !== 2 || id3 !== 3) return null;
100
+ if (iode3 !== iode || iode !== (iodc & 255)) return null;
101
+ const ref = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS) / 1e3 / SEC_PER_WEEK);
102
+ let week = week10 + 1024 * Math.round((ref - week10) / 1024);
103
+ if (toes < tow1 - 302400) week++;
104
+ else if (toes > tow1 + 302400) week--;
105
+ const prn = opts.prn ?? "G00";
106
+ const tocDate = new Date(
107
+ GPS_EPOCH_MS + (week * SEC_PER_WEEK + tocSec) * 1e3
108
+ );
109
+ return {
110
+ system: prn[0] === "J" ? "J" : "G",
111
+ prn,
112
+ tocDate,
113
+ // Same seconds-of-week convention as parseNavFile (rinex/nav.ts).
114
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK,
115
+ af0,
116
+ af1,
117
+ af2,
118
+ iode,
119
+ crs,
120
+ deltaN,
121
+ m0,
122
+ cuc,
123
+ e,
124
+ cus,
125
+ sqrtA,
126
+ toe: toes,
127
+ cic,
128
+ omega0,
129
+ cis,
130
+ i0,
131
+ crc,
132
+ omega,
133
+ omegaDot,
134
+ idot,
135
+ week,
136
+ svHealth,
137
+ tgd: tgdRaw === -128 ? 0 : tgdRaw * 2 ** -31
138
+ // IS-GPS-200: -128 reserved
139
+ };
140
+ }
141
+
142
+ export {
143
+ getBitU,
144
+ setBitU,
145
+ decodeGpsLnavFrame
146
+ };
@@ -1,3 +1,115 @@
1
+ import {
2
+ decodeGpsLnavFrame,
3
+ getBitU,
4
+ setBitU
5
+ } from "./chunk-4LVD4DYL.js";
6
+
7
+ // src/ubx/frame.ts
8
+ function* ubxFrames(data, stats = { badChecksums: 0 }) {
9
+ let i = 0;
10
+ while (i + 8 <= data.length) {
11
+ if (data[i] !== 181 || data[i + 1] !== 98) {
12
+ i++;
13
+ continue;
14
+ }
15
+ const len = data[i + 4] | data[i + 5] << 8;
16
+ const end = i + 6 + len + 2;
17
+ if (end > data.length) break;
18
+ let ckA = 0;
19
+ let ckB = 0;
20
+ for (let j = i + 2; j < i + 6 + len; j++) {
21
+ ckA = ckA + data[j] & 255;
22
+ ckB = ckB + ckA & 255;
23
+ }
24
+ if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
25
+ stats.badChecksums++;
26
+ i++;
27
+ continue;
28
+ }
29
+ yield {
30
+ msgClass: data[i + 2],
31
+ msgId: data[i + 3],
32
+ payload: data.subarray(i + 6, i + 6 + len),
33
+ payloadStart: i + 6
34
+ };
35
+ i = end;
36
+ }
37
+ }
38
+
39
+ // src/ubx/nav.ts
40
+ var PREAMB_CNAV = 139;
41
+ function parseUbxNav(data, opts = {}) {
42
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
43
+ const ephemerides = [];
44
+ let badParity = 0;
45
+ let refWeek = opts.refWeek;
46
+ if (refWeek === void 0) {
47
+ for (const f of ubxFrames(data)) {
48
+ if (f.msgClass === 2 && f.msgId === 21 && f.payload.length >= 16) {
49
+ const week = view.getUint16(f.payloadStart + 8, true);
50
+ if (week > 0) {
51
+ refWeek = week;
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ if (refWeek === void 0) return { ephemerides, badParity };
58
+ const subframes = /* @__PURE__ */ new Map();
59
+ const last = /* @__PURE__ */ new Map();
60
+ for (const f of ubxFrames(data)) {
61
+ 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);
84
+ if (id < 1 || id > 5) {
85
+ badParity++;
86
+ continue;
87
+ }
88
+ if (id > 3) continue;
89
+ const key = gnssId * 256 + svId;
90
+ let sf = subframes.get(key);
91
+ if (!sf) {
92
+ sf = { buf: new Uint8Array(90), have: 0 };
93
+ subframes.set(key, sf);
94
+ }
95
+ sf.buf.set(buff, (id - 1) * 30);
96
+ sf.have |= 1 << id - 1;
97
+ if (id !== 3 || sf.have !== 7) continue;
98
+ const eph = decodeGpsLnavFrame(sf.buf, { prn, refWeek });
99
+ if (!eph) {
100
+ badParity++;
101
+ continue;
102
+ }
103
+ const prev = last.get(prn);
104
+ if (prev && prev.iode === eph.iode && prev.tocDate.getTime() === eph.tocDate.getTime()) {
105
+ continue;
106
+ }
107
+ last.set(prn, eph);
108
+ ephemerides.push(eph);
109
+ }
110
+ return { ephemerides, badParity };
111
+ }
112
+
1
113
  // src/ubx/index.ts
2
114
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
3
115
  var MS_PER_WEEK = 7 * 864e5;
@@ -39,33 +151,14 @@ function parseUbxRawx(data) {
39
151
  const epochs = [];
40
152
  const messageCounts = {};
41
153
  const obsCodes = {};
42
- let badChecksums = 0;
43
- let i = 0;
44
- while (i + 8 <= data.length) {
45
- if (data[i] !== 181 || data[i + 1] !== 98) {
46
- i++;
47
- continue;
48
- }
49
- const cls = data[i + 2];
50
- const id = data[i + 3];
51
- const len = data[i + 4] | data[i + 5] << 8;
52
- const end = i + 6 + len + 2;
53
- if (end > data.length) break;
54
- let ckA = 0;
55
- let ckB = 0;
56
- for (let j = i + 2; j < i + 6 + len; j++) {
57
- ckA = ckA + data[j] & 255;
58
- ckB = ckB + ckA & 255;
59
- }
60
- if (ckA !== data[i + 6 + len] || ckB !== data[i + 7 + len]) {
61
- badChecksums++;
62
- i++;
63
- continue;
64
- }
154
+ const stats = { badChecksums: 0 };
155
+ for (const frame of ubxFrames(data, stats)) {
156
+ const { msgClass: cls, msgId: id } = frame;
157
+ const len = frame.payload.length;
65
158
  const key = `${cls.toString(16).padStart(2, "0")}-${id.toString(16).padStart(2, "0")}`;
66
159
  messageCounts[key] = (messageCounts[key] ?? 0) + 1;
67
160
  if (cls === 2 && id === 21 && len >= 16) {
68
- const p = i + 6;
161
+ const p = frame.payloadStart;
69
162
  const rcvTow = view.getFloat64(p, true);
70
163
  const week = view.getUint16(p + 8, true);
71
164
  const leapS = view.getInt8(p + 10);
@@ -106,11 +199,12 @@ function parseUbxRawx(data) {
106
199
  meas
107
200
  });
108
201
  }
109
- i = end;
110
202
  }
111
- return { epochs, messageCounts, obsCodes, badChecksums };
203
+ return { epochs, messageCounts, obsCodes, badChecksums: stats.badChecksums };
112
204
  }
113
205
 
114
206
  export {
207
+ ubxFrames,
208
+ parseUbxNav,
115
209
  parseUbxRawx
116
210
  };
@@ -1,10 +1,143 @@
1
- // src/novatel/index.ts
1
+ import {
2
+ decodeGpsLnavFrame
3
+ } from "./chunk-4LVD4DYL.js";
4
+ import {
5
+ getUtcDate
6
+ } from "./chunk-HVXYFUCB.js";
7
+
8
+ // src/novatel/frame.ts
9
+ var OEM4_HLEN = 28;
10
+ function crc32(data, start, len) {
11
+ let crc = 0;
12
+ for (let i = start; i < start + len; i++) {
13
+ crc ^= data[i];
14
+ for (let j = 0; j < 8; j++) {
15
+ crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
16
+ }
17
+ }
18
+ return crc >>> 0;
19
+ }
20
+ function* oem4Frames(data, view, stats) {
21
+ let i = 0;
22
+ while (i + OEM4_HLEN + 4 <= data.length) {
23
+ if (data[i] !== 170 || data[i + 1] !== 68 || data[i + 2] !== 18) {
24
+ i++;
25
+ continue;
26
+ }
27
+ const hlen = data[i + 3];
28
+ const msgLen = view.getUint16(i + 8, true);
29
+ const total = hlen + msgLen + 4;
30
+ if (hlen < OEM4_HLEN || i + total > data.length) {
31
+ i++;
32
+ continue;
33
+ }
34
+ if (crc32(data, i, hlen + msgLen) !== view.getUint32(i + hlen + msgLen, true)) {
35
+ stats.badCrc++;
36
+ i++;
37
+ continue;
38
+ }
39
+ yield {
40
+ id: view.getUint16(i + 4, true),
41
+ start: i,
42
+ payload: i + hlen,
43
+ msgLen,
44
+ binary: (data[i + 6] >> 4 & 3) === 0,
45
+ week: view.getUint16(i + 14, true),
46
+ towMs: view.getUint32(i + 16, true)
47
+ };
48
+ i += total;
49
+ }
50
+ }
51
+
52
+ // src/novatel/nav.ts
53
+ var ID_RAWEPHEM = 41;
54
+ var ID_GLOEPHEMERIS = 723;
2
55
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
56
+ var SEC_PER_WEEK = 7 * 86400;
57
+ var SEC_PER_DAY = 86400;
58
+ function decodeRawEphem(data, view, p) {
59
+ const prn = view.getUint32(p, true);
60
+ if (prn < 1 || prn > 32) return null;
61
+ const refWeek = view.getUint32(p + 4, true);
62
+ return decodeGpsLnavFrame(data.subarray(p + 12, p + 102), {
63
+ prn: `G${String(prn).padStart(2, "0")}`,
64
+ refWeek
65
+ });
66
+ }
67
+ function decodeGloEphemeris(view, p) {
68
+ const slot = view.getUint16(p, true) - 37;
69
+ if (slot < 1 || slot > 27) return null;
70
+ const freqNum = view.getUint16(p + 2, true) - 7;
71
+ const week = view.getUint16(p + 6, true);
72
+ const tow = Math.floor(view.getUint32(p + 8, true) / 1e3 + 0.5);
73
+ const toff = view.getUint32(p + 12, true);
74
+ const health = view.getUint32(p + 24, true) < 4 ? 0 : 1;
75
+ let tof = view.getUint32(p + 124, true) - toff;
76
+ tof += Math.floor(tow / SEC_PER_DAY) * SEC_PER_DAY;
77
+ if (tof < tow - 43200) tof += SEC_PER_DAY;
78
+ else if (tof > tow + 43200) tof -= SEC_PER_DAY;
79
+ const toeGpsMs = GPS_EPOCH_MS + (week * SEC_PER_WEEK + tow) * 1e3;
80
+ const tofGpsMs = GPS_EPOCH_MS + (week * SEC_PER_WEEK + tof) * 1e3;
81
+ const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS) / 1e3;
82
+ return {
83
+ system: "R",
84
+ prn: `R${String(slot).padStart(2, "0")}`,
85
+ tocDate: getUtcDate(new Date(toeGpsMs)),
86
+ tauN: -view.getFloat64(p + 100, true),
87
+ // RINEX stores −τ_n
88
+ gammaN: view.getFloat64(p + 116, true),
89
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK + SEC_PER_WEEK) % SEC_PER_WEEK,
90
+ x: view.getFloat64(p + 28, true) / 1e3,
91
+ y: view.getFloat64(p + 36, true) / 1e3,
92
+ z: view.getFloat64(p + 44, true) / 1e3,
93
+ xDot: view.getFloat64(p + 52, true) / 1e3,
94
+ yDot: view.getFloat64(p + 60, true) / 1e3,
95
+ zDot: view.getFloat64(p + 68, true) / 1e3,
96
+ xAcc: view.getFloat64(p + 76, true) / 1e3,
97
+ yAcc: view.getFloat64(p + 84, true) / 1e3,
98
+ zAcc: view.getFloat64(p + 92, true) / 1e3,
99
+ health,
100
+ freqNum
101
+ };
102
+ }
103
+ function parseNovatelNav(data) {
104
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
105
+ const stats = { badCrc: 0 };
106
+ const ephemerides = [];
107
+ const lastGps = /* @__PURE__ */ new Map();
108
+ const lastGlo = /* @__PURE__ */ new Map();
109
+ for (const frame of oem4Frames(data, view, stats)) {
110
+ if (!frame.binary) continue;
111
+ if (frame.id === ID_RAWEPHEM && frame.msgLen >= 102) {
112
+ 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;
117
+ }
118
+ lastGps.set(eph.prn, eph);
119
+ ephemerides.push(eph);
120
+ } else if (frame.id === ID_GLOEPHEMERIS && frame.msgLen >= 144) {
121
+ const eph = decodeGloEphemeris(view, frame.payload);
122
+ if (!eph) continue;
123
+ const prev = lastGlo.get(eph.prn);
124
+ if (prev && Math.abs(prev.tocDate.getTime() - eph.tocDate.getTime()) < 1e3 && prev.health === eph.health) {
125
+ continue;
126
+ }
127
+ lastGlo.set(eph.prn, eph);
128
+ ephemerides.push(eph);
129
+ }
130
+ }
131
+ return { ephemerides, badCrc: stats.badCrc };
132
+ }
133
+
134
+ // src/novatel/index.ts
135
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
3
136
  var MS_PER_WEEK = 7 * 864e5;
4
137
  var HDR = 28;
5
138
  var ID_RANGE = 43;
6
139
  var ID_RANGECMP = 140;
7
- var ID_GLOEPHEMERIS = 723;
140
+ var ID_GLOEPHEMERIS2 = 723;
8
141
  var C_LIGHT = 299792458;
9
142
  var ADR_ROLL = 8388608;
10
143
  function carrierFreq(sys, code, gloK) {
@@ -32,16 +165,6 @@ function carrierFreq(sys, code, gloK) {
32
165
  function exsign(v, bits) {
33
166
  return v & 1 << bits - 1 ? v - 2 ** bits : v;
34
167
  }
35
- function crc32(data, start, len) {
36
- let crc = 0;
37
- for (let i = start; i < start + len; i++) {
38
- crc ^= data[i];
39
- for (let j = 0; j < 8; j++) {
40
- crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
41
- }
42
- }
43
- return crc >>> 0;
44
- }
45
168
  var SIGNALS = {
46
169
  0: {
47
170
  0: ["G", "1C"],
@@ -95,7 +218,7 @@ function parseNovatelRange(data) {
95
218
  const hl = data[j + 3];
96
219
  const id = view.getUint16(j + 4, true);
97
220
  const ln = view.getUint16(j + 8, true);
98
- if (id !== ID_GLOEPHEMERIS || hl < HDR || j + hl + ln + 4 > data.length)
221
+ if (id !== ID_GLOEPHEMERIS2 || hl < HDR || j + hl + ln + 4 > data.length)
99
222
  continue;
100
223
  if (crc32(data, j, hl + ln) !== view.getUint32(j + hl + ln, true)) continue;
101
224
  const slot = view.getUint16(j + hl, true) - 37;
@@ -179,7 +302,7 @@ function parseNovatelRange(data) {
179
302
  if (!codes.includes(code)) codes.push(code);
180
303
  }
181
304
  epochs.push({
182
- timeMs: GPS_EPOCH_MS + week * MS_PER_WEEK + towMs,
305
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + towMs,
183
306
  meas
184
307
  });
185
308
  }
@@ -231,7 +354,7 @@ function parseNovatelRange(data) {
231
354
  if (!codes.includes(code)) codes.push(code);
232
355
  }
233
356
  epochs.push({
234
- timeMs: GPS_EPOCH_MS + week * MS_PER_WEEK + towMs,
357
+ timeMs: GPS_EPOCH_MS2 + week * MS_PER_WEEK + towMs,
235
358
  meas
236
359
  });
237
360
  }
@@ -242,5 +365,9 @@ function parseNovatelRange(data) {
242
365
  }
243
366
 
244
367
  export {
368
+ OEM4_HLEN,
369
+ crc32,
370
+ oem4Frames,
371
+ parseNovatelNav,
245
372
  parseNovatelRange
246
373
  };