gnss-js 1.51.0 → 1.53.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.
@@ -3048,6 +3048,65 @@ function sagnac2(pos, travelTimeS) {
3048
3048
  const s = Math.sin(theta);
3049
3049
  return [pos.x * c + pos.y * s, -pos.x * s + pos.y * c, pos.z];
3050
3050
  }
3051
+ function sbasAirTropoVar(elRad) {
3052
+ const elDeg = elRad * 180 / Math.PI;
3053
+ const sigMp = 0.13 + 0.53 * Math.exp(-elDeg / 10);
3054
+ const sigNoise = 0.15 + 0.43 * Math.exp(-elDeg / 6.9);
3055
+ const m = 1.001 / Math.sqrt(2001e-6 + Math.sin(elRad) * Math.sin(elRad));
3056
+ const sigTropo = 0.12 * m;
3057
+ return sigMp * sigMp + sigNoise * sigNoise + sigTropo * sigTropo;
3058
+ }
3059
+ function invertMatrix(A) {
3060
+ const n = A.length;
3061
+ const M = A.map((row, i) => [
3062
+ ...row,
3063
+ ...Array.from({ length: n }, (_, j) => i === j ? 1 : 0)
3064
+ ]);
3065
+ for (let col = 0; col < n; col++) {
3066
+ let piv = col;
3067
+ for (let r = col + 1; r < n; r++)
3068
+ if (Math.abs(M[r][col]) > Math.abs(M[piv][col])) piv = r;
3069
+ if (Math.abs(M[piv][col]) < 1e-12) return null;
3070
+ [M[col], M[piv]] = [M[piv], M[col]];
3071
+ const d = M[col][col];
3072
+ for (let j = 0; j < 2 * n; j++) M[col][j] /= d;
3073
+ for (let r = 0; r < n; r++) {
3074
+ if (r === col) continue;
3075
+ const f = M[r][col];
3076
+ for (let j = 0; j < 2 * n; j++) M[r][j] -= f * M[col][j];
3077
+ }
3078
+ }
3079
+ return M.map((row) => row.slice(n));
3080
+ }
3081
+ function sbasProtectionLevels(sats, opts = {}) {
3082
+ const kH = opts.kH ?? 6;
3083
+ const kV = opts.kV ?? 5.33;
3084
+ if (sats.length < 4) return null;
3085
+ const N = [0, 1, 2, 3].map(() => [0, 0, 0, 0]);
3086
+ for (const s of sats) {
3087
+ if (!(s.variance > 0)) continue;
3088
+ const ce = Math.cos(s.elRad);
3089
+ const g = [
3090
+ ce * Math.sin(s.azRad),
3091
+ ce * Math.cos(s.azRad),
3092
+ Math.sin(s.elRad),
3093
+ 1
3094
+ ];
3095
+ const w = 1 / s.variance;
3096
+ for (let i = 0; i < 4; i++)
3097
+ for (let j = 0; j < 4; j++) N[i][j] += w * g[i] * g[j];
3098
+ }
3099
+ const cov = invertMatrix(N);
3100
+ if (!cov) return null;
3101
+ const dE2 = cov[0][0];
3102
+ const dN2 = cov[1][1];
3103
+ const dEN = cov[0][1];
3104
+ const dU = Math.sqrt(Math.max(cov[2][2], 0));
3105
+ const dMajor = Math.sqrt(
3106
+ (dE2 + dN2) / 2 + Math.sqrt(((dE2 - dN2) / 2) ** 2 + dEN * dEN)
3107
+ );
3108
+ return { hpl: kH * dMajor, vpl: kV * dU, dMajor, dU };
3109
+ }
3051
3110
  function solveLinear(A, b) {
3052
3111
  const n = b.length;
3053
3112
  const M = A.map((row, i) => [...row, b[i]]);
@@ -3104,7 +3163,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3104
3163
  const sysRows = new Map(systems.map((s) => [s, 0]));
3105
3164
  for (const k of Object.keys(residuals2)) delete residuals2[k];
3106
3165
  const positionSaneIter = x9 * x9 + y2 * y2 + z2 * z2 > 1e12;
3107
- const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x9, y2, z2) : [0, 0, 0];
3166
+ const [rxLat2, rxLon2, rxHgt2] = positionSaneIter ? ecefToGeodetic(x9, y2, z2) : [0, 0, 0];
3108
3167
  for (const prn of prns) {
3109
3168
  const psr = pseudoranges.get(prn);
3110
3169
  const eph = ephemerides.get(prn);
@@ -3143,7 +3202,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3143
3202
  const sinEl = Math.max(Math.sin(elev), 0.1);
3144
3203
  weight = sinEl * sinEl;
3145
3204
  }
3146
- const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
3205
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat2, rxHgt2) : 0;
3147
3206
  let ionoM = 0;
3148
3207
  if ((sbas || gim || iono) && positionSane) {
3149
3208
  let l1M = null;
@@ -3151,18 +3210,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3151
3210
  const si = sbas.ionoDelay(
3152
3211
  gpsWeek,
3153
3212
  gpsTow,
3154
- rxLat,
3155
- rxLon,
3156
- rxHgt,
3213
+ rxLat2,
3214
+ rxLon2,
3215
+ rxHgt2,
3157
3216
  azim,
3158
3217
  elev
3159
3218
  );
3160
3219
  if (si) l1M = si.delayM;
3161
3220
  }
3162
3221
  if (l1M === null && gim)
3163
- l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
3222
+ l1M = gimSlantIonoDelayL1(gim, rxLat2, rxLon2, azim, elev, timeMs);
3164
3223
  if (l1M === null && iono) {
3165
- l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
3224
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat2, rxLon2, azim, elev, gpsTow);
3166
3225
  }
3167
3226
  if (l1M !== null) {
3168
3227
  const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
@@ -3235,6 +3294,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3235
3294
  const { x, y, z, clock, residuals, iterations, converged } = inner;
3236
3295
  const used = [];
3237
3296
  const azels = [];
3297
+ const [rxLat, rxLon, rxHgt] = ecefToGeodetic(x, y, z);
3298
+ const sbasRows = [];
3238
3299
  for (const prn of candidates) {
3239
3300
  if (!(prn in residuals)) continue;
3240
3301
  const eph = ephemerides.get(prn);
@@ -3244,8 +3305,27 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3244
3305
  if (azel.el >= elevationMaskDeg * Math.PI / 180) {
3245
3306
  used.push(prn);
3246
3307
  azels.push(azel);
3308
+ if (sbas) {
3309
+ const sc = sbas.satCorrection(prn, gpsWeek, gpsTow);
3310
+ const si = sbas.ionoDelay(
3311
+ gpsWeek,
3312
+ gpsTow,
3313
+ rxLat,
3314
+ rxLon,
3315
+ rxHgt,
3316
+ azel.az,
3317
+ azel.el
3318
+ );
3319
+ if (sc && si)
3320
+ sbasRows.push({
3321
+ azRad: azel.az,
3322
+ elRad: azel.el,
3323
+ variance: sc.varM2 + si.varM2 + sbasAirTropoVar(azel.el)
3324
+ });
3325
+ }
3247
3326
  }
3248
3327
  }
3328
+ const pl = sbas ? sbasProtectionLevels(sbasRows) : null;
3249
3329
  return {
3250
3330
  position: [x, y, z],
3251
3331
  clockBias: Object.fromEntries(clock),
@@ -3254,7 +3334,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
3254
3334
  residuals,
3255
3335
  dop: computeDop(azels),
3256
3336
  iterations,
3257
- converged
3337
+ converged,
3338
+ ...pl ? { hpl: pl.hpl, vpl: pl.vpl, sbasSats: sbasRows.length } : sbas ? { sbasSats: sbasRows.length } : {}
3258
3339
  };
3259
3340
  }
3260
3341
 
@@ -3288,5 +3369,6 @@ export {
3288
3369
  estimateNetworkFcbs,
3289
3370
  satClockCorrection,
3290
3371
  ionoFree,
3372
+ sbasProtectionLevels,
3291
3373
  solveSpp
3292
3374
  };
@@ -486,6 +486,58 @@ function parseSbfReceiverSetup(data) {
486
486
  return out;
487
487
  }
488
488
 
489
+ // src/sbf/pvt.ts
490
+ var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
491
+ var RAD2DEG = 180 / Math.PI;
492
+ var DNU_F8 = -2e10;
493
+ var MODE_LABELS = {
494
+ 0: "no-pvt",
495
+ 1: "standalone",
496
+ 2: "differential",
497
+ 3: "fixed-location",
498
+ 4: "rtk-fixed",
499
+ 5: "rtk-float",
500
+ 6: "sbas-aided",
501
+ 7: "moving-base-rtk-fixed",
502
+ 8: "moving-base-rtk-float",
503
+ 10: "ppp"
504
+ };
505
+ function parseSbfPvt(data) {
506
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
507
+ const records = [];
508
+ let messages = 0;
509
+ scanSbfFrames(data, view, (id, b, len) => {
510
+ if (id !== 4007 || len < 96) return;
511
+ messages++;
512
+ const tow = view.getUint32(b + 8, true);
513
+ const wnc = view.getUint16(b + 12, true);
514
+ if (tow === 4294967295 || wnc === 65535) return;
515
+ const modeByte = view.getUint8(b + 14);
516
+ const modeType = modeByte & 15;
517
+ const lat = view.getFloat64(b + 16, true);
518
+ const lon = view.getFloat64(b + 24, true);
519
+ const hgt = view.getFloat64(b + 32, true);
520
+ const nrSV = view.getUint8(b + 74);
521
+ const hAcc = view.getUint16(b + 90, true);
522
+ const vAcc = view.getUint16(b + 92, true);
523
+ const hasFix = lat > DNU_F8 && lon > DNU_F8 && hgt > DNU_F8;
524
+ records.push({
525
+ timeMs: GPS_EPOCH_MS2 + wnc * 6048e5 + tow,
526
+ week: wnc,
527
+ tow: tow / 1e3,
528
+ modeType,
529
+ mode: MODE_LABELS[modeType] ?? `mode-${modeType}`,
530
+ latDeg: hasFix ? lat * RAD2DEG : null,
531
+ lonDeg: hasFix ? lon * RAD2DEG : null,
532
+ heightM: hasFix ? hgt : null,
533
+ nrSV: nrSV === 255 ? null : nrSV,
534
+ hAccuracyM: hAcc === 65535 ? null : hAcc / 100,
535
+ vAccuracyM: vAcc === 65535 ? null : vAcc / 100
536
+ });
537
+ });
538
+ return { records, messages };
539
+ }
540
+
489
541
  // src/sbf/rawnav-gps.ts
490
542
  var WNC_DNU = 65535;
491
543
  function extractSbfLnavSubframe(view, b) {
@@ -621,7 +673,7 @@ function isFnavDummy(page) {
621
673
  }
622
674
 
623
675
  // src/sbf/rawnav-bds.ts
624
- var GPS_EPOCH_MS2 = Date.UTC(1980, 0, 6);
676
+ var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
625
677
  var MS_PER_WEEK2 = 7 * 86400 * 1e3;
626
678
  var TOW_DNU = 4294967295;
627
679
  var WNC_DNU2 = 65535;
@@ -682,7 +734,7 @@ function feedGloBlock(view, b, assembler) {
682
734
  const eph = assembler.push(
683
735
  prn,
684
736
  str2,
685
- new Date(GPS_EPOCH_MS2 + wnc * MS_PER_WEEK2 + towMs),
737
+ new Date(GPS_EPOCH_MS3 + wnc * MS_PER_WEEK2 + towMs),
686
738
  view.getUint8(b + 18) - 8
687
739
  // FreqNr, offset by 8
688
740
  );
@@ -1432,7 +1484,7 @@ function parseSbfHas(data) {
1432
1484
 
1433
1485
  // src/sbf/index.ts
1434
1486
  var CLIGHT = 299792458;
1435
- var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
1487
+ var GPS_EPOCH_MS4 = Date.UTC(1980, 0, 6);
1436
1488
  var MS_PER_WEEK3 = 7 * 864e5;
1437
1489
  var M3_SYS = ["G", "R", "E", "C", "S", "J", "I"];
1438
1490
  var M3_PR_BASE = [19e6, 19e6, 22e6, 2e7, 34e6, 34e6, 34e6];
@@ -2086,7 +2138,7 @@ function parseSbfMeas(data) {
2086
2138
  const tow = view.getUint32(i + 8, true);
2087
2139
  const wnc = view.getUint16(i + 12, true);
2088
2140
  if (tow !== 4294967295 && wnc !== 65535) {
2089
- ensureEpoch(GPS_EPOCH_MS3 + wnc * MS_PER_WEEK3 + tow);
2141
+ ensureEpoch(GPS_EPOCH_MS4 + wnc * MS_PER_WEEK3 + tow);
2090
2142
  if (id === 4109) decodeMeas3Ranges(i, tow);
2091
2143
  else if (id === 4110) decodeMeas3CN0(i, len);
2092
2144
  else if (id === 4111) decodeMeas3Doppler(i, len);
@@ -2105,6 +2157,7 @@ export {
2105
2157
  parseSbfAlmanac,
2106
2158
  parseSbfIonoUtc,
2107
2159
  parseSbfReceiverSetup,
2160
+ parseSbfPvt,
2108
2161
  parseSbfGpsNav,
2109
2162
  parseSbfCnav,
2110
2163
  parseSbfGalNav,