gnss-js 1.40.0 → 1.42.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,7 @@
1
+ import {
2
+ getGpsLeap
3
+ } from "./chunk-HVXYFUCB.js";
4
+
1
5
  // src/trimble/frame.ts
2
6
  var STX = 2;
3
7
  var ETX = 3;
@@ -50,6 +54,8 @@ var gpsMs = (week, sec) => GPS_EPOCH_MS + week * MS_PER_WEEK + sec * 1e3;
50
54
  var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK;
51
55
  var SUB_GPS_EPHEMERIS = 1;
52
56
  var SUB_ION_UTC = 3;
57
+ var SUB_GLO_EPHEMERIS = 9;
58
+ var SUB_GAL_EPHEMERIS = 11;
53
59
  var SUB_BDS_EPHEMERIS = 21;
54
60
  function readKepler(view, base) {
55
61
  return {
@@ -142,6 +148,78 @@ function decodeBdsEphemeris(view, base) {
142
148
  ...keplerCommon(r)
143
149
  };
144
150
  }
151
+ function decodeGalEphemeris(view, base) {
152
+ const svid = view.getUint8(base + 5);
153
+ if (svid < 1 || svid > 36) return null;
154
+ const g = (o) => view.getFloat64(base + o, false);
155
+ const week = view.getUint16(base + 7, false);
156
+ const toe = view.getUint32(base + 9, false);
157
+ const tocMs = gpsMs(week, toe);
158
+ return {
159
+ system: "E",
160
+ prn: `E${String(svid).padStart(2, "0")}`,
161
+ toc: sowOf(tocMs),
162
+ tocDate: new Date(tocMs),
163
+ toe,
164
+ week,
165
+ iode: view.getUint16(base + 13, false),
166
+ // IODnav
167
+ af0: g(146),
168
+ af1: g(154),
169
+ af2: g(162),
170
+ crs: g(19),
171
+ deltaN: g(27) * GPS_PI,
172
+ m0: g(35) * GPS_PI,
173
+ cuc: g(43) * GPS_PI,
174
+ e: g(51),
175
+ cus: g(59) * GPS_PI,
176
+ sqrtA: g(67),
177
+ cic: g(75) * GPS_PI,
178
+ omega0: g(83) * GPS_PI,
179
+ cis: g(91) * GPS_PI,
180
+ i0: g(99) * GPS_PI,
181
+ crc: g(107),
182
+ omega: g(115) * GPS_PI,
183
+ omegaDot: g(123) * GPS_PI,
184
+ idot: g(131) * GPS_PI,
185
+ svHealth: 0,
186
+ // Galileo SISA/health word not located; observed sats healthy
187
+ tgd: g(179)
188
+ // BGD E5a/E1 (the RINEX Galileo tgd slot)
189
+ };
190
+ }
191
+ function decodeGloEphemeris(view, base) {
192
+ const slot = view.getUint8(base + 5);
193
+ if (slot < 1 || slot > 24) return null;
194
+ const week = view.getUint16(base + 6, false);
195
+ const tow = view.getUint32(base + 8, false);
196
+ const gpsTimeMs = gpsMs(week, tow);
197
+ const leapMs = getGpsLeap(new Date(gpsTimeMs)) * 1e3;
198
+ const tocMs = gpsTimeMs - leapMs;
199
+ return {
200
+ system: "R",
201
+ prn: `R${String(slot).padStart(2, "0")}`,
202
+ tocDate: new Date(tocMs),
203
+ // RINEX stores −τn as the clock bias; the record carries τn (ICD sign).
204
+ tauN: -view.getFloat64(base + 113, false),
205
+ gammaN: view.getFloat64(base + 121, false),
206
+ messageFrameTime: (tocMs - GPS_EPOCH_MS) / 1e3 % SEC_PER_WEEK,
207
+ // State vector: metres / m·s⁻¹ / m·s⁻² → km / km·s⁻¹ / km·s⁻².
208
+ x: view.getFloat64(base + 33, false) / 1e3,
209
+ xDot: view.getFloat64(base + 41, false) / 1e3,
210
+ xAcc: view.getFloat64(base + 49, false) / 1e3,
211
+ y: view.getFloat64(base + 57, false) / 1e3,
212
+ yDot: view.getFloat64(base + 65, false) / 1e3,
213
+ yAcc: view.getFloat64(base + 73, false) / 1e3,
214
+ z: view.getFloat64(base + 81, false) / 1e3,
215
+ zDot: view.getFloat64(base + 89, false) / 1e3,
216
+ zAcc: view.getFloat64(base + 97, false) / 1e3,
217
+ health: 0,
218
+ // Bn health bit not located; observed sats are operational
219
+ freqNum: view.getInt8(base + 29)
220
+ // FCN, signed byte (−7…+6)
221
+ };
222
+ }
145
223
  function parseTrimbleNav(data) {
146
224
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
147
225
  const stats = { badChecksum: 0 };
@@ -160,6 +238,12 @@ function parseTrimbleNav(data) {
160
238
  if (lastIode.get(eph.prn) === eph.iode) continue;
161
239
  lastIode.set(eph.prn, eph.iode);
162
240
  ephemerides.push(eph);
241
+ } else if (sub === SUB_GAL_EPHEMERIS && f.len >= 183) {
242
+ const eph = decodeGalEphemeris(view, f.start);
243
+ if (!eph) continue;
244
+ if (lastIode.get(eph.prn) === eph.iode) continue;
245
+ lastIode.set(eph.prn, eph.iode);
246
+ ephemerides.push(eph);
163
247
  } else if (sub === SUB_ION_UTC && f.len >= 102) {
164
248
  const p = f.start;
165
249
  const alpha = [];
@@ -171,6 +255,13 @@ function parseTrimbleNav(data) {
171
255
  ionoCorrections["GPSA"] = alpha;
172
256
  ionoCorrections["GPSB"] = beta;
173
257
  leapSeconds = Math.trunc(view.getFloat64(p + 94, false));
258
+ } else if (sub === SUB_GLO_EPHEMERIS && f.len >= 125) {
259
+ const eph = decodeGloEphemeris(view, f.start);
260
+ if (!eph) continue;
261
+ const key = eph.tocDate.getTime();
262
+ if (lastIode.get(eph.prn) === key) continue;
263
+ lastIode.set(eph.prn, key);
264
+ ephemerides.push(eph);
174
265
  }
175
266
  }
176
267
  return {
package/dist/index.cjs CHANGED
@@ -9600,6 +9600,8 @@ var gpsMs3 = (week, sec) => GPS_EPOCH_MS14 + week * MS_PER_WEEK9 + sec * 1e3;
9600
9600
  var sowOf4 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK10;
9601
9601
  var SUB_GPS_EPHEMERIS = 1;
9602
9602
  var SUB_ION_UTC = 3;
9603
+ var SUB_GLO_EPHEMERIS = 9;
9604
+ var SUB_GAL_EPHEMERIS = 11;
9603
9605
  var SUB_BDS_EPHEMERIS = 21;
9604
9606
  function readKepler(view, base) {
9605
9607
  return {
@@ -9692,6 +9694,78 @@ function decodeBdsEphemeris3(view, base) {
9692
9694
  ...keplerCommon(r)
9693
9695
  };
9694
9696
  }
9697
+ function decodeGalEphemeris2(view, base) {
9698
+ const svid = view.getUint8(base + 5);
9699
+ if (svid < 1 || svid > 36) return null;
9700
+ const g = (o) => view.getFloat64(base + o, false);
9701
+ const week = view.getUint16(base + 7, false);
9702
+ const toe = view.getUint32(base + 9, false);
9703
+ const tocMs = gpsMs3(week, toe);
9704
+ return {
9705
+ system: "E",
9706
+ prn: `E${String(svid).padStart(2, "0")}`,
9707
+ toc: sowOf4(tocMs),
9708
+ tocDate: new Date(tocMs),
9709
+ toe,
9710
+ week,
9711
+ iode: view.getUint16(base + 13, false),
9712
+ // IODnav
9713
+ af0: g(146),
9714
+ af1: g(154),
9715
+ af2: g(162),
9716
+ crs: g(19),
9717
+ deltaN: g(27) * GPS_PI2,
9718
+ m0: g(35) * GPS_PI2,
9719
+ cuc: g(43) * GPS_PI2,
9720
+ e: g(51),
9721
+ cus: g(59) * GPS_PI2,
9722
+ sqrtA: g(67),
9723
+ cic: g(75) * GPS_PI2,
9724
+ omega0: g(83) * GPS_PI2,
9725
+ cis: g(91) * GPS_PI2,
9726
+ i0: g(99) * GPS_PI2,
9727
+ crc: g(107),
9728
+ omega: g(115) * GPS_PI2,
9729
+ omegaDot: g(123) * GPS_PI2,
9730
+ idot: g(131) * GPS_PI2,
9731
+ svHealth: 0,
9732
+ // Galileo SISA/health word not located; observed sats healthy
9733
+ tgd: g(179)
9734
+ // BGD E5a/E1 (the RINEX Galileo tgd slot)
9735
+ };
9736
+ }
9737
+ function decodeGloEphemeris2(view, base) {
9738
+ const slot = view.getUint8(base + 5);
9739
+ if (slot < 1 || slot > 24) return null;
9740
+ const week = view.getUint16(base + 6, false);
9741
+ const tow = view.getUint32(base + 8, false);
9742
+ const gpsTimeMs = gpsMs3(week, tow);
9743
+ const leapMs = getGpsLeap(new Date(gpsTimeMs)) * 1e3;
9744
+ const tocMs = gpsTimeMs - leapMs;
9745
+ return {
9746
+ system: "R",
9747
+ prn: `R${String(slot).padStart(2, "0")}`,
9748
+ tocDate: new Date(tocMs),
9749
+ // RINEX stores −τn as the clock bias; the record carries τn (ICD sign).
9750
+ tauN: -view.getFloat64(base + 113, false),
9751
+ gammaN: view.getFloat64(base + 121, false),
9752
+ messageFrameTime: (tocMs - GPS_EPOCH_MS14) / 1e3 % SEC_PER_WEEK10,
9753
+ // State vector: metres / m·s⁻¹ / m·s⁻² → km / km·s⁻¹ / km·s⁻².
9754
+ x: view.getFloat64(base + 33, false) / 1e3,
9755
+ xDot: view.getFloat64(base + 41, false) / 1e3,
9756
+ xAcc: view.getFloat64(base + 49, false) / 1e3,
9757
+ y: view.getFloat64(base + 57, false) / 1e3,
9758
+ yDot: view.getFloat64(base + 65, false) / 1e3,
9759
+ yAcc: view.getFloat64(base + 73, false) / 1e3,
9760
+ z: view.getFloat64(base + 81, false) / 1e3,
9761
+ zDot: view.getFloat64(base + 89, false) / 1e3,
9762
+ zAcc: view.getFloat64(base + 97, false) / 1e3,
9763
+ health: 0,
9764
+ // Bn health bit not located; observed sats are operational
9765
+ freqNum: view.getInt8(base + 29)
9766
+ // FCN, signed byte (−7…+6)
9767
+ };
9768
+ }
9695
9769
  function parseTrimbleNav(data) {
9696
9770
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
9697
9771
  const stats = { badChecksum: 0 };
@@ -9710,6 +9784,12 @@ function parseTrimbleNav(data) {
9710
9784
  if (lastIode.get(eph.prn) === eph.iode) continue;
9711
9785
  lastIode.set(eph.prn, eph.iode);
9712
9786
  ephemerides.push(eph);
9787
+ } else if (sub === SUB_GAL_EPHEMERIS && f.len >= 183) {
9788
+ const eph = decodeGalEphemeris2(view, f.start);
9789
+ if (!eph) continue;
9790
+ if (lastIode.get(eph.prn) === eph.iode) continue;
9791
+ lastIode.set(eph.prn, eph.iode);
9792
+ ephemerides.push(eph);
9713
9793
  } else if (sub === SUB_ION_UTC && f.len >= 102) {
9714
9794
  const p = f.start;
9715
9795
  const alpha = [];
@@ -9721,6 +9801,13 @@ function parseTrimbleNav(data) {
9721
9801
  ionoCorrections["GPSA"] = alpha;
9722
9802
  ionoCorrections["GPSB"] = beta;
9723
9803
  leapSeconds = Math.trunc(view.getFloat64(p + 94, false));
9804
+ } else if (sub === SUB_GLO_EPHEMERIS && f.len >= 125) {
9805
+ const eph = decodeGloEphemeris2(view, f.start);
9806
+ if (!eph) continue;
9807
+ const key = eph.tocDate.getTime();
9808
+ if (lastIode.get(eph.prn) === key) continue;
9809
+ lastIode.set(eph.prn, key);
9810
+ ephemerides.push(eph);
9724
9811
  }
9725
9812
  }
9726
9813
  return {
package/dist/index.js CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  parseTrimble,
80
80
  parseTrimbleNav,
81
81
  trimbleFrames
82
- } from "./chunk-J2E2IKJR.js";
82
+ } from "./chunk-4ER3NBP3.js";
83
83
  import {
84
84
  binexCrc16,
85
85
  binexCrc32,
package/dist/trimble.cjs CHANGED
@@ -66,6 +66,93 @@ function* trimbleFrames(data, stats) {
66
66
  }
67
67
  }
68
68
 
69
+ // src/constants/time.ts
70
+ var MILLISECONDS_IN_SECOND = 1e3;
71
+ var MILLISECONDS_GPS_TAI = 19e3;
72
+ var START_NTP_TIME = /* @__PURE__ */ new Date("1900-01-01T00:00:00Z");
73
+
74
+ // src/time/gnss.ts
75
+ function getTaiDate(date) {
76
+ return new Date(date.getTime() + MILLISECONDS_GPS_TAI);
77
+ }
78
+ function getNtpTime(date) {
79
+ return getTaiDate(date).getTime() - START_NTP_TIME.getTime();
80
+ }
81
+
82
+ // src/time/utc.ts
83
+ var LEAP_SECONDS_TABLE = [
84
+ [3692217600, 37],
85
+ // 1 Jan 2017
86
+ [3644697600, 36],
87
+ // 1 Jul 2015
88
+ [3550089600, 35],
89
+ // 1 Jul 2012
90
+ [3439756800, 34],
91
+ // 1 Jan 2009
92
+ [3345062400, 33],
93
+ // 1 Jan 2006
94
+ [3124137600, 32],
95
+ // 1 Jan 1999
96
+ [3076704e3, 31],
97
+ // 1 Jul 1997
98
+ [3029443200, 30],
99
+ // 1 Jan 1996
100
+ [2982009600, 29],
101
+ // 1 Jul 1994
102
+ [2950473600, 28],
103
+ // 1 Jul 1993
104
+ [2918937600, 27],
105
+ // 1 Jul 1992
106
+ [2871676800, 26],
107
+ // 1 Jan 1991
108
+ [2840140800, 25],
109
+ // 1 Jan 1990
110
+ [2776982400, 24],
111
+ // 1 Jan 1988
112
+ [2698012800, 23],
113
+ // 1 Jul 1985
114
+ [2634854400, 22],
115
+ // 1 Jul 1983
116
+ [2603318400, 21],
117
+ // 1 Jul 1982
118
+ [2571782400, 20],
119
+ // 1 Jul 1981
120
+ [2524521600, 19],
121
+ // 1 Jan 1980
122
+ [2492985600, 18],
123
+ // 1 Jan 1979
124
+ [2461449600, 17],
125
+ // 1 Jan 1978
126
+ [2429913600, 16],
127
+ // 1 Jan 1977
128
+ [2398291200, 15],
129
+ // 1 Jan 1976
130
+ [2366755200, 14],
131
+ // 1 Jan 1975
132
+ [2335219200, 13],
133
+ // 1 Jan 1974
134
+ [2303683200, 12],
135
+ // 1 Jan 1973
136
+ [2287785600, 11],
137
+ // 1 Jul 1972
138
+ [2272060800, 10]
139
+ // 1 Jan 1972
140
+ ];
141
+ function getLeap(date) {
142
+ const ntp_time = getNtpTime(date);
143
+ for (const [timestamp, leapSeconds] of LEAP_SECONDS_TABLE) {
144
+ if (ntp_time / MILLISECONDS_IN_SECOND - leapSeconds >= timestamp) {
145
+ return leapSeconds;
146
+ }
147
+ }
148
+ return 8;
149
+ }
150
+ function getGpsLeap(date) {
151
+ const leap_seconds = getLeap(date);
152
+ if (leap_seconds < 0) return 0;
153
+ return leap_seconds - 19;
154
+ }
155
+
69
156
  // src/trimble/nav.ts
70
157
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
71
158
  var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
@@ -78,6 +165,8 @@ var gpsMs = (week, sec) => GPS_EPOCH_MS + week * MS_PER_WEEK + sec * 1e3;
78
165
  var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK;
79
166
  var SUB_GPS_EPHEMERIS = 1;
80
167
  var SUB_ION_UTC = 3;
168
+ var SUB_GLO_EPHEMERIS = 9;
169
+ var SUB_GAL_EPHEMERIS = 11;
81
170
  var SUB_BDS_EPHEMERIS = 21;
82
171
  function readKepler(view, base) {
83
172
  return {
@@ -170,6 +259,78 @@ function decodeBdsEphemeris(view, base) {
170
259
  ...keplerCommon(r)
171
260
  };
172
261
  }
262
+ function decodeGalEphemeris(view, base) {
263
+ const svid = view.getUint8(base + 5);
264
+ if (svid < 1 || svid > 36) return null;
265
+ const g = (o) => view.getFloat64(base + o, false);
266
+ const week = view.getUint16(base + 7, false);
267
+ const toe = view.getUint32(base + 9, false);
268
+ const tocMs = gpsMs(week, toe);
269
+ return {
270
+ system: "E",
271
+ prn: `E${String(svid).padStart(2, "0")}`,
272
+ toc: sowOf(tocMs),
273
+ tocDate: new Date(tocMs),
274
+ toe,
275
+ week,
276
+ iode: view.getUint16(base + 13, false),
277
+ // IODnav
278
+ af0: g(146),
279
+ af1: g(154),
280
+ af2: g(162),
281
+ crs: g(19),
282
+ deltaN: g(27) * GPS_PI,
283
+ m0: g(35) * GPS_PI,
284
+ cuc: g(43) * GPS_PI,
285
+ e: g(51),
286
+ cus: g(59) * GPS_PI,
287
+ sqrtA: g(67),
288
+ cic: g(75) * GPS_PI,
289
+ omega0: g(83) * GPS_PI,
290
+ cis: g(91) * GPS_PI,
291
+ i0: g(99) * GPS_PI,
292
+ crc: g(107),
293
+ omega: g(115) * GPS_PI,
294
+ omegaDot: g(123) * GPS_PI,
295
+ idot: g(131) * GPS_PI,
296
+ svHealth: 0,
297
+ // Galileo SISA/health word not located; observed sats healthy
298
+ tgd: g(179)
299
+ // BGD E5a/E1 (the RINEX Galileo tgd slot)
300
+ };
301
+ }
302
+ function decodeGloEphemeris(view, base) {
303
+ const slot = view.getUint8(base + 5);
304
+ if (slot < 1 || slot > 24) return null;
305
+ const week = view.getUint16(base + 6, false);
306
+ const tow = view.getUint32(base + 8, false);
307
+ const gpsTimeMs = gpsMs(week, tow);
308
+ const leapMs = getGpsLeap(new Date(gpsTimeMs)) * 1e3;
309
+ const tocMs = gpsTimeMs - leapMs;
310
+ return {
311
+ system: "R",
312
+ prn: `R${String(slot).padStart(2, "0")}`,
313
+ tocDate: new Date(tocMs),
314
+ // RINEX stores −τn as the clock bias; the record carries τn (ICD sign).
315
+ tauN: -view.getFloat64(base + 113, false),
316
+ gammaN: view.getFloat64(base + 121, false),
317
+ messageFrameTime: (tocMs - GPS_EPOCH_MS) / 1e3 % SEC_PER_WEEK,
318
+ // State vector: metres / m·s⁻¹ / m·s⁻² → km / km·s⁻¹ / km·s⁻².
319
+ x: view.getFloat64(base + 33, false) / 1e3,
320
+ xDot: view.getFloat64(base + 41, false) / 1e3,
321
+ xAcc: view.getFloat64(base + 49, false) / 1e3,
322
+ y: view.getFloat64(base + 57, false) / 1e3,
323
+ yDot: view.getFloat64(base + 65, false) / 1e3,
324
+ yAcc: view.getFloat64(base + 73, false) / 1e3,
325
+ z: view.getFloat64(base + 81, false) / 1e3,
326
+ zDot: view.getFloat64(base + 89, false) / 1e3,
327
+ zAcc: view.getFloat64(base + 97, false) / 1e3,
328
+ health: 0,
329
+ // Bn health bit not located; observed sats are operational
330
+ freqNum: view.getInt8(base + 29)
331
+ // FCN, signed byte (−7…+6)
332
+ };
333
+ }
173
334
  function parseTrimbleNav(data) {
174
335
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
175
336
  const stats = { badChecksum: 0 };
@@ -188,6 +349,12 @@ function parseTrimbleNav(data) {
188
349
  if (lastIode.get(eph.prn) === eph.iode) continue;
189
350
  lastIode.set(eph.prn, eph.iode);
190
351
  ephemerides.push(eph);
352
+ } else if (sub === SUB_GAL_EPHEMERIS && f.len >= 183) {
353
+ const eph = decodeGalEphemeris(view, f.start);
354
+ if (!eph) continue;
355
+ if (lastIode.get(eph.prn) === eph.iode) continue;
356
+ lastIode.set(eph.prn, eph.iode);
357
+ ephemerides.push(eph);
191
358
  } else if (sub === SUB_ION_UTC && f.len >= 102) {
192
359
  const p = f.start;
193
360
  const alpha = [];
@@ -199,6 +366,13 @@ function parseTrimbleNav(data) {
199
366
  ionoCorrections["GPSA"] = alpha;
200
367
  ionoCorrections["GPSB"] = beta;
201
368
  leapSeconds = Math.trunc(view.getFloat64(p + 94, false));
369
+ } else if (sub === SUB_GLO_EPHEMERIS && f.len >= 125) {
370
+ const eph = decodeGloEphemeris(view, f.start);
371
+ if (!eph) continue;
372
+ const key = eph.tocDate.getTime();
373
+ if (lastIode.get(eph.prn) === key) continue;
374
+ lastIode.set(eph.prn, key);
375
+ ephemerides.push(eph);
202
376
  }
203
377
  }
204
378
  return {
@@ -25,7 +25,8 @@ declare function trimbleFrames(data: Uint8Array, stats: {
25
25
 
26
26
  /**
27
27
  * Trimble RT17/RT27 navigation-message decoding: RETSVDATA (0x55)
28
- * GPS ephemeris (subtype 1), BeiDou ephemeris (subtype 21) and ION / UTC
28
+ * GPS ephemeris (subtype 1), GLONASS ephemeris (subtype 9), Galileo
29
+ * ephemeris (subtype 11), BeiDou ephemeris (subtype 21) and ION / UTC
29
30
  * data (subtype 3).
30
31
  *
31
32
  * The GPS path is ported field-for-field from RTKLIB's `src/rcv/rt17.c`
@@ -42,8 +43,12 @@ declare function trimbleFrames(data: Uint8Array, stats: {
42
43
  * RINEX 3 navigation record and match the NovAtel/SBF ephemeris paths
43
44
  * (GPS: GPS-scale `tocDate`; BeiDou: naive-BDT `tocDate`; angles in rad).
44
45
  *
45
- * Still not decoded: the GLONASS/Galileo/QZSS ephemeris and almanac
46
- * subtypes (RETSVDATA 9/11/13/23/27 on this stream).
46
+ * The subtype→constellation map is confirmed by RTKLIB's rt17.c
47
+ * RETSVDATA table (st[]: 1 = GPS Eph, 9 = GLONASS Eph, 11 = Galileo Eph,
48
+ * 21 = BeiDou Eph) — but RTKLIB only *decodes* subtypes 1 and 3, so the
49
+ * GLONASS/BeiDou field layouts here were reverse-engineered and validated
50
+ * against physics + the 2026-07-27 BRDC. Still not decoded: the
51
+ * QZSS / almanac subtypes (13/23/27 on this stream).
47
52
  */
48
53
 
49
54
  interface TrimbleNavResult {
@@ -62,9 +67,10 @@ interface TrimbleNavResult {
62
67
  badChecksum: number;
63
68
  }
64
69
  /**
65
- * Decode every RETSVDATA navigation message (GPS ephemeris, ION/UTC) in
66
- * a Trimble binary byte stream. Repeated broadcasts of an unchanged GPS
67
- * ephemeris are suppressed by issue of data (IODE), as RTKLIB does.
70
+ * Decode every RETSVDATA navigation message (GPS/BeiDou/GLONASS
71
+ * ephemeris, ION/UTC) in a Trimble binary byte stream. Repeated
72
+ * broadcasts of an unchanged ephemeris are suppressed by issue of data
73
+ * (Keplerian) / reference epoch (GLONASS), as RTKLIB does.
68
74
  */
69
75
  declare function parseTrimbleNav(data: Uint8Array): TrimbleNavResult;
70
76
 
package/dist/trimble.d.ts CHANGED
@@ -25,7 +25,8 @@ declare function trimbleFrames(data: Uint8Array, stats: {
25
25
 
26
26
  /**
27
27
  * Trimble RT17/RT27 navigation-message decoding: RETSVDATA (0x55)
28
- * GPS ephemeris (subtype 1), BeiDou ephemeris (subtype 21) and ION / UTC
28
+ * GPS ephemeris (subtype 1), GLONASS ephemeris (subtype 9), Galileo
29
+ * ephemeris (subtype 11), BeiDou ephemeris (subtype 21) and ION / UTC
29
30
  * data (subtype 3).
30
31
  *
31
32
  * The GPS path is ported field-for-field from RTKLIB's `src/rcv/rt17.c`
@@ -42,8 +43,12 @@ declare function trimbleFrames(data: Uint8Array, stats: {
42
43
  * RINEX 3 navigation record and match the NovAtel/SBF ephemeris paths
43
44
  * (GPS: GPS-scale `tocDate`; BeiDou: naive-BDT `tocDate`; angles in rad).
44
45
  *
45
- * Still not decoded: the GLONASS/Galileo/QZSS ephemeris and almanac
46
- * subtypes (RETSVDATA 9/11/13/23/27 on this stream).
46
+ * The subtype→constellation map is confirmed by RTKLIB's rt17.c
47
+ * RETSVDATA table (st[]: 1 = GPS Eph, 9 = GLONASS Eph, 11 = Galileo Eph,
48
+ * 21 = BeiDou Eph) — but RTKLIB only *decodes* subtypes 1 and 3, so the
49
+ * GLONASS/BeiDou field layouts here were reverse-engineered and validated
50
+ * against physics + the 2026-07-27 BRDC. Still not decoded: the
51
+ * QZSS / almanac subtypes (13/23/27 on this stream).
47
52
  */
48
53
 
49
54
  interface TrimbleNavResult {
@@ -62,9 +67,10 @@ interface TrimbleNavResult {
62
67
  badChecksum: number;
63
68
  }
64
69
  /**
65
- * Decode every RETSVDATA navigation message (GPS ephemeris, ION/UTC) in
66
- * a Trimble binary byte stream. Repeated broadcasts of an unchanged GPS
67
- * ephemeris are suppressed by issue of data (IODE), as RTKLIB does.
70
+ * Decode every RETSVDATA navigation message (GPS/BeiDou/GLONASS
71
+ * ephemeris, ION/UTC) in a Trimble binary byte stream. Repeated
72
+ * broadcasts of an unchanged ephemeris are suppressed by issue of data
73
+ * (Keplerian) / reference epoch (GLONASS), as RTKLIB does.
68
74
  */
69
75
  declare function parseTrimbleNav(data: Uint8Array): TrimbleNavResult;
70
76
 
package/dist/trimble.js CHANGED
@@ -2,7 +2,9 @@ import {
2
2
  parseTrimble,
3
3
  parseTrimbleNav,
4
4
  trimbleFrames
5
- } from "./chunk-J2E2IKJR.js";
5
+ } from "./chunk-4ER3NBP3.js";
6
+ import "./chunk-HVXYFUCB.js";
7
+ import "./chunk-LEEU5OIO.js";
6
8
  export {
7
9
  parseTrimble,
8
10
  parseTrimbleNav,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.40.0",
3
+ "version": "1.42.0",
4
4
  "description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
5
5
  "type": "module",
6
6
  "sideEffects": false,