gnss-js 2.10.1 → 2.12.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.
Files changed (55) hide show
  1. package/README.md +16 -1
  2. package/dist/analysis.cjs +24 -25
  3. package/dist/analysis.js +2 -2
  4. package/dist/{clk-DGVBGRzA.d.cts → bias-sinex-OGMw9xEZ.d.cts} +66 -1
  5. package/dist/{clk-DGVBGRzA.d.ts → bias-sinex-OGMw9xEZ.d.ts} +66 -1
  6. package/dist/binex.d.cts +1 -1
  7. package/dist/binex.d.ts +1 -1
  8. package/dist/{chunk-56HM5B6T.js → chunk-2DJ4XENO.js} +177 -9
  9. package/dist/{chunk-GH7MYXON.js → chunk-2KQMCJ5Z.js} +7 -5
  10. package/dist/chunk-E3Y6YKQM.js +65 -0
  11. package/dist/{chunk-3U5AX7PY.js → chunk-FEABDV5P.js} +24 -25
  12. package/dist/{chunk-WQX354KD.js → chunk-H3AKFD25.js} +1 -1
  13. package/dist/{chunk-YUW33VXK.js → chunk-HUXQABJI.js} +2 -0
  14. package/dist/{chunk-74IVYOQV.js → chunk-HWHD5E6V.js} +128 -78
  15. package/dist/{chunk-KUSUN7DP.js → chunk-MFCPYUEI.js} +1 -1
  16. package/dist/{chunk-3KSDL3CO.js → chunk-NIJ6WFQJ.js} +6 -321
  17. package/dist/chunk-NL2ORX75.js +319 -0
  18. package/dist/{chunk-K3GLUWZI.js → chunk-RWFJEVLJ.js} +590 -296
  19. package/dist/{chunk-SEYH4BEN.js → chunk-SZP4TLYR.js} +2 -2
  20. package/dist/chunk-TWGZTRBL.js +323 -0
  21. package/dist/index.cjs +1315 -460
  22. package/dist/index.d.cts +5 -5
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +20 -12
  25. package/dist/{nav-DImXUdbp.d.cts → nav-COp0Vmlw.d.cts} +83 -2
  26. package/dist/{nav-DImXUdbp.d.ts → nav-COp0Vmlw.d.ts} +83 -2
  27. package/dist/{nav-BYmYoh9A.d.cts → nav-CvZXmumD.d.cts} +1 -1
  28. package/dist/{nav-CN8rxL4h.d.ts → nav-D2DFlVpa.d.ts} +1 -1
  29. package/dist/novatel.d.cts +1 -1
  30. package/dist/novatel.d.ts +1 -1
  31. package/dist/novatel.js +3 -3
  32. package/dist/orbit.d.cts +2 -2
  33. package/dist/orbit.d.ts +2 -2
  34. package/dist/osnma.cjs +1336 -0
  35. package/dist/osnma.d.cts +273 -0
  36. package/dist/osnma.d.ts +273 -0
  37. package/dist/osnma.js +1216 -0
  38. package/dist/positioning.cjs +585 -291
  39. package/dist/positioning.d.cts +116 -6
  40. package/dist/positioning.d.ts +116 -6
  41. package/dist/positioning.js +3 -3
  42. package/dist/rinex.cjs +210 -36
  43. package/dist/rinex.d.cts +10 -55
  44. package/dist/rinex.d.ts +10 -55
  45. package/dist/rinex.js +6 -3
  46. package/dist/sbf.cjs +420 -24
  47. package/dist/sbf.d.cts +46 -5
  48. package/dist/sbf.d.ts +46 -5
  49. package/dist/sbf.js +12 -6
  50. package/dist/trimble.d.cts +1 -1
  51. package/dist/trimble.d.ts +1 -1
  52. package/dist/ubx.d.cts +1 -1
  53. package/dist/ubx.d.ts +1 -1
  54. package/dist/ubx.js +5 -4
  55. package/package.json +6 -1
package/dist/index.cjs CHANGED
@@ -106,6 +106,7 @@ __export(src_exports, {
106
106
  SYSTEM_NAMES: () => SYSTEM_NAMES,
107
107
  SYSTEM_ORDER: () => SYSTEM_ORDER,
108
108
  SYS_SHORT: () => SYS_SHORT,
109
+ SatOsbTable: () => SatOsbTable,
109
110
  SbasProcessor: () => SbasProcessor,
110
111
  Scale: () => Scale,
111
112
  Sp3EphemerisSource: () => Sp3EphemerisSource,
@@ -288,6 +289,7 @@ __export(src_exports, {
288
289
  parseNovatelRange: () => parseNovatelRange,
289
290
  parseRinexStream: () => parseRinexStream,
290
291
  parseSbfAlmanac: () => parseSbfAlmanac,
292
+ parseSbfBdsCnav: () => parseSbfBdsCnav,
291
293
  parseSbfBdsNav: () => parseSbfBdsNav,
292
294
  parseSbfCnav: () => parseSbfCnav,
293
295
  parseSbfGalNav: () => parseSbfGalNav,
@@ -1373,20 +1375,14 @@ function geodeticToGeohash(lat, lon, precision = 8) {
1373
1375
  // src/rinex/crx.ts
1374
1376
  function crxRepair(old, diff) {
1375
1377
  const chars = old.split("");
1376
- for (let i = 0; i < diff.length; i++) {
1378
+ const n = Math.min(old.length, diff.length);
1379
+ for (let i = 0; i < n; i++) {
1377
1380
  const dc = diff[i];
1378
- if (dc === " ") {
1379
- } else if (dc === "&") {
1380
- chars[i] = " ";
1381
- } else {
1382
- chars[i] = dc;
1383
- }
1384
- }
1385
- if (diff.length > chars.length) {
1386
- for (let i = chars.length; i < diff.length; i++) {
1387
- chars[i] = diff[i] === "&" ? " " : diff[i];
1388
- }
1381
+ if (dc === " ") continue;
1382
+ chars[i] = dc === "&" ? " " : dc;
1389
1383
  }
1384
+ for (let i = old.length; i < diff.length; i++)
1385
+ chars[i] = diff[i] === "&" ? " " : diff[i];
1390
1386
  return chars.join("");
1391
1387
  }
1392
1388
  function parseCrxDataLine(line, ntype) {
@@ -2084,6 +2080,15 @@ function readObsValue(line, index) {
2084
2080
  const v = parseFloat(raw);
2085
2081
  return isNaN(v) ? null : v;
2086
2082
  }
2083
+ function normalizePrn(id) {
2084
+ let sys = id[0];
2085
+ let num = id.substring(1);
2086
+ if (sys === " " || /\d/.test(sys)) {
2087
+ num = id;
2088
+ sys = "G";
2089
+ }
2090
+ return sys + num.replace(/\s/g, "").padStart(2, "0");
2091
+ }
2087
2092
  function parseEpochLine3(line) {
2088
2093
  if (line[0] !== ">") return null;
2089
2094
  const yr = parseInt(line.substring(2, 6));
@@ -2233,7 +2238,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
2233
2238
  function processSatLineV3(line) {
2234
2239
  if (!header || line.length < 3) return;
2235
2240
  const sys = line[0];
2236
- const prn = line.substring(0, 3);
2241
+ const prn = normalizePrn(line.substring(0, 3));
2237
2242
  trackSat(sys, prn);
2238
2243
  const obsLine = line.substring(3);
2239
2244
  const codes = header.obsTypes[sys] ?? [];
@@ -2258,10 +2263,8 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
2258
2263
  let v2ObsAccum = [];
2259
2264
  function processSatLineV2(line) {
2260
2265
  if (!header || v2SatIndex >= v2SatIds.length) return;
2261
- const satId = v2SatIds[v2SatIndex];
2262
- let sys = satId[0];
2263
- if (sys === " " || /\d/.test(sys)) sys = "G";
2264
- const prn = sys + satId.substring(1).padStart(2, "0");
2266
+ const prn = normalizePrn(v2SatIds[v2SatIndex]);
2267
+ const sys = prn[0];
2265
2268
  if (v2CurrentSatLine === 0) {
2266
2269
  trackSat(sys, prn);
2267
2270
  if (onSatObs) v2ObsAccum = [];
@@ -2352,6 +2355,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
2352
2355
  } else {
2353
2356
  epochLine = crxRepair(crxPrevEpochLine, line);
2354
2357
  }
2358
+ epochLine = epochLine.replace(/ +$/, "");
2355
2359
  if (isV3) {
2356
2360
  if (epochLine[0] !== ">") return;
2357
2361
  const info = parseEpochLine3(epochLine);
@@ -2369,10 +2373,11 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
2369
2373
  return;
2370
2374
  }
2371
2375
  resetEpochAccum(info);
2372
- const satPart = epochLine.substring(41).replace(/\s+$/, "");
2373
2376
  crxSatList = [];
2374
- for (let i = 0; i + 3 <= satPart.length; i += 3) {
2375
- crxSatList.push(satPart.substring(i, i + 3));
2377
+ for (let k = 0; k < info.numSats; k++) {
2378
+ crxSatList.push(
2379
+ normalizePrn(epochLine.substring(41 + 3 * k, 44 + 3 * k))
2380
+ );
2376
2381
  }
2377
2382
  crxSatIndex = 0;
2378
2383
  crxPrevEpochLine = epochLine;
@@ -2393,11 +2398,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
2393
2398
  return;
2394
2399
  }
2395
2400
  resetEpochAccum(info);
2396
- crxSatList = (info.satIds ?? []).map((id) => {
2397
- let sys = id[0];
2398
- if (sys === " " || /\d/.test(sys)) sys = "G";
2399
- return sys + id.substring(1).padStart(2, "0");
2400
- });
2401
+ crxSatList = (info.satIds ?? []).slice(0, info.numSats).map(normalizePrn);
2401
2402
  crxSatIndex = 0;
2402
2403
  crxPrevEpochLine = epochLine;
2403
2404
  crxPhase = "clock";
@@ -3026,7 +3027,7 @@ var isc = (b, pos) => {
3026
3027
  return raw === -4096 ? null : raw * 2 ** -35;
3027
3028
  };
3028
3029
  function decodeClock(b, msgType) {
3029
- const clock = {
3030
+ const clock2 = {
3030
3031
  msgType,
3031
3032
  top: getBitU(b, 38, 11) * 300,
3032
3033
  uraNed0: getBitS(b, 49, 5),
@@ -3038,26 +3039,26 @@ function decodeClock(b, msgType) {
3038
3039
  af2: getBitS(b, 117, 10) * 2 ** -60
3039
3040
  };
3040
3041
  if (msgType === 30) {
3041
- clock.tgd = isc(b, 127);
3042
- clock.iscL1ca = isc(b, 140);
3043
- clock.iscL2c = isc(b, 153);
3044
- clock.iscL5i5 = isc(b, 166);
3045
- clock.iscL5q5 = isc(b, 179);
3046
- clock.ionoAlpha = [
3042
+ clock2.tgd = isc(b, 127);
3043
+ clock2.iscL1ca = isc(b, 140);
3044
+ clock2.iscL2c = isc(b, 153);
3045
+ clock2.iscL5i5 = isc(b, 166);
3046
+ clock2.iscL5q5 = isc(b, 179);
3047
+ clock2.ionoAlpha = [
3047
3048
  getBitS(b, 192, 8) * 2 ** -30,
3048
3049
  getBitS(b, 200, 8) * 2 ** -27,
3049
3050
  getBitS(b, 208, 8) * 2 ** -24,
3050
3051
  getBitS(b, 216, 8) * 2 ** -24
3051
3052
  ];
3052
- clock.ionoBeta = [
3053
+ clock2.ionoBeta = [
3053
3054
  getBitS(b, 224, 8) * 2 ** 11,
3054
3055
  getBitS(b, 232, 8) * 2 ** 14,
3055
3056
  getBitS(b, 240, 8) * 2 ** 16,
3056
3057
  getBitS(b, 248, 8) * 2 ** 16
3057
3058
  ];
3058
- clock.wnOp = getBitU(b, 256, 8);
3059
+ clock2.wnOp = getBitU(b, 256, 8);
3059
3060
  }
3060
- return clock;
3061
+ return clock2;
3061
3062
  }
3062
3063
  function resolveWeek(week, towSec, sec) {
3063
3064
  if (sec < towSec - HALF_WEEK) return week + 1;
@@ -3095,10 +3096,10 @@ var CnavAssembler = class {
3095
3096
  return this.tryEmit(prn, sat);
3096
3097
  }
3097
3098
  tryEmit(prn, sat) {
3098
- const { t10, t11, clock, extras } = sat;
3099
- if (!t10 || !t11 || !clock) return null;
3100
- if (t10.toe !== t11.toe || clock.toc !== t10.toe) return null;
3101
- const key = `${t10.week}:${t10.toe}:${clock.af0}:${clock.af1}`;
3099
+ const { t10, t11, clock: clock2, extras } = sat;
3100
+ if (!t10 || !t11 || !clock2) return null;
3101
+ if (t10.toe !== t11.toe || clock2.toc !== t10.toe) return null;
3102
+ const key = `${t10.week}:${t10.toe}:${clock2.af0}:${clock2.af1}`;
3102
3103
  if (key === sat.lastKey) return null;
3103
3104
  sat.lastKey = key;
3104
3105
  const weekToe = resolveWeek(t10.week, t10.tow, t10.toe);
@@ -3109,9 +3110,9 @@ var CnavAssembler = class {
3109
3110
  week: t10.week,
3110
3111
  health: t10.health,
3111
3112
  uraEd: t10.uraEd,
3112
- uraNed0: clock.uraNed0,
3113
- uraNed1: clock.uraNed1,
3114
- uraNed2: clock.uraNed2,
3113
+ uraNed0: clock2.uraNed0,
3114
+ uraNed1: clock2.uraNed1,
3115
+ uraNed2: clock2.uraNed2,
3115
3116
  top: t10.top,
3116
3117
  toe: t10.toe,
3117
3118
  toeDate: gpsDate(weekToe, t10.toe),
@@ -3134,12 +3135,12 @@ var CnavAssembler = class {
3134
3135
  crc: t11.crc,
3135
3136
  cus: t11.cus,
3136
3137
  cuc: t11.cuc,
3137
- toc: clock.toc,
3138
- tocDate: gpsDate(weekToe, clock.toc),
3139
- af0: clock.af0,
3140
- af1: clock.af1,
3141
- af2: clock.af2,
3142
- clockMsgType: clock.msgType,
3138
+ toc: clock2.toc,
3139
+ tocDate: gpsDate(weekToe, clock2.toc),
3140
+ af0: clock2.af0,
3141
+ af1: clock2.af1,
3142
+ af2: clock2.af2,
3143
+ clockMsgType: clock2.msgType,
3143
3144
  tgd: extras?.tgd ?? null,
3144
3145
  iscL1ca: extras?.iscL1ca ?? null,
3145
3146
  iscL2c: extras?.iscL2c ?? null,
@@ -3155,6 +3156,304 @@ var CnavAssembler = class {
3155
3156
  }
3156
3157
  };
3157
3158
 
3159
+ // src/navbits/bds-cnav.ts
3160
+ var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
3161
+ var SEC_PER_WEEK3 = 7 * 86400;
3162
+ var HALF_WEEK2 = 302400;
3163
+ var BDS_CNAV_A_REF_MEO = 27906100;
3164
+ var BDS_CNAV_A_REF_IGSO_GEO = 42162200;
3165
+ function bdsCnavAref(satType) {
3166
+ return satType === 3 ? BDS_CNAV_A_REF_MEO : BDS_CNAV_A_REF_IGSO_GEO;
3167
+ }
3168
+ function ephI(b, p) {
3169
+ return {
3170
+ toe: getBitU(b, p, 11) * 300,
3171
+ satType: getBitU(b, p + 11, 2),
3172
+ deltaA: getBitS(b, p + 13, 26) * 2 ** -9,
3173
+ aDot: getBitS(b, p + 39, 25) * 2 ** -21,
3174
+ deltaN0: getBitS(b, p + 64, 17) * 2 ** -44 * GPS_PI,
3175
+ deltaN0Dot: getBitS(b, p + 81, 23) * 2 ** -57 * GPS_PI,
3176
+ m0: getBitS33(b, p + 104) * 2 ** -32 * GPS_PI,
3177
+ e: getBitU33(b, p + 137) * 2 ** -34,
3178
+ omega: getBitS33(b, p + 170) * 2 ** -32 * GPS_PI
3179
+ };
3180
+ }
3181
+ function ephII(b, p) {
3182
+ return {
3183
+ omega0: getBitS33(b, p) * 2 ** -32 * GPS_PI,
3184
+ i0: getBitS33(b, p + 33) * 2 ** -32 * GPS_PI,
3185
+ omegaDot: getBitS(b, p + 66, 19) * 2 ** -44 * GPS_PI,
3186
+ i0Dot: getBitS(b, p + 85, 15) * 2 ** -44 * GPS_PI,
3187
+ cis: getBitS(b, p + 100, 16) * 2 ** -30,
3188
+ cic: getBitS(b, p + 116, 16) * 2 ** -30,
3189
+ crs: getBitS(b, p + 132, 24) * 2 ** -8,
3190
+ crc: getBitS(b, p + 156, 24) * 2 ** -8,
3191
+ cus: getBitS(b, p + 180, 21) * 2 ** -30,
3192
+ cuc: getBitS(b, p + 201, 21) * 2 ** -30
3193
+ };
3194
+ }
3195
+ function clock(b, p) {
3196
+ return {
3197
+ toc: getBitU(b, p, 11) * 300,
3198
+ af0: getBitS(b, p + 11, 25) * 2 ** -34,
3199
+ af1: getBitS(b, p + 36, 22) * 2 ** -50,
3200
+ af2: getBitS(b, p + 58, 11) * 2 ** -66
3201
+ };
3202
+ }
3203
+ function sisaiOc(b, p) {
3204
+ return {
3205
+ top: getBitU(b, p, 11) * 300,
3206
+ ocb: getBitS(b, p + 11, 5),
3207
+ oc1: getBitS(b, p + 16, 3),
3208
+ oc2: getBitS(b, p + 19, 3)
3209
+ };
3210
+ }
3211
+ var delay = (b, p) => getBitS(b, p, 12) * 2 ** -34;
3212
+ function crcOk(msg, dataBits) {
3213
+ const need = Math.ceil((dataBits + 24) / 8);
3214
+ return msg.length >= need && crc24q(msg, dataBits) === getBitU(msg, dataBits, 24);
3215
+ }
3216
+ var bdsCnav1Sf2CrcOk = (sf2) => crcOk(sf2, 576);
3217
+ var bdsCnav1Sf3CrcOk = (sf3) => crcOk(sf3, 240);
3218
+ var bdsCnav2CrcOk = (msg) => crcOk(msg, 264);
3219
+ var bdsCnav3CrcOk = (msg) => crcOk(msg, 462);
3220
+ function resolveWeek2(week, towSec, sec) {
3221
+ if (sec < towSec - HALF_WEEK2) return week + 1;
3222
+ if (sec > towSec + HALF_WEEK2) return week - 1;
3223
+ return week;
3224
+ }
3225
+ function bdtDate(week, sec) {
3226
+ return new Date(BDT_EPOCH_MS + (week * SEC_PER_WEEK3 + sec) * 1e3);
3227
+ }
3228
+ var emptyStatus = () => ({
3229
+ top: null,
3230
+ sisaiOe: null,
3231
+ sisaiOcb: null,
3232
+ sisaiOc1: null,
3233
+ sisaiOc2: null,
3234
+ sismai: null,
3235
+ health: null,
3236
+ integrityFlags: null
3237
+ });
3238
+ function applyOc(s, oc) {
3239
+ s.top = oc.top;
3240
+ s.sisaiOcb = oc.ocb;
3241
+ s.sisaiOc1 = oc.oc1;
3242
+ s.sisaiOc2 = oc.oc2;
3243
+ }
3244
+ function build(message, prn, wn, tTm, e1, e22, c, d, s) {
3245
+ const week = resolveWeek2(wn, tTm, e1.toe);
3246
+ const weekToc = resolveWeek2(wn, tTm, c.toc);
3247
+ return {
3248
+ system: "C",
3249
+ prn,
3250
+ message,
3251
+ week,
3252
+ toe: e1.toe,
3253
+ toeDate: bdtDate(week, e1.toe),
3254
+ toc: c.toc,
3255
+ tocDate: bdtDate(weekToc, c.toc),
3256
+ satType: e1.satType,
3257
+ a: bdsCnavAref(e1.satType) + e1.deltaA,
3258
+ deltaA: e1.deltaA,
3259
+ aDot: e1.aDot,
3260
+ deltaN0: e1.deltaN0,
3261
+ deltaN0Dot: e1.deltaN0Dot,
3262
+ m0: e1.m0,
3263
+ e: e1.e,
3264
+ omega: e1.omega,
3265
+ ...e22,
3266
+ af0: c.af0,
3267
+ af1: c.af1,
3268
+ af2: c.af2,
3269
+ ...d,
3270
+ ...s,
3271
+ tow: tTm
3272
+ };
3273
+ }
3274
+ var prnLabel = (prn) => `C${String(prn).padStart(2, "0")}`;
3275
+ var BdsCnav1Assembler = class {
3276
+ status = /* @__PURE__ */ new Map();
3277
+ lastKey = /* @__PURE__ */ new Map();
3278
+ /**
3279
+ * Push one frame's CRC-valid subframes (bit-0 aligned; pass null for
3280
+ * a subframe that failed its CRC) for satellite `prn` (1-63). `tTm`
3281
+ * is the frame's transmit time, s of the BDT week. Returns the newly
3282
+ * seen ephemeris, or null.
3283
+ */
3284
+ push(prn, sf2, sf3, tTm) {
3285
+ let s = this.status.get(prn);
3286
+ if (!s) this.status.set(prn, s = emptyStatus());
3287
+ if (sf3) {
3288
+ const page = getBitU(sf3, 0, 6);
3289
+ if (page >= 1 && page <= 4) {
3290
+ s.health = getBitU(sf3, 6, 2);
3291
+ s.integrityFlags = getBitU(sf3, 8, 3);
3292
+ s.sismai = getBitU(sf3, 11, 4);
3293
+ if (page === 1) {
3294
+ s.sisaiOe = getBitU(sf3, 15, 5);
3295
+ applyOc(s, sisaiOc(sf3, 20));
3296
+ } else if (page === 3) s.sisaiOe = getBitU(sf3, 15, 5);
3297
+ else applyOc(s, sisaiOc(sf3, 15));
3298
+ }
3299
+ }
3300
+ if (!sf2) return null;
3301
+ const wn = getBitU(sf2, 0, 13);
3302
+ const iodc = getBitU(sf2, 21, 10);
3303
+ const iode = getBitU(sf2, 31, 8);
3304
+ const e1 = ephI(sf2, 39);
3305
+ const c = clock(sf2, 464);
3306
+ const key = `${wn}:${iodc}:${iode}:${e1.toe}:${c.toc}`;
3307
+ if (this.lastKey.get(prn) === key) return null;
3308
+ this.lastKey.set(prn, key);
3309
+ return build(
3310
+ "CNV1",
3311
+ prnLabel(prn),
3312
+ wn,
3313
+ tTm,
3314
+ e1,
3315
+ ephII(sf2, 242),
3316
+ c,
3317
+ {
3318
+ iode,
3319
+ iodc,
3320
+ tgdB1Cp: delay(sf2, 557),
3321
+ tgdB2ap: delay(sf2, 533),
3322
+ iscB1Cd: delay(sf2, 545),
3323
+ iscB2ad: null,
3324
+ tgdB2bI: null
3325
+ },
3326
+ { ...s }
3327
+ );
3328
+ }
3329
+ };
3330
+ var BdsCnav2Assembler = class {
3331
+ sats = /* @__PURE__ */ new Map();
3332
+ /**
3333
+ * Push one CRC-valid 288-bit message (bit 0 = first PRN bit, the
3334
+ * preamble stripped). Returns the newly completed ephemeris, or null.
3335
+ */
3336
+ push(msg) {
3337
+ const prn = getBitU(msg, 0, 6);
3338
+ const type = getBitU(msg, 6, 6);
3339
+ const sow = getBitU(msg, 12, 18) * 3;
3340
+ if (prn < 1) return null;
3341
+ let sat = this.sats.get(prn);
3342
+ if (!sat) this.sats.set(prn, sat = { status: emptyStatus() });
3343
+ const s = sat.status;
3344
+ if (type === 10) {
3345
+ sat.t10 = {
3346
+ wn: getBitU(msg, 30, 13),
3347
+ iode: getBitU(msg, 53, 8),
3348
+ e1: ephI(msg, 61),
3349
+ sow
3350
+ };
3351
+ s.integrityFlags = getBitU(msg, 43, 3) << 3 | getBitU(msg, 50, 3);
3352
+ s.sismai = getBitU(msg, 46, 4);
3353
+ if (sat.t11 && sat.t11.sow < sow) sat.t11 = void 0;
3354
+ } else if (type === 11 || type >= 30 && type <= 34 || type === 40) {
3355
+ s.health = getBitU(msg, 30, 2);
3356
+ s.integrityFlags = getBitU(msg, 32, 3) << 3 | getBitU(msg, 39, 3);
3357
+ s.sismai = getBitU(msg, 35, 4);
3358
+ if (type === 11) {
3359
+ if (!sat.t10 || sow <= sat.t10.sow) return null;
3360
+ sat.t11 = { e2: ephII(msg, 42), sow };
3361
+ } else if (type === 34) {
3362
+ applyOc(s, sisaiOc(msg, 42));
3363
+ sat.clock = { c: clock(msg, 64), iodc: getBitU(msg, 133, 10) };
3364
+ } else if (type === 40) {
3365
+ s.sisaiOe = getBitU(msg, 42, 5);
3366
+ applyOc(s, sisaiOc(msg, 47));
3367
+ return null;
3368
+ } else {
3369
+ sat.clock = { c: clock(msg, 42), iodc: getBitU(msg, 111, 10) };
3370
+ if (type === 30)
3371
+ sat.delays = {
3372
+ tgdB2ap: delay(msg, 121),
3373
+ iscB2ad: delay(msg, 133),
3374
+ tgdB1Cp: delay(msg, 219)
3375
+ };
3376
+ }
3377
+ } else return null;
3378
+ const { t10, t11, clock: ck } = sat;
3379
+ if (!t10 || !t11 || !ck || (ck.iodc & 255) !== t10.iode) return null;
3380
+ const key = `${t10.wn}:${ck.iodc}:${t10.iode}:${t10.e1.toe}:${ck.c.toc}`;
3381
+ if (sat.lastKey === key) return null;
3382
+ sat.lastKey = key;
3383
+ return build(
3384
+ "CNV2",
3385
+ prnLabel(prn),
3386
+ t10.wn,
3387
+ t10.sow,
3388
+ t10.e1,
3389
+ t11.e2,
3390
+ ck.c,
3391
+ {
3392
+ iode: t10.iode,
3393
+ iodc: ck.iodc,
3394
+ tgdB1Cp: sat.delays?.tgdB1Cp ?? null,
3395
+ tgdB2ap: sat.delays?.tgdB2ap ?? null,
3396
+ iscB1Cd: null,
3397
+ iscB2ad: sat.delays?.iscB2ad ?? null,
3398
+ tgdB2bI: null
3399
+ },
3400
+ { ...s }
3401
+ );
3402
+ }
3403
+ };
3404
+ var BdsCnav3Assembler = class {
3405
+ sats = /* @__PURE__ */ new Map();
3406
+ /**
3407
+ * Push one CRC-valid 486-bit message (bit 0 = first MesType bit) from
3408
+ * satellite `prn`. Returns the newly completed ephemeris, or null.
3409
+ */
3410
+ push(prn, msg) {
3411
+ const type = getBitU(msg, 0, 6);
3412
+ const sow = getBitU(msg, 6, 20);
3413
+ let sat = this.sats.get(prn);
3414
+ if (!sat) this.sats.set(prn, sat = { status: emptyStatus() });
3415
+ const s = sat.status;
3416
+ if (type === 10) {
3417
+ sat.t10 = { e1: ephI(msg, 30), e2: ephII(msg, 233), sow };
3418
+ s.integrityFlags = getBitU(msg, 455, 3);
3419
+ s.sismai = getBitU(msg, 458, 4);
3420
+ } else if (type === 30) {
3421
+ sat.t30 = {
3422
+ wn: getBitU(msg, 26, 13),
3423
+ c: clock(msg, 43),
3424
+ tgd: delay(msg, 112)
3425
+ };
3426
+ applyOc(s, sisaiOc(msg, 433));
3427
+ s.sisaiOe = getBitU(msg, 455, 5);
3428
+ s.health = getBitU(msg, 460, 2);
3429
+ } else return null;
3430
+ const { t10, t30 } = sat;
3431
+ if (!t10 || !t30 || t10.e1.toe !== t30.c.toc) return null;
3432
+ const key = `${t30.wn}:${t10.e1.toe}:${t30.c.af0}:${t10.e1.m0}`;
3433
+ if (sat.lastKey === key) return null;
3434
+ sat.lastKey = key;
3435
+ return build(
3436
+ "CNV3",
3437
+ prnLabel(prn),
3438
+ t30.wn,
3439
+ t10.sow,
3440
+ t10.e1,
3441
+ t10.e2,
3442
+ t30.c,
3443
+ {
3444
+ iode: null,
3445
+ iodc: null,
3446
+ tgdB1Cp: null,
3447
+ tgdB2ap: null,
3448
+ iscB1Cd: null,
3449
+ iscB2ad: null,
3450
+ tgdB2bI: t30.tgd
3451
+ },
3452
+ { ...s }
3453
+ );
3454
+ }
3455
+ };
3456
+
3158
3457
  // src/rinex/nav.ts
3159
3458
  var DATA_LINES = {
3160
3459
  G: 7,
@@ -3180,16 +3479,17 @@ var R4_MSG_LINES = {
3180
3479
  // RINEX 4 adds orbit-4 (status flags) vs 3 in RINEX 3
3181
3480
  // EPH type parsed into RinexCnavEphemeris for G/J (skipped otherwise)
3182
3481
  CNAV: 9,
3183
- // EPH types we skip (different field layout)
3482
+ // BeiDou-3 B-CNAV, parsed into BdsCnavEphemeris
3184
3483
  CNV1: 10,
3185
3484
  CNV2: 10,
3186
3485
  CNV3: 9,
3486
+ // EPH types we skip (different field layout)
3187
3487
  L1NV: 8,
3188
3488
  L1OC: 9,
3189
3489
  L3OC: 9
3190
3490
  };
3191
3491
  var GPS_EPOCH_MS3 = Date.UTC(1980, 0, 6);
3192
- var SEC_PER_WEEK3 = 7 * 86400;
3492
+ var SEC_PER_WEEK4 = 7 * 86400;
3193
3493
  var QZSS_CNAV_A_REF = 42164200;
3194
3494
  var SUPPORTED_EPH_MSGS = /* @__PURE__ */ new Set([
3195
3495
  "LNAV",
@@ -3334,8 +3634,8 @@ function buildCnavEphemeris(sys, prn, date, epochVals, d) {
3334
3634
  const num = (r, c) => d[r]?.[c] ?? 0;
3335
3635
  const opt = (r, c) => d[r]?.[c] ?? null;
3336
3636
  const t = (date.getTime() - GPS_EPOCH_MS3) / 1e3;
3337
- const week = Math.floor(t / SEC_PER_WEEK3);
3338
- const toc = t - week * SEC_PER_WEEK3;
3637
+ const week = Math.floor(t / SEC_PER_WEEK4);
3638
+ const toc = t - week * SEC_PER_WEEK4;
3339
3639
  const sqrtA = num(1, 3);
3340
3640
  const a = sqrtA * sqrtA;
3341
3641
  const aRef = sys === "J" ? QZSS_CNAV_A_REF : CNAV_A_REF;
@@ -3392,6 +3692,68 @@ function buildCnavEphemeris(sys, prn, date, epochVals, d) {
3392
3692
  tow: num(7, 0)
3393
3693
  };
3394
3694
  }
3695
+ var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
3696
+ function buildBdsCnavEphemeris(message, prn, date, epochVals, d) {
3697
+ const num = (r, c) => d[r]?.[c] ?? 0;
3698
+ const opt = (r, c) => d[r]?.[c] ?? null;
3699
+ const cnv3 = message === "CNV3";
3700
+ const t = (date.getTime() - BDT_EPOCH_MS2) / 1e3;
3701
+ const weekToc = Math.floor(t / SEC_PER_WEEK4);
3702
+ const toc = t - weekToc * SEC_PER_WEEK4;
3703
+ const toe = num(2, 0);
3704
+ const week = toe - toc > SEC_PER_WEEK4 / 2 ? weekToc - 1 : toc - toe > SEC_PER_WEEK4 / 2 ? weekToc + 1 : weekToc;
3705
+ const sqrtA = num(1, 3);
3706
+ const a = sqrtA * sqrtA;
3707
+ const satType = num(4, 2);
3708
+ return {
3709
+ system: "C",
3710
+ prn,
3711
+ message,
3712
+ week,
3713
+ toe,
3714
+ toeDate: new Date(BDT_EPOCH_MS2 + (week * SEC_PER_WEEK4 + toe) * 1e3),
3715
+ toc,
3716
+ tocDate: date,
3717
+ satType,
3718
+ a,
3719
+ deltaA: a - bdsCnavAref(satType),
3720
+ aDot: num(0, 0),
3721
+ deltaN0: num(0, 2),
3722
+ deltaN0Dot: num(4, 1),
3723
+ m0: num(0, 3),
3724
+ e: num(1, 1),
3725
+ omega: num(3, 2),
3726
+ omega0: num(2, 2),
3727
+ i0: num(3, 0),
3728
+ omegaDot: num(3, 3),
3729
+ i0Dot: num(4, 0),
3730
+ cis: num(2, 3),
3731
+ cic: num(2, 1),
3732
+ crs: num(0, 1),
3733
+ crc: num(3, 1),
3734
+ cus: num(1, 2),
3735
+ cuc: num(1, 0),
3736
+ af0: epochVals[0] ?? 0,
3737
+ af1: epochVals[1] ?? 0,
3738
+ af2: epochVals[2] ?? 0,
3739
+ iode: cnv3 ? null : opt(8, 3),
3740
+ iodc: cnv3 ? null : opt(7, 3),
3741
+ tgdB1Cp: cnv3 ? null : opt(6, 2),
3742
+ tgdB2ap: cnv3 ? null : opt(6, 3),
3743
+ iscB1Cd: cnv3 ? null : opt(6, 0),
3744
+ iscB2ad: cnv3 ? null : opt(6, 1),
3745
+ tgdB2bI: cnv3 ? opt(6, 3) : null,
3746
+ top: opt(4, 3),
3747
+ sisaiOe: opt(5, 0),
3748
+ sisaiOcb: opt(5, 1),
3749
+ sisaiOc1: opt(5, 2),
3750
+ sisaiOc2: opt(5, 3),
3751
+ sismai: cnv3 ? opt(6, 0) : opt(7, 0),
3752
+ health: cnv3 ? opt(6, 1) : opt(7, 1),
3753
+ integrityFlags: cnv3 ? opt(6, 2) : opt(7, 2),
3754
+ tow: cnv3 ? num(7, 0) : num(8, 0)
3755
+ };
3756
+ }
3395
3757
  function parseNavFile(text) {
3396
3758
  const lines = text.split("\n");
3397
3759
  const header = {
@@ -3402,6 +3764,7 @@ function parseNavFile(text) {
3402
3764
  };
3403
3765
  const ephemerides = [];
3404
3766
  const cnav = [];
3767
+ const bdsCnav = [];
3405
3768
  let inHeader = true;
3406
3769
  let i = 0;
3407
3770
  while (i < lines.length) {
@@ -3470,7 +3833,8 @@ function parseNavFile(text) {
3470
3833
  continue;
3471
3834
  }
3472
3835
  const isCnav = msgType === "CNAV" && (svSys === "G" || svSys === "J");
3473
- if (!SUPPORTED_EPH_MSGS.has(msgType) && !isCnav) {
3836
+ const isBdsCnav = svSys === "C" && (msgType === "CNV1" || msgType === "CNV2" || msgType === "CNV3");
3837
+ if (!SUPPORTED_EPH_MSGS.has(msgType) && !isCnav && !isBdsCnav) {
3474
3838
  const totalLines = R4_MSG_LINES[msgType] ?? 2;
3475
3839
  i += totalLines + 1;
3476
3840
  continue;
@@ -3482,17 +3846,34 @@ function parseNavFile(text) {
3482
3846
  const sys2 = epochLine.charAt(0);
3483
3847
  const parsed = parseNavEpochV3(epochLine);
3484
3848
  const prn2 = `${sys2}${parsed.prn.substring(1)}`;
3485
- if (isCnav) {
3849
+ if (isCnav || isBdsCnav) {
3486
3850
  const slotLines = [];
3487
3851
  for (let j = 0; j < numDataLines2; j++) {
3488
3852
  i++;
3489
3853
  if (i >= lines.length) break;
3490
3854
  slotLines.push(parseDataLineSlots(lines[i]));
3491
3855
  }
3492
- if (slotLines.length === numDataLines2 && (sys2 === "G" || sys2 === "J")) {
3493
- cnav.push(
3494
- buildCnavEphemeris(sys2, prn2, parsed.date, parsed.values, slotLines)
3495
- );
3856
+ if (slotLines.length === numDataLines2) {
3857
+ if (isBdsCnav && sys2 === "C")
3858
+ bdsCnav.push(
3859
+ buildBdsCnavEphemeris(
3860
+ msgType,
3861
+ prn2,
3862
+ parsed.date,
3863
+ parsed.values,
3864
+ slotLines
3865
+ )
3866
+ );
3867
+ else if (sys2 === "G" || sys2 === "J")
3868
+ cnav.push(
3869
+ buildCnavEphemeris(
3870
+ sys2,
3871
+ prn2,
3872
+ parsed.date,
3873
+ parsed.values,
3874
+ slotLines
3875
+ )
3876
+ );
3496
3877
  }
3497
3878
  i++;
3498
3879
  continue;
@@ -3570,7 +3951,12 @@ function parseNavFile(text) {
3570
3951
  }
3571
3952
  i++;
3572
3953
  }
3573
- return cnav.length > 0 ? { header, ephemerides, cnav } : { header, ephemerides };
3954
+ return {
3955
+ header,
3956
+ ephemerides,
3957
+ ...cnav.length > 0 && { cnav },
3958
+ ...bdsCnav.length > 0 && { bdsCnav }
3959
+ };
3574
3960
  }
3575
3961
 
3576
3962
  // src/rinex/nav-writer.ts
@@ -3766,6 +4152,30 @@ function cnavRecord(eph) {
3766
4152
  orbitLine4([eph.tow, resolveWnOp(eph.week, eph.wnOp)])
3767
4153
  ].join("\n");
3768
4154
  }
4155
+ function bdsCnavRecord(eph) {
4156
+ const lines = [
4157
+ epochLine4(eph.prn, eph.tocDate, [eph.af0, eph.af1, eph.af2]),
4158
+ orbitLine4([eph.aDot, eph.crs, eph.deltaN0, eph.m0]),
4159
+ orbitLine4([eph.cuc, eph.e, eph.cus, Math.sqrt(eph.a)]),
4160
+ orbitLine4([eph.toe, eph.cic, eph.omega0, eph.cis]),
4161
+ orbitLine4([eph.i0, eph.crc, eph.omega, eph.omegaDot]),
4162
+ orbitLine4([eph.i0Dot, eph.deltaN0Dot, eph.satType, eph.top]),
4163
+ orbitLine4([eph.sisaiOe, eph.sisaiOcb, eph.sisaiOc1, eph.sisaiOc2])
4164
+ ];
4165
+ if (eph.message === "CNV3") {
4166
+ lines.push(
4167
+ orbitLine4([eph.sismai, eph.health, eph.integrityFlags, eph.tgdB2bI]),
4168
+ orbitLine4([eph.tow])
4169
+ );
4170
+ } else {
4171
+ lines.push(
4172
+ orbitLine4([eph.iscB1Cd, eph.iscB2ad, eph.tgdB1Cp, eph.tgdB2ap]),
4173
+ orbitLine4([eph.sismai, eph.health, eph.integrityFlags, eph.iodc]),
4174
+ orbitLine4([eph.tow, null, null, eph.iode])
4175
+ );
4176
+ }
4177
+ return lines.join("\n");
4178
+ }
3769
4179
  var KLOBUCHAR_SOURCES = [
3770
4180
  ["GPSA", "GPSB", "G", "LNAV"],
3771
4181
  ["QZSA", "QZSB", "J", "LNAV"],
@@ -3843,6 +4253,15 @@ function writeRinexNav4(nav) {
3843
4253
  ` + cnavRecord(eph)
3844
4254
  });
3845
4255
  }
4256
+ for (const eph of nav.bdsCnav ?? []) {
4257
+ records.push({
4258
+ prn: eph.prn,
4259
+ time: eph.tocDate.getTime(),
4260
+ label: eph.message,
4261
+ body: `> EPH ${padL(eph.prn, 3)} ${eph.message}
4262
+ ` + bdsCnavRecord(eph)
4263
+ });
4264
+ }
3846
4265
  records.sort(
3847
4266
  (a, b) => a.prn.localeCompare(b.prn) || a.time - b.time || a.label.localeCompare(b.label)
3848
4267
  );
@@ -4189,6 +4608,50 @@ function findSatBias(bias, prn, obs1, obs2, tMs) {
4189
4608
  }
4190
4609
  return null;
4191
4610
  }
4611
+ var SatOsbTable = class {
4612
+ byKey = /* @__PURE__ */ new Map();
4613
+ constructor(bias) {
4614
+ const recs = Array.isArray(bias) ? bias : bias.records;
4615
+ const groups = /* @__PURE__ */ new Map();
4616
+ for (const r of recs) {
4617
+ if (r.type !== "OSB" || !r.prn || r.station) continue;
4618
+ if (r.unit !== "ns") continue;
4619
+ const k = `${r.prn} ${r.obs1}`;
4620
+ let g = groups.get(k);
4621
+ if (!g) groups.set(k, g = []);
4622
+ g.push(r);
4623
+ }
4624
+ for (const [k, g] of groups) {
4625
+ g.sort((a, b) => a.startMs - b.startMs);
4626
+ this.byKey.set(k, {
4627
+ start: Float64Array.from(g, (r) => r.startMs),
4628
+ end: Float64Array.from(g, (r) => r.endMs),
4629
+ m: Float64Array.from(g, biasMetres)
4630
+ });
4631
+ }
4632
+ }
4633
+ /** Whether any phase (L…) OSB is present — i.e. the product supports AR. */
4634
+ get hasPhase() {
4635
+ for (const k of this.byKey.keys())
4636
+ if (k.split(" ")[1][0] === "L") return true;
4637
+ return false;
4638
+ }
4639
+ /** OSB (m) of `prn`'s observable `obs` (e.g. 'C1W', 'L2W') at `tMs`;
4640
+ * null when the product has none valid then. */
4641
+ get(prn, obs, tMs) {
4642
+ const e = this.byKey.get(`${prn} ${obs}`) ?? this.byKey.get(`${prn} ${obs.slice(0, 2)}X`);
4643
+ if (!e) return null;
4644
+ let lo = 0;
4645
+ let hi = e.start.length - 1;
4646
+ if (tMs < e.start[0]) return null;
4647
+ while (lo < hi) {
4648
+ const mid = lo + hi + 1 >> 1;
4649
+ if (e.start[mid] <= tMs) lo = mid;
4650
+ else hi = mid - 1;
4651
+ }
4652
+ return tMs <= e.end[lo] ? e.m[lo] : null;
4653
+ }
4654
+ };
4192
4655
 
4193
4656
  // src/rinex/obs-writer-common.ts
4194
4657
  function createGzipLineSink() {
@@ -6937,11 +7400,11 @@ function parseUbxCnav(data) {
6937
7400
  }
6938
7401
 
6939
7402
  // src/navbits/bds.ts
6940
- var BDT_EPOCH_MS = Date.UTC(2006, 0, 1);
6941
- var SEC_PER_WEEK4 = 7 * 86400;
6942
- var MS_PER_WEEK2 = SEC_PER_WEEK4 * 1e3;
6943
- var HALF_WEEK2 = 302400;
6944
- var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK4;
7403
+ var BDT_EPOCH_MS3 = Date.UTC(2006, 0, 1);
7404
+ var SEC_PER_WEEK5 = 7 * 86400;
7405
+ var MS_PER_WEEK2 = SEC_PER_WEEK5 * 1e3;
7406
+ var HALF_WEEK3 = 302400;
7407
+ var sowOf = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK5;
6945
7408
  var BDS_SUBFRAME_BYTES = 38;
6946
7409
  function getBitU2(b, p1, l1, p2, l2) {
6947
7410
  return getBitU(b, p1, l1) * 2 ** l2 + getBitU(b, p2, l2);
@@ -6975,9 +7438,9 @@ function bdsSubframeParityOk(subframe) {
6975
7438
  }
6976
7439
  function buildBdsEphemeris(prn, f) {
6977
7440
  let week = f.week;
6978
- if (f.toes < f.sow - HALF_WEEK2) week++;
6979
- else if (f.toes > f.sow + HALF_WEEK2) week--;
6980
- const tocDate = new Date(BDT_EPOCH_MS + week * MS_PER_WEEK2 + f.tocSec * 1e3);
7441
+ if (f.toes < f.sow - HALF_WEEK3) week++;
7442
+ else if (f.toes > f.sow + HALF_WEEK3) week--;
7443
+ const tocDate = new Date(BDT_EPOCH_MS3 + week * MS_PER_WEEK2 + f.tocSec * 1e3);
6981
7444
  return {
6982
7445
  system: "C",
6983
7446
  prn,
@@ -7216,8 +7679,8 @@ var BdsAssembler = class {
7216
7679
 
7217
7680
  // src/navbits/gal.ts
7218
7681
  var GPS_EPOCH_MS5 = Date.UTC(1980, 0, 6);
7219
- var SEC_PER_WEEK5 = 7 * 86400;
7220
- var HALF_WEEK3 = 302400;
7682
+ var SEC_PER_WEEK6 = 7 * 86400;
7683
+ var HALF_WEEK4 = 302400;
7221
7684
  var GST_GPS_WEEK_OFFSET = 1024;
7222
7685
  function galInavPageCrcOk(page) {
7223
7686
  if (page.length < 28) return false;
@@ -7228,11 +7691,11 @@ function galFnavPageCrcOk(page) {
7228
7691
  return crc24q(page, 214) === getBitU(page, 214, 24);
7229
7692
  }
7230
7693
  function gpsDate2(week, sec) {
7231
- return new Date(GPS_EPOCH_MS5 + (week * SEC_PER_WEEK5 + sec) * 1e3);
7694
+ return new Date(GPS_EPOCH_MS5 + (week * SEC_PER_WEEK6 + sec) * 1e3);
7232
7695
  }
7233
7696
  function weekOfToe(week, tow, toe) {
7234
- if (toe - tow > HALF_WEEK3) return week - 1;
7235
- if (toe - tow < -HALF_WEEK3) return week + 1;
7697
+ if (toe - tow > HALF_WEEK4) return week - 1;
7698
+ if (toe - tow < -HALF_WEEK4) return week + 1;
7236
7699
  return week;
7237
7700
  }
7238
7701
  var two4 = (n) => String(n).padStart(2, "0");
@@ -7324,7 +7787,7 @@ function decodeGalInavWords(words) {
7324
7787
  return {
7325
7788
  system: "E",
7326
7789
  prn: `E${two4(svid)}`,
7327
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
7790
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK6,
7328
7791
  tocDate,
7329
7792
  af0,
7330
7793
  af1,
@@ -7432,7 +7895,7 @@ function decodeGalFnavPages(pages) {
7432
7895
  return {
7433
7896
  system: "E",
7434
7897
  prn: `E${two4(svid)}`,
7435
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK5,
7898
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK6,
7436
7899
  tocDate,
7437
7900
  af0,
7438
7901
  af1,
@@ -7524,7 +7987,7 @@ var GalFnavAssembler = class {
7524
7987
 
7525
7988
  // src/navbits/glo.ts
7526
7989
  var GPS_EPOCH_MS6 = Date.UTC(1980, 0, 6);
7527
- var SEC_PER_WEEK6 = 7 * 86400;
7990
+ var SEC_PER_WEEK7 = 7 * 86400;
7528
7991
  var SEC_PER_DAY = 86400;
7529
7992
  var GLO_STRING_BYTES = 10;
7530
7993
  function getBitG(b, pos, len) {
@@ -7618,8 +8081,8 @@ function decodeGloStrings(strings, refDate, opts = {}) {
7618
8081
  if (frn1 !== 1 || frn2 !== 2 || frn3 !== 3 || frn4 !== 4) return null;
7619
8082
  if (slot < 1 || slot > 27) return null;
7620
8083
  const utcSec = (getUtcDate(refDate).getTime() - GPS_EPOCH_MS6) / 1e3;
7621
- const week = Math.floor(utcSec / SEC_PER_WEEK6);
7622
- let tow = utcSec - week * SEC_PER_WEEK6;
8084
+ const week = Math.floor(utcSec / SEC_PER_WEEK7);
8085
+ let tow = utcSec - week * SEC_PER_WEEK7;
7623
8086
  const tod = tow % SEC_PER_DAY;
7624
8087
  tow -= tod;
7625
8088
  let tof = tkH * 3600 + tkM * 60 + tkS - 10800;
@@ -7632,12 +8095,12 @@ function decodeGloStrings(strings, refDate, opts = {}) {
7632
8095
  system: "R",
7633
8096
  prn: `R${String(slot).padStart(2, "0")}`,
7634
8097
  // RINEX GLONASS epochs are UTC: build the UTC toe Date directly.
7635
- tocDate: new Date(GPS_EPOCH_MS6 + (week * SEC_PER_WEEK6 + tow + toe) * 1e3),
8098
+ tocDate: new Date(GPS_EPOCH_MS6 + (week * SEC_PER_WEEK7 + tow + toe) * 1e3),
7636
8099
  tauN: -tauN,
7637
8100
  // RINEX stores −τn; the SIS carries τn (ICD sign)
7638
8101
  gammaN,
7639
8102
  // v3 message frame time: seconds of the UTC week (RTKLIB tof).
7640
- messageFrameTime: ((tow + tof) % SEC_PER_WEEK6 + SEC_PER_WEEK6) % SEC_PER_WEEK6,
8103
+ messageFrameTime: ((tow + tof) % SEC_PER_WEEK7 + SEC_PER_WEEK7) % SEC_PER_WEEK7,
7641
8104
  x,
7642
8105
  xDot,
7643
8106
  xAcc,
@@ -7730,7 +8193,7 @@ function decodeSbasGeoNav(msg, prn, week, tow) {
7730
8193
 
7731
8194
  // src/ubx/rawnav.ts
7732
8195
  var GPS_EPOCH_MS8 = Date.UTC(1980, 0, 6);
7733
- var SEC_PER_WEEK7 = 7 * 86400;
8196
+ var SEC_PER_WEEK8 = 7 * 86400;
7734
8197
  var two6 = (n) => String(n).padStart(2, "0");
7735
8198
  function parseUbxRawNav(data, opts = {}) {
7736
8199
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
@@ -7752,7 +8215,7 @@ function parseUbxRawNav(data, opts = {}) {
7752
8215
  if (week === 0 && opts.refWeek !== void 0) week = opts.refWeek;
7753
8216
  if (week > 0) {
7754
8217
  refDate = new Date(
7755
- GPS_EPOCH_MS8 + (week * SEC_PER_WEEK7 + rcvTow) * 1e3
8218
+ GPS_EPOCH_MS8 + (week * SEC_PER_WEEK8 + rcvTow) * 1e3
7756
8219
  );
7757
8220
  refWeek = week;
7758
8221
  refTow = rcvTow;
@@ -8066,9 +8529,9 @@ function svidToPrn(svid) {
8066
8529
  // src/sbf/nav.ts
8067
8530
  var PI2 = Math.PI;
8068
8531
  var GPS_EPOCH_MS11 = Date.UTC(1980, 0, 6);
8069
- var BDT_EPOCH_MS2 = Date.UTC(2006, 0, 1);
8070
- var SEC_PER_WEEK8 = 7 * 86400;
8071
- var MS_PER_WEEK5 = SEC_PER_WEEK8 * 1e3;
8532
+ var BDT_EPOCH_MS4 = Date.UTC(2006, 0, 1);
8533
+ var SEC_PER_WEEK9 = 7 * 86400;
8534
+ var MS_PER_WEEK5 = SEC_PER_WEEK9 * 1e3;
8072
8535
  var F4_DNU = -2e10;
8073
8536
  var U4_DNU = 4294967295;
8074
8537
  function adjustWeek(refWeek, wn, mod) {
@@ -8078,7 +8541,7 @@ function adjustWeek(refWeek, wn, mod) {
8078
8541
  return refWeek - offset;
8079
8542
  }
8080
8543
  var gpsMs = (week, sec) => GPS_EPOCH_MS11 + week * MS_PER_WEEK5 + sec * 1e3;
8081
- var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK8;
8544
+ var sowOf2 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK9;
8082
8545
  function decodeGpsQzsNav(view, b, sys) {
8083
8546
  const svid = view.getUint8(b + 14);
8084
8547
  const prn = sys === "G" ? svid : svid - 180;
@@ -8181,7 +8644,7 @@ function decodeBdsNav(view, b) {
8181
8644
  const toes = view.getUint32(b + 88, true);
8182
8645
  const wnToc = adjustWeek(bdsWeekRef, view.getUint16(b + 136, true), 8192);
8183
8646
  const wnToe = adjustWeek(bdsWeekRef, view.getUint16(b + 138, true), 8192);
8184
- const tocDate = new Date(BDT_EPOCH_MS2 + wnToc * MS_PER_WEEK5 + tocs * 1e3);
8647
+ const tocDate = new Date(BDT_EPOCH_MS4 + wnToc * MS_PER_WEEK5 + tocs * 1e3);
8185
8648
  return {
8186
8649
  system: "C",
8187
8650
  prn,
@@ -8227,7 +8690,7 @@ function decodeGloNav(view, b) {
8227
8690
  const tocDate = new Date(toeGpsMs - leapMs);
8228
8691
  const tofGpsMs = gpsMs(wnc, 0) + view.getUint32(b + 8, true);
8229
8692
  const tofLeapMs = getGpsLeap(new Date(tofGpsMs)) * 1e3;
8230
- const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS11) / 1e3 % SEC_PER_WEEK8;
8693
+ const messageFrameTime = (tofGpsMs - tofLeapMs - GPS_EPOCH_MS11) / 1e3 % SEC_PER_WEEK9;
8231
8694
  return {
8232
8695
  system: "R",
8233
8696
  prn,
@@ -8550,7 +9013,7 @@ function parseSbfGpsNav(data) {
8550
9013
 
8551
9014
  // src/navbits/navic.ts
8552
9015
  var GPS_EPOCH_MS13 = Date.UTC(1980, 0, 6);
8553
- var SEC_PER_WEEK9 = 604800;
9016
+ var SEC_PER_WEEK10 = 604800;
8554
9017
  var DATA = 30;
8555
9018
  function navicSubframeCrcOk(sf) {
8556
9019
  return crc24q(sf, 262) === getBitU(sf, 262, 24);
@@ -8612,16 +9075,16 @@ function decodeNavicSubframes(sf1, sf2, opts = {}) {
8612
9075
  j += 22;
8613
9076
  const i0 = getBitS(sf2, j, 32) * 2 ** -31 * GPS_PI;
8614
9077
  if (sqrtA <= 0 || e >= 1) return null;
8615
- const week = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS13) / 1e3 / SEC_PER_WEEK9);
9078
+ const week = opts.refWeek ?? Math.floor((Date.now() - GPS_EPOCH_MS13) / 1e3 / SEC_PER_WEEK10);
8616
9079
  const prn = opts.prn ?? "I00";
8617
9080
  const tocDate = new Date(
8618
- GPS_EPOCH_MS13 + (week * SEC_PER_WEEK9 + tocSec) * 1e3
9081
+ GPS_EPOCH_MS13 + (week * SEC_PER_WEEK10 + tocSec) * 1e3
8619
9082
  );
8620
9083
  return {
8621
9084
  system: "I",
8622
9085
  prn,
8623
9086
  tocDate,
8624
- toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK9,
9087
+ toc: tocDate.getTime() / 1e3 % SEC_PER_WEEK10,
8625
9088
  af0,
8626
9089
  af1,
8627
9090
  af2,
@@ -8896,6 +9359,80 @@ function parseSbfGloNav(data) {
8896
9359
  return { ephemerides, badCrc, messages };
8897
9360
  }
8898
9361
 
9362
+ // src/sbf/rawnav-bds-cnav.ts
9363
+ var GPS_BDT_OFFSET = 14;
9364
+ var B1C_FRAME_S = 18;
9365
+ var TOW_DNU2 = 4294967295;
9366
+ function newBdsCnavAssemblers() {
9367
+ return {
9368
+ b1c: new BdsCnav1Assembler(),
9369
+ b2a: new BdsCnav2Assembler(),
9370
+ b2b: new BdsCnav3Assembler()
9371
+ };
9372
+ }
9373
+ function navBits(view, wordsAt, start, nBits) {
9374
+ const out = new Uint8Array(Math.ceil(nBits / 8));
9375
+ for (let i = 0; i < nBits; i++) {
9376
+ const p = start + i;
9377
+ const w = view.getUint32(wordsAt + 4 * (p >>> 5), true);
9378
+ if (w >>> 31 - (p & 31) & 1) out[i >> 3] |= 128 >> (i & 7);
9379
+ }
9380
+ return out;
9381
+ }
9382
+ var MIN_LEN = { 4218: 248, 4219: 92, 4242: 144 };
9383
+ function feedBdsCnavBlock(view, b, id, len, asm) {
9384
+ if (len < (MIN_LEN[id] ?? Infinity)) return {};
9385
+ const prnStr = svidToPrn(view.getUint8(b + 14));
9386
+ if (!prnStr || prnStr[0] !== "C") return {};
9387
+ const prn = Number(prnStr.slice(1));
9388
+ const words = b + 20;
9389
+ if (id === 4218) {
9390
+ const towMs = view.getUint32(b + 8, true);
9391
+ if (towMs === TOW_DNU2) return {};
9392
+ const sf2 = navBits(view, words, 72, 600);
9393
+ const sf3 = navBits(view, words, 1272, 264);
9394
+ const sf2Ok = bdsCnav1Sf2CrcOk(sf2);
9395
+ const bdt = (towMs / 1e3 - GPS_BDT_OFFSET + 604800) % 604800;
9396
+ const tTm = (Math.floor(bdt) - B1C_FRAME_S + 604800) % 604800;
9397
+ const eph = asm.b1c.push(
9398
+ prn,
9399
+ sf2Ok ? sf2 : null,
9400
+ bdsCnav1Sf3CrcOk(sf3) ? sf3 : null,
9401
+ tTm
9402
+ );
9403
+ if (!sf2Ok) return { badCrc: true };
9404
+ return eph ? { eph } : {};
9405
+ }
9406
+ if (id === 4219) {
9407
+ const msg = navBits(view, words, 0, 288);
9408
+ if (!bdsCnav2CrcOk(msg)) return { badCrc: true };
9409
+ const eph = asm.b2a.push(msg);
9410
+ return eph ? { eph } : {};
9411
+ }
9412
+ if (id === 4242) {
9413
+ const msg = navBits(view, words, 12, 486);
9414
+ if (!bdsCnav3CrcOk(msg)) return { badCrc: true };
9415
+ const eph = asm.b2b.push(prn, msg);
9416
+ return eph ? { eph } : {};
9417
+ }
9418
+ return {};
9419
+ }
9420
+ function parseSbfBdsCnav(data) {
9421
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
9422
+ const asm = newBdsCnavAssemblers();
9423
+ const ephemerides = [];
9424
+ let badCrc = 0;
9425
+ let messages = 0;
9426
+ scanSbfFrames(data, view, (id, b, len) => {
9427
+ if (id !== 4218 && id !== 4219 && id !== 4242) return;
9428
+ messages++;
9429
+ const r = feedBdsCnavBlock(view, b, id, len, asm);
9430
+ if (r.eph) ephemerides.push(r.eph);
9431
+ else if (r.badCrc) badCrc++;
9432
+ });
9433
+ return { ephemerides, badCrc, messages };
9434
+ }
9435
+
8899
9436
  // src/navbits/sbas-l5.ts
8900
9437
  var SBAS_L5_PREAMBLE_UW = 6056250;
8901
9438
  function isSbasL1Preamble(msg) {
@@ -9038,6 +9575,7 @@ function decodeSbfNavigation(data, opts = {}) {
9038
9575
  const ephemerides = [];
9039
9576
  const seen = /* @__PURE__ */ new Set();
9040
9577
  const cnav = [];
9578
+ const bdsCnav = [];
9041
9579
  const ionoCorrections = {};
9042
9580
  let leapSeconds = null;
9043
9581
  const counts = {
@@ -9047,6 +9585,7 @@ function decodeSbfNavigation(data, opts = {}) {
9047
9585
  galRaw: 0,
9048
9586
  gloRaw: 0,
9049
9587
  bdsRaw: 0,
9588
+ bdsCnavRaw: 0,
9050
9589
  sbasRaw: 0,
9051
9590
  sbasL5Raw: 0,
9052
9591
  navicRaw: 0,
@@ -9058,6 +9597,7 @@ function decodeSbfNavigation(data, opts = {}) {
9058
9597
  const galAsm = newGalAssemblers();
9059
9598
  const gloAsm = new GloStringAssembler();
9060
9599
  const bdsAsm = new BdsAssembler();
9600
+ const bdsCnavAsm = newBdsCnavAssemblers();
9061
9601
  const lnavAsm = new GpsLnavAssembler();
9062
9602
  const navicAsm = new NavicAssembler();
9063
9603
  const addEph = (e) => {
@@ -9164,6 +9704,17 @@ function decodeSbfNavigation(data, opts = {}) {
9164
9704
  else if (r.badCrc) counts.badFrames++;
9165
9705
  return;
9166
9706
  }
9707
+ case 4218:
9708
+ // BDSRawB1C
9709
+ case 4219:
9710
+ // BDSRawB2a
9711
+ case 4242: {
9712
+ counts.bdsCnavRaw++;
9713
+ const r = feedBdsCnavBlock(view, b, id, len, bdsCnavAsm);
9714
+ if (r.eph) bdsCnav.push(r.eph);
9715
+ else if (r.badCrc) counts.badFrames++;
9716
+ return;
9717
+ }
9167
9718
  case 4020: {
9168
9719
  if (len < 52) return;
9169
9720
  counts.sbasRaw++;
@@ -9220,7 +9771,15 @@ function decodeSbfNavigation(data, opts = {}) {
9220
9771
  prns: [...dfmcPrns].sort(),
9221
9772
  messages: Object.values(dfmcByType).reduce((a, v) => a + v, 0)
9222
9773
  };
9223
- return { ephemerides, cnav, ionoCorrections, leapSeconds, dfmc, counts };
9774
+ return {
9775
+ ephemerides,
9776
+ cnav,
9777
+ bdsCnav,
9778
+ ionoCorrections,
9779
+ leapSeconds,
9780
+ dfmc,
9781
+ counts
9782
+ };
9224
9783
  }
9225
9784
 
9226
9785
  // src/navbits/rs255.ts
@@ -10470,12 +11029,12 @@ var ID_BDSEPHEMERIS = 1696;
10470
11029
  var ID_RAWWAASFRAME = 287;
10471
11030
  var ID_RAWSBASFRAME = 973;
10472
11031
  var GPS_EPOCH_MS16 = Date.UTC(1980, 0, 6);
10473
- var BDT_EPOCH_MS3 = Date.UTC(2006, 0, 1);
10474
- var SEC_PER_WEEK10 = 7 * 86400;
11032
+ var BDT_EPOCH_MS5 = Date.UTC(2006, 0, 1);
11033
+ var SEC_PER_WEEK11 = 7 * 86400;
10475
11034
  var SEC_PER_DAY2 = 86400;
10476
- var MS_PER_WEEK8 = SEC_PER_WEEK10 * 1e3;
11035
+ var MS_PER_WEEK8 = SEC_PER_WEEK11 * 1e3;
10477
11036
  var gpsMs2 = (week, sec) => GPS_EPOCH_MS16 + week * MS_PER_WEEK8 + sec * 1e3;
10478
- var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK10;
11037
+ var sowOf3 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK11;
10479
11038
  function nearWeekMs(refMs, sow) {
10480
11039
  const refWeek = Math.floor((refMs - GPS_EPOCH_MS16) / MS_PER_WEEK8);
10481
11040
  let ms = gpsMs2(refWeek, sow);
@@ -10504,8 +11063,8 @@ function decodeGloEphemeris(view, p) {
10504
11063
  tof += Math.floor(tow / SEC_PER_DAY2) * SEC_PER_DAY2;
10505
11064
  if (tof < tow - 43200) tof += SEC_PER_DAY2;
10506
11065
  else if (tof > tow + 43200) tof -= SEC_PER_DAY2;
10507
- const toeGpsMs = GPS_EPOCH_MS16 + (week * SEC_PER_WEEK10 + tow) * 1e3;
10508
- const tofGpsMs = GPS_EPOCH_MS16 + (week * SEC_PER_WEEK10 + tof) * 1e3;
11066
+ const toeGpsMs = GPS_EPOCH_MS16 + (week * SEC_PER_WEEK11 + tow) * 1e3;
11067
+ const tofGpsMs = GPS_EPOCH_MS16 + (week * SEC_PER_WEEK11 + tof) * 1e3;
10509
11068
  const tofUtcSec = (getUtcDate(new Date(tofGpsMs)).getTime() - GPS_EPOCH_MS16) / 1e3;
10510
11069
  return {
10511
11070
  system: "R",
@@ -10514,7 +11073,7 @@ function decodeGloEphemeris(view, p) {
10514
11073
  tauN: -view.getFloat64(p + 100, true),
10515
11074
  // RINEX stores −τ_n
10516
11075
  gammaN: view.getFloat64(p + 116, true),
10517
- messageFrameTime: (tofUtcSec % SEC_PER_WEEK10 + SEC_PER_WEEK10) % SEC_PER_WEEK10,
11076
+ messageFrameTime: (tofUtcSec % SEC_PER_WEEK11 + SEC_PER_WEEK11) % SEC_PER_WEEK11,
10518
11077
  x: view.getFloat64(p + 28, true) / 1e3,
10519
11078
  y: view.getFloat64(p + 36, true) / 1e3,
10520
11079
  z: view.getFloat64(p + 44, true) / 1e3,
@@ -10588,7 +11147,7 @@ function decodeBdsEphemeris2(view, p) {
10588
11147
  const tgd1 = view.getFloat64(p + 20, true);
10589
11148
  const tocs = view.getUint32(p + 40, true);
10590
11149
  const toes = view.getUint32(p + 72, true);
10591
- const tocDate = new Date(BDT_EPOCH_MS3 + week * MS_PER_WEEK8 + tocs * 1e3);
11150
+ const tocDate = new Date(BDT_EPOCH_MS5 + week * MS_PER_WEEK8 + tocs * 1e3);
10592
11151
  return {
10593
11152
  system: "C",
10594
11153
  prn: `C${String(prn).padStart(2, "0")}`,
@@ -10936,7 +11495,7 @@ function parseNovatelRange(data) {
10936
11495
  const sigtype = stat >>> 21 & 31;
10937
11496
  const plock = stat >>> 10 & 1;
10938
11497
  const parity = stat >>> 11 & 1;
10939
- const clock = stat >>> 12 & 1;
11498
+ const clock2 = stat >>> 12 & 1;
10940
11499
  const sig = SIGNALS2[satsys]?.[sigtype];
10941
11500
  if (!sig) continue;
10942
11501
  let [sysL, code] = sig;
@@ -10966,7 +11525,7 @@ function parseNovatelRange(data) {
10966
11525
  meas.push({
10967
11526
  prn: `${sysL}${String(prnNum).padStart(2, "0")}`,
10968
11527
  code,
10969
- pr: clock ? psr : null,
11528
+ pr: clock2 ? psr : null,
10970
11529
  cp: plock ? cp : null,
10971
11530
  doppler: plock ? dop : null,
10972
11531
  cn0,
@@ -10993,7 +11552,7 @@ function parseNovatelRange(data) {
10993
11552
  const sigtype = stat >>> 21 & 31;
10994
11553
  const plock = stat >>> 10 & 1;
10995
11554
  const parity = stat >>> 11 & 1;
10996
- const clock = stat >>> 12 & 1;
11555
+ const clock2 = stat >>> 12 & 1;
10997
11556
  const sig = SIGNALS2[satsys]?.[sigtype];
10998
11557
  if (!sig) continue;
10999
11558
  let [sysL, code] = sig;
@@ -11018,7 +11577,7 @@ function parseNovatelRange(data) {
11018
11577
  meas.push({
11019
11578
  prn,
11020
11579
  code,
11021
- pr: clock ? psr : null,
11580
+ pr: clock2 ? psr : null,
11022
11581
  cp: plock ? -adr : null,
11023
11582
  doppler: plock ? dop : null,
11024
11583
  cn0: snr,
@@ -11081,14 +11640,14 @@ function* trimbleFrames(data, stats) {
11081
11640
 
11082
11641
  // src/trimble/nav.ts
11083
11642
  var GPS_EPOCH_MS19 = Date.UTC(1980, 0, 6);
11084
- var BDT_EPOCH_MS4 = Date.UTC(2006, 0, 1);
11085
- var SEC_PER_WEEK11 = 7 * 86400;
11086
- var MS_PER_WEEK10 = SEC_PER_WEEK11 * 1e3;
11643
+ var BDT_EPOCH_MS6 = Date.UTC(2006, 0, 1);
11644
+ var SEC_PER_WEEK12 = 7 * 86400;
11645
+ var MS_PER_WEEK10 = SEC_PER_WEEK12 * 1e3;
11087
11646
  var GPS_PI2 = 3.1415926535898;
11088
11647
  var BDS_WEEK_OFFSET = 1356;
11089
11648
  var BDS_LEAP_SEC = 14;
11090
11649
  var gpsMs3 = (week, sec) => GPS_EPOCH_MS19 + week * MS_PER_WEEK10 + sec * 1e3;
11091
- var sowOf4 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK11;
11650
+ var sowOf4 = (dateMs) => dateMs / 1e3 % SEC_PER_WEEK12;
11092
11651
  var SUB_GPS_EPHEMERIS = 1;
11093
11652
  var SUB_ION_UTC = 3;
11094
11653
  var SUB_GLO_EPHEMERIS = 9;
@@ -11169,7 +11728,7 @@ function decodeBdsEphemeris3(view, base) {
11169
11728
  const tocSec = r.tocSec - BDS_LEAP_SEC;
11170
11729
  const toeSec = r.toeSec - BDS_LEAP_SEC;
11171
11730
  const tocDate = new Date(
11172
- BDT_EPOCH_MS4 + bdsWeek * MS_PER_WEEK10 + tocSec * 1e3
11731
+ BDT_EPOCH_MS6 + bdsWeek * MS_PER_WEEK10 + tocSec * 1e3
11173
11732
  );
11174
11733
  return {
11175
11734
  system: "C",
@@ -11240,7 +11799,7 @@ function decodeGloEphemeris2(view, base) {
11240
11799
  // RINEX stores −τn as the clock bias; the record carries τn (ICD sign).
11241
11800
  tauN: -view.getFloat64(base + 113, false),
11242
11801
  gammaN: view.getFloat64(base + 121, false),
11243
- messageFrameTime: (tocMs - GPS_EPOCH_MS19) / 1e3 % SEC_PER_WEEK11,
11802
+ messageFrameTime: (tocMs - GPS_EPOCH_MS19) / 1e3 % SEC_PER_WEEK12,
11244
11803
  // State vector: metres / m·s⁻¹ / m·s⁻² → km / km·s⁻¹ / km·s⁻².
11245
11804
  x: view.getFloat64(base + 33, false) / 1e3,
11246
11805
  xDot: view.getFloat64(base + 41, false) / 1e3,
@@ -11692,12 +12251,12 @@ function* binexRecords(data, view, stats) {
11692
12251
  // src/binex/nav.ts
11693
12252
  var GPS_EPOCH_MS21 = Date.UTC(1980, 0, 6);
11694
12253
  var GLO_DAY0_MS = Date.UTC(1980, 0, 1);
11695
- var BDT_EPOCH_MS5 = Date.UTC(2006, 0, 1);
11696
- var SEC_PER_WEEK12 = 7 * 86400;
11697
- var MS_PER_WEEK12 = SEC_PER_WEEK12 * 1e3;
12254
+ var BDT_EPOCH_MS7 = Date.UTC(2006, 0, 1);
12255
+ var SEC_PER_WEEK13 = 7 * 86400;
12256
+ var MS_PER_WEEK12 = SEC_PER_WEEK13 * 1e3;
11698
12257
  var SC2RAD = GPS_PI;
11699
12258
  var gpsMs4 = (week, sec) => GPS_EPOCH_MS21 + week * MS_PER_WEEK12 + sec * 1e3;
11700
- var sowOf5 = (dateMs) => (dateMs / 1e3 % SEC_PER_WEEK12 + SEC_PER_WEEK12) % SEC_PER_WEEK12;
12259
+ var sowOf5 = (dateMs) => (dateMs / 1e3 % SEC_PER_WEEK13 + SEC_PER_WEEK13) % SEC_PER_WEEK13;
11701
12260
  var two9 = (n) => String(n).padStart(2, "0");
11702
12261
  var Reader = class {
11703
12262
  constructor(view, start, le) {
@@ -11861,7 +12420,7 @@ function decodeBeidou(view, p, le) {
11861
12420
  const iode = flag1 >> 6 & 31;
11862
12421
  const svHealth = flag1 & 1;
11863
12422
  const tgd = bdsTgd(flag2 >> 4);
11864
- const tocDate = new Date(BDT_EPOCH_MS5 + week * MS_PER_WEEK12 + toes * 1e3);
12423
+ const tocDate = new Date(BDT_EPOCH_MS7 + week * MS_PER_WEEK12 + toes * 1e3);
11865
12424
  return {
11866
12425
  system: "C",
11867
12426
  prn: `C${two9(prn)}`,
@@ -12389,13 +12948,13 @@ var GM_GPS = 3986005e8;
12389
12948
  var GM_GAL = 3986004418e5;
12390
12949
  var GM_BDS = 3986004418e5;
12391
12950
  var GPS_EPOCH_MS23 = Date.UTC(1980, 0, 6);
12392
- var BDT_EPOCH_MS6 = Date.UTC(2006, 0, 1);
12951
+ var BDT_EPOCH_MS8 = Date.UTC(2006, 0, 1);
12393
12952
  var WEEK_MS = 6048e5;
12394
12953
  var BDT_MINUS_GPST_MS = 14e3;
12395
12954
  var BDS_GEO_PRNS = /* @__PURE__ */ new Set([1, 2, 3, 4, 5, 59, 60, 61, 62, 63]);
12396
12955
  function almanacEpochMs(alm) {
12397
12956
  if (alm.system === "C") {
12398
- return BDT_EPOCH_MS6 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3 + BDT_MINUS_GPST_MS;
12957
+ return BDT_EPOCH_MS8 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3 + BDT_MINUS_GPST_MS;
12399
12958
  }
12400
12959
  return GPS_EPOCH_MS23 + alm.weekAlm * WEEK_MS + alm.toaSec * 1e3;
12401
12960
  }
@@ -12770,8 +13329,8 @@ function computeDop(satAzEls) {
12770
13329
  for (const { az, el, prn } of sats) {
12771
13330
  const cosEl = Math.cos(el);
12772
13331
  const row = [cosEl * Math.sin(az), cosEl * Math.cos(az), Math.sin(el)];
12773
- const clock = dopClock(prn);
12774
- for (const c of clocks) row.push(c === clock ? 1 : 0);
13332
+ const clock2 = dopClock(prn);
13333
+ for (const c of clocks) row.push(c === clock2 ? 1 : 0);
12775
13334
  H3.push(row);
12776
13335
  }
12777
13336
  const HtH = Array.from({ length: m }, () => new Array(m).fill(0));
@@ -13575,6 +14134,12 @@ function solveZt(n, m, Z2, E) {
13575
14134
  for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
13576
14135
  return X;
13577
14136
  }
14137
+ function normCdf(x) {
14138
+ const z = Math.abs(x) / Math.SQRT2;
14139
+ const t = 1 / (1 + 0.3275911 * z);
14140
+ const erf = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-z * z);
14141
+ return x >= 0 ? 0.5 * (1 + erf) : 0.5 * (1 - erf);
14142
+ }
13578
14143
  function lambdaSearch(a, Q, n, m = 2) {
13579
14144
  if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
13580
14145
  const L = new Float64Array(n * n);
@@ -13601,7 +14166,10 @@ function lambdaSearch(a, Q, n, m = 2) {
13601
14166
  candidates.push(v);
13602
14167
  }
13603
14168
  const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
13604
- return { candidates, residuals: s, ratio };
14169
+ let successRate = 1;
14170
+ for (let i = 0; i < n; i++)
14171
+ successRate *= 2 * normCdf(1 / (2 * Math.sqrt(D[i]))) - 1;
14172
+ return { candidates, residuals: s, ratio, successRate };
13605
14173
  }
13606
14174
  function lambdaReduction(Q, n) {
13607
14175
  if (n <= 0 || Q.length < n * n) return null;
@@ -13616,7 +14184,7 @@ function lambdaReduction(Q, n) {
13616
14184
 
13617
14185
  // src/positioning/ppp-antenna.ts
13618
14186
  function parseAntexTime(s) {
13619
- const p = s.trim().split(/\s+/).map(Number);
14187
+ const p = s.trim().split(/[\s:-]+/).map(Number);
13620
14188
  if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
13621
14189
  return Date.UTC(
13622
14190
  p[0],
@@ -13666,11 +14234,21 @@ function buildPppAntenna(antex) {
13666
14234
  satOffset(prn, dateMs, band1, band2, f1, f2) {
13667
14235
  const list = sats.get(prn);
13668
14236
  if (!list) return null;
13669
- const entry = list.find((e) => {
13670
- const from = parseAntexTime(e.validFrom);
13671
- const until = parseAntexTime(e.validUntil);
13672
- return (from == null || dateMs >= from) && (until == null || dateMs <= until);
13673
- });
14237
+ let entry;
14238
+ let bestDist = Infinity;
14239
+ for (const e of list) {
14240
+ const from = parseAntexTime(e.validFrom) ?? -Infinity;
14241
+ const until = parseAntexTime(e.validUntil) ?? Infinity;
14242
+ if (dateMs >= from && dateMs <= until) {
14243
+ entry = e;
14244
+ break;
14245
+ }
14246
+ const dist = dateMs < from ? from - dateMs : dateMs - until;
14247
+ if (dist < bestDist) {
14248
+ bestDist = dist;
14249
+ entry = e;
14250
+ }
14251
+ }
13674
14252
  if (!entry) return null;
13675
14253
  const a = freqOf(entry, band1);
13676
14254
  const b = freqOf(entry, band2);
@@ -13678,10 +14256,14 @@ function buildPppAntenna(antex) {
13678
14256
  return withZen(entry, combineIf(a, b, f1, f2));
13679
14257
  },
13680
14258
  rcvOffset(antType, band1, band2, f1, f2) {
13681
- const entry = rcvs.get(antType);
14259
+ const entry = rcvs.get(antType) ?? rcvs.get(antType.substring(0, 16).padEnd(16, " ") + "NONE");
13682
14260
  if (!entry) return null;
13683
- const a = freqOf(entry, band1);
13684
- const b = freqOf(entry, band2);
14261
+ let a = freqOf(entry, band1);
14262
+ let b = freqOf(entry, band2);
14263
+ if (!a || !b) {
14264
+ a = freqOf(entry, "G01");
14265
+ b = freqOf(entry, "G02");
14266
+ }
13685
14267
  if (!a || !b) return null;
13686
14268
  return withZen(entry, combineIf(a, b, f1, f2));
13687
14269
  }
@@ -13757,13 +14339,13 @@ function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
13757
14339
  kx[2] - k[2] * kdotx - kcrossY[2]
13758
14340
  ];
13759
14341
  const north = enuToEcef([0, 1, 0], latRad, lonRad);
13760
- const east = enuToEcef([1, 0, 0], latRad, lonRad);
14342
+ const west = enuToEcef([-1, 0, 0], latRad, lonRad);
13761
14343
  const kdotn = dot(k, north);
13762
- const kcrossE = cross(k, east);
14344
+ const kcrossW = cross(k, west);
13763
14345
  const dR = [
13764
- north[0] - k[0] * kdotn + kcrossE[0],
13765
- north[1] - k[1] * kdotn + kcrossE[1],
13766
- north[2] - k[2] * kdotn + kcrossE[2]
14346
+ north[0] - k[0] * kdotn + kcrossW[0],
14347
+ north[1] - k[1] * kdotn + kcrossW[1],
14348
+ north[2] - k[2] * kdotn + kcrossW[2]
13767
14349
  ];
13768
14350
  const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
13769
14351
  const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
@@ -13890,9 +14472,11 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
13890
14472
  let baseLatRad = 0;
13891
14473
  let baseLonRad = 0;
13892
14474
  if (antenna?.base) {
13893
- const [latDeg, lonDeg] = ecefToGeodetic(basePos[0], basePos[1], basePos[2]);
13894
- baseLatRad = latDeg * Math.PI / 180;
13895
- baseLonRad = lonDeg * Math.PI / 180;
14475
+ [baseLatRad, baseLonRad] = ecefToGeodetic(
14476
+ basePos[0],
14477
+ basePos[1],
14478
+ basePos[2]
14479
+ );
13896
14480
  }
13897
14481
  for (const [prn, mR] of rover) {
13898
14482
  const sys = prn[0];
@@ -13988,12 +14572,12 @@ function roverTerms(g, x, y, z, troposphere) {
13988
14572
  dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
13989
14573
  }
13990
14574
  if (g.offR) {
13991
- const [latDeg, lonDeg] = ecefToGeodetic(x, y, z);
14575
+ const [latRad, lonRad] = ecefToGeodetic(x, y, z);
13992
14576
  rho += rcvAntennaRangeM(
13993
14577
  g.offR,
13994
14578
  [-u[0], -u[1], -u[2]],
13995
- latDeg * Math.PI / 180,
13996
- lonDeg * Math.PI / 180,
14579
+ latRad,
14580
+ lonRad,
13997
14581
  elR,
13998
14582
  g.roverDelta
13999
14583
  );
@@ -14997,7 +15581,7 @@ var P2_31 = 2 ** -31;
14997
15581
  var P2_39 = 2 ** -39;
14998
15582
  var D2R = Math.PI / 180;
14999
15583
  var R2D = 180 / Math.PI;
15000
- var SEC_PER_WEEK13 = 604800;
15584
+ var SEC_PER_WEEK14 = 604800;
15001
15585
  var MAXSBSAGEF = 30;
15002
15586
  var MAXSBSAGEL = 360;
15003
15587
  var MAXBAND = 10;
@@ -15145,7 +15729,7 @@ var igpband2 = [
15145
15729
  [{ coord: 60, arr: x5, bits: 1, bite: 72 }, { coord: 65, arr: x6, bits: 73, bite: 108 }, { coord: 70, arr: x6, bits: 109, bite: 144 }, { coord: 75, arr: x6, bits: 145, bite: 180 }, { coord: 85, arr: x7, bits: 181, bite: 192 }],
15146
15730
  [{ coord: -60, arr: x5, bits: 1, bite: 72 }, { coord: -65, arr: x6, bits: 73, bite: 108 }, { coord: -70, arr: x6, bits: 109, bite: 144 }, { coord: -75, arr: x6, bits: 145, bite: 180 }, { coord: -85, arr: x8, bits: 181, bite: 192 }]
15147
15731
  ];
15148
- var gpsSeconds = (week, tow) => week * SEC_PER_WEEK13 + tow;
15732
+ var gpsSeconds = (week, tow) => week * SEC_PER_WEEK14 + tow;
15149
15733
  function maskPrn(i) {
15150
15734
  if (i >= 1 && i <= 37) return `G${two11(i)}`;
15151
15735
  if (i >= 38 && i <= 61) return `R${two11(i - 37)}`;
@@ -15525,8 +16109,8 @@ var SbasProcessor = class {
15525
16109
  const j = block * 15 + i;
15526
16110
  if (j >= b.igp.length) continue;
15527
16111
  const give = getBitU(msg, 22 + i * 13 + 9, 4);
15528
- const delay = getBitU(msg, 22 + i * 13, 9);
15529
- b.igp[j].delay = delay === 511 ? 0 : delay * 0.125;
16112
+ const delay2 = getBitU(msg, 22 + i * 13, 9);
16113
+ b.igp[j].delay = delay2 === 511 ? 0 : delay2 * 0.125;
15530
16114
  b.igp[j].give = give + 1 >= 16 ? 0 : give + 1;
15531
16115
  b.igp[j].t0s = t0s;
15532
16116
  }
@@ -15692,19 +16276,19 @@ var SbasProcessor = class {
15692
16276
  if (w[3] < 0) return null;
15693
16277
  } else return null;
15694
16278
  const now = gpsSeconds(week, tow);
15695
- let delay = 0;
16279
+ let delay2 = 0;
15696
16280
  let varVert = 0;
15697
16281
  for (let i = 0; i < 4; i++) {
15698
16282
  const g = pts[i];
15699
16283
  if (!g) continue;
15700
- delay += w[i] * g.delay;
16284
+ delay2 += w[i] * g.delay;
15701
16285
  const sigGive = Math.sqrt(varicorr(g.give));
15702
16286
  const d = this.degr;
15703
16287
  const eiono = d ? sbasIonoDeg(d, now - g.t0s) : 0;
15704
16288
  const sig2 = d?.rssIono ? sigGive * sigGive + eiono * eiono : (sigGive + eiono) ** 2;
15705
16289
  varVert += w[i] * sig2;
15706
16290
  }
15707
- return { delayM: delay * fp, varM2: varVert * fp * fp };
16291
+ return { delayM: delay2 * fp, varM2: varVert * fp * fp };
15708
16292
  }
15709
16293
  /** Find the four IGPs bracketing a pierce point (RTKLIB `searchigp`). */
15710
16294
  searchIgp(latP, lonP) {
@@ -15792,10 +16376,10 @@ function applyOrbitClock(eph, corr, timeMs) {
15792
16376
  const clockM = corr.clockC0 + (corr.clockC1 ?? 0) * age + (corr.clockC2 ?? 0) * age * age + (corr.highRateClock ?? 0);
15793
16377
  return { x, y, z, clockOffsetS: clockM / C_LIGHT };
15794
16378
  }
15795
- function hasToOrbitClock(orbit, clock, refTimeMs, validitySeconds) {
16379
+ function hasToOrbitClock(orbit, clock2, refTimeMs, validitySeconds) {
15796
16380
  if (orbit.deltaRadial == null || orbit.deltaInTrack == null || orbit.deltaCrossTrack == null)
15797
16381
  return null;
15798
- const clockC0 = clock && clock.deltaClock != null && !clock.notUsable ? clock.deltaClock : 0;
16382
+ const clockC0 = clock2 && clock2.deltaClock != null && !clock2.notUsable ? clock2.deltaClock : 0;
15799
16383
  return {
15800
16384
  prn: orbit.prn,
15801
16385
  iod: orbit.gnssIod,
@@ -15808,41 +16392,269 @@ function hasToOrbitClock(orbit, clock, refTimeMs, validitySeconds) {
15808
16392
  };
15809
16393
  }
15810
16394
 
15811
- // src/positioning/ppp-tropo.ts
15812
- var HYD_AVG_A = [
15813
- 0.0012769934,
15814
- 1268323e-9,
15815
- 0.0012465397,
15816
- 0.0012196049,
15817
- 0.0012045996
15818
- ];
15819
- var HYD_AVG_B = [
15820
- 0.0029153695,
15821
- 0.0029152299,
15822
- 0.0029288445,
15823
- 0.0029022565,
15824
- 0.0029024912
15825
- ];
15826
- var HYD_AVG_C = [
15827
- 0.062610505,
15828
- 0.062837393,
15829
- 0.063721774,
15830
- 0.063824265,
15831
- 0.064258455
15832
- ];
15833
- var HYD_AMP_A = [0, 12709626e-12, 26523662e-12, 34000452e-12, 41202191e-12];
15834
- var HYD_AMP_B = [0, 21414979e-12, 30160779e-12, 72562722e-12, 11723375e-11];
15835
- var HYD_AMP_C = [0, 901284e-10, 43497037e-12, 84795348e-11, 0.0017037206];
15836
- var HT_A = 253e-7;
15837
- var HT_B = 549e-5;
15838
- var HT_C = 114e-5;
15839
- var WET_A = [
15840
- 58021897e-11,
15841
- 56794847e-11,
15842
- 58118019e-11,
15843
- 59727542e-11,
15844
- 61641693e-11
15845
- ];
16395
+ // src/positioning/ppp-ar.ts
16396
+ function wlWavelength(f1, f2) {
16397
+ return C_LIGHT / (f1 - f2);
16398
+ }
16399
+ function nlWavelength(f1, f2) {
16400
+ return C_LIGHT / (f1 + f2);
16401
+ }
16402
+ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
16403
+ const wlThreshold = opts.wlThreshold ?? 0.25;
16404
+ const ratioThreshold = opts.ratioThreshold ?? 3;
16405
+ const minPartial = opts.minPartial ?? 4;
16406
+ const minSuccess = opts.minSuccessRate ?? 0;
16407
+ const n = sats.length;
16408
+ const none = (rejectedWl2, ratio = 0) => ({
16409
+ fixed: false,
16410
+ ratio,
16411
+ refPrn: null,
16412
+ refPrns: [],
16413
+ sats: [],
16414
+ rejectedWl: rejectedWl2
16415
+ });
16416
+ const elev = (i) => sats[i].elevDeg ?? -1;
16417
+ const wlOf = (i) => sats[i].mwCyc - (sats[i].wlBiasCyc ?? 0);
16418
+ const refOfSys = /* @__PURE__ */ new Map();
16419
+ for (let i = 0; i < n; i++) {
16420
+ const sys = sats[i].prn[0];
16421
+ const r = refOfSys.get(sys);
16422
+ if (r === void 0 || elev(i) > elev(r)) refOfSys.set(sys, i);
16423
+ }
16424
+ const nWlOf = /* @__PURE__ */ new Map();
16425
+ const refIdx = /* @__PURE__ */ new Map();
16426
+ const rejectedWl = [];
16427
+ for (const r of refOfSys.values()) {
16428
+ nWlOf.set(r, Math.round(wlOf(r)));
16429
+ refIdx.set(r, r);
16430
+ }
16431
+ for (let i = 0; i < n; i++) {
16432
+ if (refIdx.has(i)) continue;
16433
+ const r = refOfSys.get(sats[i].prn[0]);
16434
+ const sdWl = wlOf(i) - wlOf(r);
16435
+ const k = Math.round(sdWl);
16436
+ if (Math.abs(sdWl - k) <= wlThreshold) {
16437
+ nWlOf.set(i, nWlOf.get(r) + k);
16438
+ refIdx.set(i, r);
16439
+ } else rejectedWl.push(sats[i].prn);
16440
+ }
16441
+ const lamNl = (i) => nlWavelength(sats[i].f1, sats[i].f2);
16442
+ const n1Float = (i) => {
16443
+ const s = sats[i];
16444
+ return s.aIF / lamNl(i) - s.f2 / (s.f1 - s.f2) * nWlOf.get(i) - (s.nlBiasCyc ?? 0);
16445
+ };
16446
+ const qN1 = (a, b) => Qaif[a * n + b] / (lamNl(a) * lamNl(b));
16447
+ let cand = [...refIdx.keys()].filter((i) => refIdx.get(i) !== i).sort((a, b) => elev(b) - elev(a));
16448
+ if (cand.length < 1) return none(rejectedWl);
16449
+ let best = null;
16450
+ let firstRatio = 0;
16451
+ for (; ; ) {
16452
+ const m = cand.length;
16453
+ const sd2 = new Float64Array(m);
16454
+ const Qsd = new Float64Array(m * m);
16455
+ for (let a = 0; a < m; a++) {
16456
+ const ia = cand[a];
16457
+ const ra = refIdx.get(ia);
16458
+ sd2[a] = n1Float(ia) - n1Float(ra);
16459
+ for (let b = 0; b < m; b++) {
16460
+ const ib = cand[b];
16461
+ const rb = refIdx.get(ib);
16462
+ Qsd[a * m + b] = qN1(ia, ib) - qN1(ia, rb) - qN1(ra, ib) + qN1(ra, rb);
16463
+ }
16464
+ }
16465
+ const res = lambdaSearch(sd2, Qsd, m, 2);
16466
+ const ratio = res ? Math.min(res.ratio, 999.9) : 0;
16467
+ if (best === null && firstRatio === 0) firstRatio = ratio;
16468
+ if (res && ratio >= ratioThreshold && res.successRate >= minSuccess) {
16469
+ best = { set: cand, ratio, fix: Float64Array.from(res.candidates[0]) };
16470
+ break;
16471
+ }
16472
+ if (m <= minPartial) break;
16473
+ cand = cand.slice(0, -1);
16474
+ }
16475
+ if (!best) return { ...none(rejectedWl, firstRatio), refPrn: null };
16476
+ const refN1 = /* @__PURE__ */ new Map();
16477
+ for (const r of refOfSys.values()) refN1.set(r, Math.round(n1Float(r)));
16478
+ const out = [];
16479
+ const emit = (i, n1) => {
16480
+ const s = sats[i];
16481
+ const l = lamNl(i);
16482
+ const nWl = nWlOf.get(i);
16483
+ out.push({
16484
+ prn: s.prn,
16485
+ nWl,
16486
+ n1,
16487
+ aIF: l * n1 + l * (s.f2 / (s.f1 - s.f2)) * nWl,
16488
+ ref: sats[refIdx.get(i)].prn
16489
+ });
16490
+ };
16491
+ const usedRefs = new Set(best.set.map((i) => refIdx.get(i)));
16492
+ for (const r of usedRefs) emit(r, refN1.get(r));
16493
+ best.set.forEach(
16494
+ (i, a) => emit(i, refN1.get(refIdx.get(i)) + Math.round(best.fix[a]))
16495
+ );
16496
+ const refPrns = [...usedRefs].map((r) => sats[r].prn);
16497
+ return {
16498
+ fixed: true,
16499
+ ratio: best.ratio,
16500
+ refPrn: refPrns[0] ?? null,
16501
+ refPrns,
16502
+ sats: out,
16503
+ rejectedWl
16504
+ };
16505
+ }
16506
+ function solveSpd(A, b) {
16507
+ const n = b.length;
16508
+ const L = Array.from({ length: n }, () => new Array(n).fill(0));
16509
+ for (let i = 0; i < n; i++) {
16510
+ for (let j = 0; j <= i; j++) {
16511
+ let s = A[i][j];
16512
+ for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
16513
+ if (i === j) {
16514
+ if (s <= 0) return null;
16515
+ L[i][j] = Math.sqrt(s);
16516
+ } else {
16517
+ L[i][j] = s / L[j][j];
16518
+ }
16519
+ }
16520
+ }
16521
+ const y = new Array(n).fill(0);
16522
+ for (let i = 0; i < n; i++) {
16523
+ let s = b[i];
16524
+ for (let k = 0; k < i; k++) s -= L[i][k] * y[k];
16525
+ y[i] = s / L[i][i];
16526
+ }
16527
+ const x = new Array(n).fill(0);
16528
+ for (let i = n - 1; i >= 0; i--) {
16529
+ let s = y[i];
16530
+ for (let k = i + 1; k < n; k++) s -= L[k][i] * x[k];
16531
+ x[i] = s / L[i][i];
16532
+ }
16533
+ return x;
16534
+ }
16535
+ function fixPppPosition(state, fcb, opts = {}) {
16536
+ const floatPos = state.position;
16537
+ const noFix = {
16538
+ fixed: false,
16539
+ position: floatPos,
16540
+ floatPosition: floatPos,
16541
+ shift: [0, 0, 0],
16542
+ nFixed: 0,
16543
+ ratio: 0,
16544
+ refPrn: null
16545
+ };
16546
+ const amb = state.ambiguities;
16547
+ if (amb.length < 3) return noFix;
16548
+ const sats = amb.map((a) => ({
16549
+ prn: a.prn,
16550
+ aIF: a.aIF,
16551
+ mwCyc: a.mwCyc,
16552
+ f1: a.f1,
16553
+ f2: a.f2,
16554
+ wlBiasCyc: fcb.satWlFcb.get(a.prn) ?? 0,
16555
+ nlBiasCyc: fcb.satNlFcb.get(a.prn) ?? 0,
16556
+ elevDeg: a.elevDeg
16557
+ }));
16558
+ const n = sats.length;
16559
+ const Qaif = new Float64Array(n * n);
16560
+ for (let i = 0; i < n; i++)
16561
+ for (let j = 0; j < n; j++)
16562
+ Qaif[i * n + j] = state.covariance[amb[i].index][amb[j].index];
16563
+ const res = resolvePppAmbiguities(sats, Qaif, {
16564
+ ...opts,
16565
+ minPartial: opts.minPartial ?? Math.max(4, opts.minFixed ?? 0)
16566
+ });
16567
+ if (!res.fixed || res.refPrn === null) {
16568
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16569
+ }
16570
+ const idxOfPrn = new Map(amb.map((a) => [a.prn, a.index]));
16571
+ const floatOfPrn = new Map(amb.map((a) => [a.prn, a.aIF]));
16572
+ const fixedOf = new Map(res.sats.map((s) => [s.prn, s.aIF]));
16573
+ const pairs = res.sats.filter(
16574
+ (s) => s.ref !== s.prn && idxOfPrn.has(s.prn) && idxOfPrn.has(s.ref)
16575
+ ).map((s) => ({ prn: s.prn, ref: s.ref }));
16576
+ const m = pairs.length;
16577
+ if (m < Math.max(1, opts.minFixed ?? 1))
16578
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16579
+ const P = state.covariance;
16580
+ const ix = (prn) => idxOfPrn.get(prn);
16581
+ const qAA = (i, j) => P[ix(i)][ix(j)];
16582
+ const qXA = (r, i) => P[r][ix(i)];
16583
+ const Qzz = Array.from({ length: m }, () => new Array(m).fill(0));
16584
+ const z = new Array(m).fill(0);
16585
+ for (let a = 0; a < m; a++) {
16586
+ const { prn: ia, ref: ra } = pairs[a];
16587
+ z[a] = floatOfPrn.get(ia) - floatOfPrn.get(ra) - (fixedOf.get(ia) - fixedOf.get(ra));
16588
+ for (let b = 0; b < m; b++) {
16589
+ const { prn: ib, ref: rb } = pairs[b];
16590
+ Qzz[a][b] = qAA(ia, ib) - qAA(ia, rb) - qAA(ra, ib) + qAA(ra, rb);
16591
+ }
16592
+ }
16593
+ const Qxz = [0, 1, 2].map(
16594
+ (r) => pairs.map(({ prn, ref }) => qXA(r, prn) - qXA(r, ref))
16595
+ );
16596
+ const y = solveSpd(Qzz, z);
16597
+ if (!y) return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16598
+ let chi2 = 0;
16599
+ for (let a = 0; a < m; a++) chi2 += z[a] * y[a];
16600
+ if (chi2 / m > (opts.maxChi2 ?? Infinity))
16601
+ return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16602
+ const shift = [0, 0, 0];
16603
+ for (let r = 0; r < 3; r++) {
16604
+ let s = 0;
16605
+ for (let a = 0; a < m; a++) s += Qxz[r][a] * y[a];
16606
+ shift[r] = -s;
16607
+ }
16608
+ return {
16609
+ fixed: true,
16610
+ position: [
16611
+ floatPos[0] + shift[0],
16612
+ floatPos[1] + shift[1],
16613
+ floatPos[2] + shift[2]
16614
+ ],
16615
+ floatPosition: floatPos,
16616
+ shift,
16617
+ nFixed: m,
16618
+ ratio: res.ratio,
16619
+ refPrn: res.refPrn
16620
+ };
16621
+ }
16622
+
16623
+ // src/positioning/ppp-tropo.ts
16624
+ var HYD_AVG_A = [
16625
+ 0.0012769934,
16626
+ 1268323e-9,
16627
+ 0.0012465397,
16628
+ 0.0012196049,
16629
+ 0.0012045996
16630
+ ];
16631
+ var HYD_AVG_B = [
16632
+ 0.0029153695,
16633
+ 0.0029152299,
16634
+ 0.0029288445,
16635
+ 0.0029022565,
16636
+ 0.0029024912
16637
+ ];
16638
+ var HYD_AVG_C = [
16639
+ 0.062610505,
16640
+ 0.062837393,
16641
+ 0.063721774,
16642
+ 0.063824265,
16643
+ 0.064258455
16644
+ ];
16645
+ var HYD_AMP_A = [0, 12709626e-12, 26523662e-12, 34000452e-12, 41202191e-12];
16646
+ var HYD_AMP_B = [0, 21414979e-12, 30160779e-12, 72562722e-12, 11723375e-11];
16647
+ var HYD_AMP_C = [0, 901284e-10, 43497037e-12, 84795348e-11, 0.0017037206];
16648
+ var HT_A = 253e-7;
16649
+ var HT_B = 549e-5;
16650
+ var HT_C = 114e-5;
16651
+ var WET_A = [
16652
+ 58021897e-11,
16653
+ 56794847e-11,
16654
+ 58118019e-11,
16655
+ 59727542e-11,
16656
+ 61641693e-11
16657
+ ];
15846
16658
  var WET_B = [
15847
16659
  0.0014275268,
15848
16660
  0.0015138625,
@@ -16015,13 +16827,11 @@ function createPppCorrections(cfg) {
16015
16827
  let rangeM = 0;
16016
16828
  let phaseWindupM = 0;
16017
16829
  const e = geom.los;
16018
- const [latDeg, lonDeg] = ecefToGeodetic(
16830
+ const [latRad, lonRad] = ecefToGeodetic(
16019
16831
  geom.rcvEcef[0],
16020
16832
  geom.rcvEcef[1],
16021
16833
  geom.rcvEcef[2]
16022
16834
  );
16023
- const latRad = latDeg * Math.PI / 180;
16024
- const lonRad = lonDeg * Math.PI / 180;
16025
16835
  const frame = ctx ? satBodyFrame(geom.satEcef, ctx.sun) : null;
16026
16836
  if (useSat && frame && geom.band1 && geom.band2) {
16027
16837
  const off = cfg.antenna.satOffset(
@@ -16079,6 +16889,10 @@ function createPppCorrections(cfg) {
16079
16889
  }
16080
16890
 
16081
16891
  // src/positioning/ppp.ts
16892
+ var NO_FCB = {
16893
+ satWlFcb: /* @__PURE__ */ new Map(),
16894
+ satNlFcb: /* @__PURE__ */ new Map()
16895
+ };
16082
16896
  function matVec(P, h) {
16083
16897
  const n = P.length;
16084
16898
  const out = new Array(n).fill(0);
@@ -16090,6 +16904,34 @@ function matVec(P, h) {
16090
16904
  }
16091
16905
  return out;
16092
16906
  }
16907
+ function tdLsq(qs) {
16908
+ const M = [0, 1, 2, 3].map(() => [0, 0, 0, 0, 0]);
16909
+ for (const q of qs) {
16910
+ const a = [-q.v.e[0], -q.v.e[1], -q.v.e[2], 1];
16911
+ for (let i = 0; i < 4; i++) {
16912
+ M[i][4] += a[i] * q.d;
16913
+ for (let j = 0; j < 4; j++) M[i][j] += a[i] * a[j];
16914
+ }
16915
+ }
16916
+ for (let c = 0; c < 4; c++) {
16917
+ let p = c;
16918
+ for (let r = c + 1; r < 4; r++)
16919
+ if (Math.abs(M[r][c]) > Math.abs(M[p][c])) p = r;
16920
+ if (Math.abs(M[p][c]) < 1e-9) return null;
16921
+ [M[c], M[p]] = [M[p], M[c]];
16922
+ for (let r = 0; r < 4; r++) {
16923
+ if (r === c) continue;
16924
+ const f = M[r][c] / M[c][c];
16925
+ for (let k = c; k < 5; k++) M[r][k] -= f * M[c][k];
16926
+ }
16927
+ }
16928
+ return [0, 1, 2, 3].map((i) => M[i][4] / M[i][i]);
16929
+ }
16930
+ function tdResidual(q, s) {
16931
+ return q.d - (-q.v.e[0] * s[0] - q.v.e[1] * s[1] - q.v.e[2] * s[2] + s[3]);
16932
+ }
16933
+ var TD_SLIP_M = 0.08;
16934
+ var CODE_GATE_M = 15;
16093
16935
  function ekfUpdateScalar(x, P, h, innovation, r, rejectK = 0) {
16094
16936
  const n = x.length;
16095
16937
  const Ph = matVec(P, h);
@@ -16122,10 +16964,17 @@ function ifCoeffs(f1, f2) {
16122
16964
  return { g, lambdaIf };
16123
16965
  }
16124
16966
  var Sp3EphemerisSource = class {
16125
- constructor(sp3, clk) {
16967
+ constructor(sp3, clk, osb) {
16126
16968
  this.sp3 = sp3;
16127
16969
  this.clk = clk;
16970
+ this.osb = osb;
16971
+ if (osb) {
16972
+ this.codeBias = (prn, code, t) => osb.get(prn, "C" + code, t);
16973
+ this.phaseBias = (prn, code, t) => osb.get(prn, "L" + code, t);
16974
+ }
16128
16975
  }
16976
+ codeBias;
16977
+ phaseBias;
16129
16978
  satState(prn, tEmitMs) {
16130
16979
  const p = sp3Position(this.sp3, prn, tEmitMs);
16131
16980
  if (!p || p.clk == null) return null;
@@ -16143,6 +16992,21 @@ var Sp3EphemerisSource = class {
16143
16992
  return { x: p.x, y: p.y, z: p.z, vx, vy, vz, clkS };
16144
16993
  }
16145
16994
  };
16995
+ function applySatBiases(source, o, tMs) {
16996
+ if (!source.codeBias && !source.phaseBias) return o;
16997
+ const b1 = source.codeBias?.(o.prn, o.code1 ?? "", tMs) ?? 0;
16998
+ const b2 = source.codeBias?.(o.prn, o.code2 ?? "", tMs) ?? 0;
16999
+ let l1 = o.l1;
17000
+ let l2 = o.l2;
17001
+ if (source.phaseBias) {
17002
+ const p1 = source.phaseBias(o.prn, o.phase1 ?? o.code1 ?? "", tMs);
17003
+ const p2 = source.phaseBias(o.prn, o.phase2 ?? o.code2 ?? "", tMs);
17004
+ if (p1 == null || p2 == null) return null;
17005
+ l1 -= p1 / (C_LIGHT / o.f1);
17006
+ l2 -= p2 / (C_LIGHT / o.f2);
17007
+ }
17008
+ return { ...o, c1: o.c1 - b1, c2: o.c2 - b2, l1, l2 };
17009
+ }
16146
17010
  function sagnacRotate(x, y, z, travelS) {
16147
17011
  const theta = OMEGA_E * travelS;
16148
17012
  const c = Math.cos(theta);
@@ -16204,6 +17068,16 @@ var PppEngine = class {
16204
17068
  arcs;
16205
17069
  arcSnap;
16206
17070
  mwState;
17071
+ /** Previous epoch's phase terms per satellite, for the time-differenced
17072
+ * (clock-free) cycle-slip test. */
17073
+ tdPrev;
17074
+ /** Consecutive gated-out phase updates per satellite. */
17075
+ phaseRejects;
17076
+ /** PPP-AR: the latest accepted fix (null: float). */
17077
+ lastFix;
17078
+ lastArMs;
17079
+ firstFixMs;
17080
+ fixedEpochs;
16207
17081
  series;
16208
17082
  ei = 0;
16209
17083
  t0 = 0;
@@ -16239,6 +17113,12 @@ var PppEngine = class {
16239
17113
  this.arcs = [];
16240
17114
  this.arcSnap = /* @__PURE__ */ new Map();
16241
17115
  this.mwState = /* @__PURE__ */ new Map();
17116
+ this.tdPrev = /* @__PURE__ */ new Map();
17117
+ this.phaseRejects = /* @__PURE__ */ new Map();
17118
+ this.lastFix = null;
17119
+ this.lastArMs = -Infinity;
17120
+ this.firstFixMs = null;
17121
+ this.fixedEpochs = 0;
16242
17122
  this.series = [];
16243
17123
  this.ei = 0;
16244
17124
  this.t0 = 0;
@@ -16332,7 +17212,9 @@ var PppEngine = class {
16332
17212
  lastSeen,
16333
17213
  arcSnap,
16334
17214
  mwState,
16335
- series
17215
+ series,
17216
+ tdPrev,
17217
+ phaseRejects
16336
17218
  } = this;
16337
17219
  const flushArc = this.flushArc.bind(this);
16338
17220
  const detectSlip = this.detectSlip.bind(this);
@@ -16354,13 +17236,15 @@ var PppEngine = class {
16354
17236
  for (const ci of clkIdx.values()) P[ci][ci] = CLK_VAR;
16355
17237
  P[IDX_ZWD][IDX_ZWD] += ztdQ * (ei > 0 ? 1 : 0);
16356
17238
  const rcv = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
16357
- const [latDeg, , heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
16358
- const latRad = latDeg * Math.PI / 180;
17239
+ const [latRad, , heightM] = ecefToGeodetic(rcv[0], rcv[1], rcv[2]);
16359
17240
  const epochCtx = corrections?.epochContext?.(epoch.timeMs, rcv) ?? null;
16360
17241
  const vis = [];
16361
17242
  let epochZhd = 0;
16362
- for (const o of epoch.obs) {
16363
- if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
17243
+ for (const raw of epoch.obs) {
17244
+ if (raw.c1 === 0 || raw.c2 === 0 || raw.l1 === 0 || raw.l2 === 0)
17245
+ continue;
17246
+ const o = applySatBiases(source, raw, epoch.timeMs);
17247
+ if (!o) continue;
16364
17248
  const slip = detectSlip(o, ei);
16365
17249
  const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
16366
17250
  if (!sat) continue;
@@ -16382,9 +17266,7 @@ var PppEngine = class {
16382
17266
  const lam1 = C_LIGHT / o.f1;
16383
17267
  const lam2 = C_LIGHT / o.f2;
16384
17268
  if (o.c1 === 0 || o.c2 === 0 || o.l1 === 0 || o.l2 === 0) continue;
16385
- const b1 = source.codeBias?.(o.prn, o.code1 ?? "", epoch.timeMs) ?? 0;
16386
- const b2 = source.codeBias?.(o.prn, o.code2 ?? "", epoch.timeMs) ?? 0;
16387
- const pIf = g * (o.c1 - b1) - (g - 1) * (o.c2 - b2);
17269
+ const pIf = g * o.c1 - (g - 1) * o.c2;
16388
17270
  const lIf = g * (o.l1 * lam1) - (g - 1) * (o.l2 * lam2);
16389
17271
  if (!Number.isFinite(pIf) || !Number.isFinite(lIf)) continue;
16390
17272
  const trop = opts.troposphere ?? true ? niellMapping(elRad, latRad, heightM, epoch.timeMs) : { zhd: 0, mh: 0, mw: 0 };
@@ -16417,9 +17299,63 @@ var PppEngine = class {
16417
17299
  windupM: corr.phaseWindupM,
16418
17300
  slip,
16419
17301
  f1: o.f1,
16420
- f2: o.f2
17302
+ f2: o.f2,
17303
+ sat: [sat.state.x, sat.state.y, sat.state.z]
16421
17304
  });
16422
17305
  }
17306
+ {
17307
+ const dq = [];
17308
+ for (const v of vis) {
17309
+ const prev = tdPrev.get(v.prn);
17310
+ const model = -v.satClkM + v.tropoFixed + v.windupM;
17311
+ if (prev && prev.ei === ei - 1 && !v.slip) {
17312
+ const r0 = Math.hypot(
17313
+ prev.sat[0] - rcv[0],
17314
+ prev.sat[1] - rcv[1],
17315
+ prev.sat[2] - rcv[2]
17316
+ );
17317
+ dq.push({
17318
+ v,
17319
+ d: v.lIf - prev.lIf - (v.range - r0) - (model - prev.model)
17320
+ });
17321
+ }
17322
+ tdPrev.set(v.prn, { ei, lIf: v.lIf, sat: v.sat, model });
17323
+ }
17324
+ if (!kinematic) {
17325
+ if (dq.length >= 4) {
17326
+ const sorted = dq.map((q) => q.d).sort((a, b) => a - b);
17327
+ const med = sorted[Math.floor(sorted.length / 2)];
17328
+ for (const q of dq)
17329
+ if (Math.abs(q.d - med) > TD_SLIP_M) q.v.slip = true;
17330
+ }
17331
+ } else if (dq.length >= 6) {
17332
+ let use = dq;
17333
+ for (let iter = 0; iter < 6; iter++) {
17334
+ const sol = use.length >= 6 ? tdLsq(use) : null;
17335
+ if (!sol) {
17336
+ for (const q of dq) q.v.slip = true;
17337
+ break;
17338
+ }
17339
+ let worst = -1;
17340
+ let worstR = 0;
17341
+ use.forEach((q, k) => {
17342
+ const r = Math.abs(tdResidual(q, sol));
17343
+ if (r > worstR) {
17344
+ worstR = r;
17345
+ worst = k;
17346
+ }
17347
+ });
17348
+ if (worstR <= TD_SLIP_M || iter === 5) {
17349
+ for (const q of dq)
17350
+ if (Math.abs(tdResidual(q, sol)) > TD_SLIP_M) q.v.slip = true;
17351
+ break;
17352
+ }
17353
+ use = use.filter((_, k) => k !== worst);
17354
+ }
17355
+ } else {
17356
+ for (const q of dq) q.v.slip = true;
17357
+ }
17358
+ }
16423
17359
  const zwd0 = x[IDX_ZWD];
16424
17360
  const bySys = /* @__PURE__ */ new Map();
16425
17361
  for (const v of vis) {
@@ -16429,6 +17365,20 @@ var PppEngine = class {
16429
17365
  if (arr) arr.push(r);
16430
17366
  else bySys.set(sys, [r]);
16431
17367
  }
17368
+ const posSigma = Math.sqrt(
17369
+ P[IDX_X][IDX_X] + P[IDX_Y][IDX_Y] + P[IDX_Z][IDX_Z]
17370
+ );
17371
+ const codeGate = CODE_GATE_M + 3 * posSigma;
17372
+ const screened = /* @__PURE__ */ new Set();
17373
+ for (const [sys, res] of bySys) {
17374
+ if (res.length < 4) continue;
17375
+ const med = [...res].sort((a, b) => a - b)[Math.floor(res.length / 2)];
17376
+ for (const v of vis) {
17377
+ if (v.prn[0] !== sys) continue;
17378
+ const r = v.pIf - (v.range - v.satClkM + v.tropoFixed + zwd0 * v.mw);
17379
+ if (Math.abs(r - med) > codeGate) screened.add(v.prn);
17380
+ }
17381
+ }
16432
17382
  for (const [sys, res] of bySys) {
16433
17383
  const ci = clockOf(sys);
16434
17384
  if (!clockSeeded.has(sys)) {
@@ -16439,9 +17389,11 @@ var PppEngine = class {
16439
17389
  P[ci][ci] = CLK_VAR;
16440
17390
  }
16441
17391
  let nSats = 0;
16442
- for (const v of vis) {
17392
+ const live = vis.filter((v) => !screened.has(v.prn));
17393
+ const rangeNow = (v) => v.range - (v.e[0] * (x[IDX_X] - rcv[0]) + v.e[1] * (x[IDX_Y] - rcv[1]) + v.e[2] * (x[IDX_Z] - rcv[2]));
17394
+ const prepAmbiguity = (v) => {
16443
17395
  const ci = clockOf(v.prn[0]);
16444
- const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
17396
+ const common = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
16445
17397
  if (v.slip) flushArc(v.prn);
16446
17398
  let ai = ambIdx.get(v.prn);
16447
17399
  if (ai === void 0 || v.slip) {
@@ -16458,67 +17410,91 @@ var PppEngine = class {
16458
17410
  }
16459
17411
  ambIdx.set(v.prn, ai);
16460
17412
  }
16461
- const n = x.length;
17413
+ return ai;
17414
+ };
17415
+ const codeUpdate = (v) => {
17416
+ const ci = clockOf(v.prn[0]);
17417
+ const common = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
17418
+ const sinEl = Math.max(Math.sin(v.elRad), 0.1);
17419
+ const h = new Array(x.length).fill(0);
17420
+ h[IDX_X] = -v.e[0];
17421
+ h[IDX_Y] = -v.e[1];
17422
+ h[IDX_Z] = -v.e[2];
17423
+ h[ci] = 1;
17424
+ h[IDX_ZWD] = v.mw;
17425
+ const innov = v.pIf - common;
17426
+ if (Math.abs(innov) < 1e6)
17427
+ ekfUpdateScalar(x, P, h, innov, (codeSigma / sinEl) ** 2, 4);
17428
+ };
17429
+ const phaseUpdate = (v, ai) => {
17430
+ const ci = clockOf(v.prn[0]);
16462
17431
  const sinEl = Math.max(Math.sin(v.elRad), 0.1);
16463
- const rCode = (codeSigma / sinEl) ** 2;
16464
17432
  const rPhase = (phaseSigma / sinEl) ** 2;
16465
- {
16466
- const h = new Array(n).fill(0);
17433
+ const commonPh = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
17434
+ const innov = v.lIf - (commonPh + v.windupM + x[ai]);
17435
+ const reinit = (phaseRejects.get(v.prn) ?? 0) >= 3;
17436
+ let used = false;
17437
+ if (!reinit) {
17438
+ const h = new Array(x.length).fill(0);
16467
17439
  h[IDX_X] = -v.e[0];
16468
17440
  h[IDX_Y] = -v.e[1];
16469
17441
  h[IDX_Z] = -v.e[2];
16470
17442
  h[ci] = 1;
16471
17443
  h[IDX_ZWD] = v.mw;
16472
- const innov = v.pIf - common;
16473
- if (Math.abs(innov) < 1e6) ekfUpdateScalar(x, P, h, innov, rCode, 4);
17444
+ h[ai] = 1;
17445
+ used = ekfUpdateScalar(x, P, h, innov, rPhase, 4);
16474
17446
  }
16475
- {
16476
- const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
16477
- const innov = v.lIf - (commonPh + v.windupM + x[ai]);
16478
- if (Math.abs(innov) > 0.15) {
16479
- flushArc(v.prn);
16480
- x[ai] = v.lIf - (commonPh + v.windupM);
16481
- for (let j = 0; j < P.length; j++) {
16482
- P[ai][j] = 0;
16483
- P[j][ai] = 0;
16484
- }
16485
- P[ai][ai] = 100 * 100;
16486
- } else {
16487
- const h = new Array(n).fill(0);
16488
- h[IDX_X] = -v.e[0];
16489
- h[IDX_Y] = -v.e[1];
16490
- h[IDX_Z] = -v.e[2];
16491
- h[ci] = 1;
16492
- h[IDX_ZWD] = v.mw;
16493
- h[ai] = 1;
16494
- ekfUpdateScalar(x, P, h, innov, rPhase, 4);
16495
- }
16496
- }
16497
- {
16498
- const elevDeg = v.elRad * 180 / Math.PI;
16499
- const mwMean = mwState.get(v.prn)?.mean ?? 0;
16500
- const s = arcSnap.get(v.prn);
16501
- if (s) {
16502
- s.aIF = x[ai];
16503
- s.mwCyc = mwMean;
16504
- s.elevSum += elevDeg;
16505
- s.nEpochs++;
16506
- s.endMs = epoch.timeMs;
16507
- } else {
16508
- arcSnap.set(v.prn, {
16509
- aIF: x[ai],
16510
- mwCyc: mwMean,
16511
- f1: v.f1,
16512
- f2: v.f2,
16513
- elevSum: elevDeg,
16514
- nEpochs: 1,
16515
- startMs: epoch.timeMs,
16516
- endMs: epoch.timeMs
16517
- });
17447
+ if (reinit) {
17448
+ flushArc(v.prn);
17449
+ x[ai] = v.lIf - (commonPh + v.windupM);
17450
+ for (let j = 0; j < P.length; j++) {
17451
+ P[ai][j] = 0;
17452
+ P[j][ai] = 0;
16518
17453
  }
17454
+ P[ai][ai] = 100 * 100;
17455
+ phaseRejects.delete(v.prn);
17456
+ } else if (used) phaseRejects.delete(v.prn);
17457
+ else phaseRejects.set(v.prn, (phaseRejects.get(v.prn) ?? 0) + 1);
17458
+ };
17459
+ const snapshot = (v, ai) => {
17460
+ const elevDeg = v.elRad * 180 / Math.PI;
17461
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
17462
+ const s = arcSnap.get(v.prn);
17463
+ if (s) {
17464
+ s.aIF = x[ai];
17465
+ s.mwCyc = mwMean;
17466
+ s.elevSum += elevDeg;
17467
+ s.nEpochs++;
17468
+ s.endMs = epoch.timeMs;
17469
+ } else {
17470
+ arcSnap.set(v.prn, {
17471
+ aIF: x[ai],
17472
+ mwCyc: mwMean,
17473
+ f1: v.f1,
17474
+ f2: v.f2,
17475
+ elevSum: elevDeg,
17476
+ nEpochs: 1,
17477
+ startMs: epoch.timeMs,
17478
+ endMs: epoch.timeMs
17479
+ });
16519
17480
  }
16520
17481
  lastSeen.set(v.prn, ei);
16521
17482
  nSats++;
17483
+ };
17484
+ if (kinematic) {
17485
+ for (const v of live) codeUpdate(v);
17486
+ for (const v of live) {
17487
+ const ai = prepAmbiguity(v);
17488
+ phaseUpdate(v, ai);
17489
+ snapshot(v, ai);
17490
+ }
17491
+ } else {
17492
+ for (const v of live) {
17493
+ const ai = prepAmbiguity(v);
17494
+ codeUpdate(v);
17495
+ phaseUpdate(v, ai);
17496
+ snapshot(v, ai);
17497
+ }
16522
17498
  }
16523
17499
  let sumPhaseResSq = 0;
16524
17500
  let nPhaseRes = 0;
@@ -16529,7 +17505,7 @@ var PppEngine = class {
16529
17505
  if (ai === void 0) continue;
16530
17506
  const sys = v.prn[0];
16531
17507
  const ci = clockOf(sys);
16532
- const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
17508
+ const commonPh = rangeNow(v) + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
16533
17509
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
16534
17510
  if (Math.abs(innov) >= 0.15) continue;
16535
17511
  const arr = bySysRes.get(sys);
@@ -16553,6 +17529,8 @@ var PppEngine = class {
16553
17529
  flushArc(prn);
16554
17530
  }
16555
17531
  }
17532
+ this.tryFix(epoch.timeMs, vis);
17533
+ if (this.lastFix) this.fixedEpochs++;
16556
17534
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
16557
17535
  let enu = null;
16558
17536
  let error3d = null;
@@ -16588,12 +17566,75 @@ var PppEngine = class {
16588
17566
  nSats,
16589
17567
  ztdWet: x[IDX_ZWD],
16590
17568
  ztdHydrostatic: epochZhd,
16591
- phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
17569
+ phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0,
17570
+ nFixed: this.lastFix?.nFixed ?? 0,
17571
+ fixedPosition: this.lastFix?.position ?? null
16592
17572
  };
16593
17573
  series.push(result);
16594
17574
  this.ei = ei + 1;
16595
17575
  return result;
16596
17576
  }
17577
+ /**
17578
+ * PPP-AR. Every `everyS`, resolve the converged ambiguities of the
17579
+ * satellites in view (between satellites of one constellation) and, when
17580
+ * the fix passes the ratio, success-rate and χ² tests, condition the
17581
+ * float position on it ({@link fixPppPosition}). The float filter itself
17582
+ * is never constrained — a wrong fix can't contaminate later epochs, and
17583
+ * the next attempt starts clean. Between attempts the last fix carries
17584
+ * over (static) or is dropped (kinematic).
17585
+ */
17586
+ tryFix(tMs, vis) {
17587
+ const ar = this.opts.ar;
17588
+ if (!ar || !this.source.phaseBias) return;
17589
+ const cfg = ar === true ? {} : ar;
17590
+ if (tMs - this.lastArMs < (cfg.everyS ?? 120) * 1e3) {
17591
+ if (this.kinematic) this.lastFix = null;
17592
+ return;
17593
+ }
17594
+ this.lastArMs = tMs;
17595
+ this.lastFix = null;
17596
+ const { x, P, ambIdx, arcSnap, mwState } = this;
17597
+ const minArcMs = (cfg.minArcS ?? 600) * 1e3;
17598
+ const ambiguities = [];
17599
+ for (const v of vis) {
17600
+ const index = ambIdx.get(v.prn);
17601
+ const snap = arcSnap.get(v.prn);
17602
+ const mw = mwState.get(v.prn);
17603
+ if (index === void 0 || !snap || !mw) continue;
17604
+ if (tMs - snap.startMs < minArcMs || snap.nEpochs < 20) continue;
17605
+ ambiguities.push({
17606
+ prn: v.prn,
17607
+ index,
17608
+ aIF: x[index],
17609
+ mwCyc: mw.mean,
17610
+ f1: v.f1,
17611
+ f2: v.f2,
17612
+ elevDeg: v.elRad * 180 / Math.PI
17613
+ });
17614
+ }
17615
+ const fx = fixPppPosition(
17616
+ {
17617
+ position: [x[IDX_X], x[IDX_Y], x[IDX_Z]],
17618
+ covariance: P,
17619
+ ambiguities
17620
+ },
17621
+ NO_FCB,
17622
+ {
17623
+ ratioThreshold: cfg.ratio ?? 3,
17624
+ wlThreshold: cfg.wlThreshold ?? 0.15,
17625
+ minSuccessRate: cfg.successRate ?? 0.999,
17626
+ minFixed: cfg.minFixed ?? 5,
17627
+ maxChi2: cfg.maxChi2 ?? 20
17628
+ }
17629
+ );
17630
+ if (!fx.fixed) return;
17631
+ this.lastFix = {
17632
+ position: fx.position,
17633
+ nFixed: fx.nFixed,
17634
+ ratio: fx.ratio
17635
+ };
17636
+ this.firstFixMs ??= tMs;
17637
+ }
16597
17638
  /** Finalise the session: capture the AR state, flush open arcs, assemble. */
16598
17639
  solution() {
16599
17640
  const { opts, x, P, ambIdx, arcSnap, mwState, arcs, series, truth } = this;
@@ -16626,7 +17667,12 @@ var PppEngine = class {
16626
17667
  x[IDX_Y],
16627
17668
  x[IDX_Z]
16628
17669
  ];
16629
- const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
17670
+ const geo = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
17671
+ const llh = [
17672
+ geo[0] * 180 / Math.PI,
17673
+ geo[1] * 180 / Math.PI,
17674
+ geo[2]
17675
+ ];
16630
17676
  const finalError3d = truth ? Math.hypot(
16631
17677
  finalPos[0] - truth[0],
16632
17678
  finalPos[1] - truth[1],
@@ -16642,218 +17688,25 @@ var PppEngine = class {
16642
17688
  convergenceSec: this.convergenceSec,
16643
17689
  finalError3d,
16644
17690
  arcs,
16645
- finalState
17691
+ finalState,
17692
+ ...opts.ar && this.source.phaseBias ? {
17693
+ ar: {
17694
+ firstFixSec: this.firstFixMs === null || !series.length ? null : (this.firstFixMs - series[0].timeMs) / 1e3,
17695
+ nFixedEnd: this.lastFix?.nFixed ?? 0,
17696
+ position: this.lastFix?.position ?? null,
17697
+ fixedEpochShare: series.length ? this.fixedEpochs / series.length : 0
17698
+ }
17699
+ } : {}
16646
17700
  };
16647
17701
  }
16648
17702
  };
16649
17703
  function solvePpp(epochs, sp3OrSource, opts) {
16650
- const source = "satState" in sp3OrSource ? sp3OrSource : new Sp3EphemerisSource(sp3OrSource, opts.clk);
17704
+ const source = "satState" in sp3OrSource ? sp3OrSource : new Sp3EphemerisSource(sp3OrSource, opts.clk, opts.osb);
16651
17705
  const eng = new PppEngine(opts, source);
16652
17706
  for (const epoch of epochs) eng.process(epoch);
16653
17707
  return eng.solution();
16654
17708
  }
16655
17709
 
16656
- // src/positioning/ppp-ar.ts
16657
- function wlWavelength(f1, f2) {
16658
- return C_LIGHT / (f1 - f2);
16659
- }
16660
- function nlWavelength(f1, f2) {
16661
- return C_LIGHT / (f1 + f2);
16662
- }
16663
- function resolvePppAmbiguities(sats, Qaif, opts = {}) {
16664
- const wlThreshold = opts.wlThreshold ?? 0.25;
16665
- const ratioThreshold = opts.ratioThreshold ?? 3;
16666
- const kept = [];
16667
- const nWl = [];
16668
- const rejectedWl = [];
16669
- for (let i = 0; i < sats.length; i++) {
16670
- const s = sats[i];
16671
- const wlFloat = s.mwCyc - (s.wlBiasCyc ?? 0);
16672
- const r = Math.round(wlFloat);
16673
- if (Math.abs(wlFloat - r) <= wlThreshold) {
16674
- kept.push(i);
16675
- nWl.push(r);
16676
- } else {
16677
- rejectedWl.push(s.prn);
16678
- }
16679
- }
16680
- if (kept.length < 2) {
16681
- return {
16682
- fixed: false,
16683
- ratio: 0,
16684
- refPrn: null,
16685
- sats: [],
16686
- rejectedWl
16687
- };
16688
- }
16689
- const n1Float = [];
16690
- const lamNl = [];
16691
- for (let k = 0; k < kept.length; k++) {
16692
- const s = sats[kept[k]];
16693
- const lNl = nlWavelength(s.f1, s.f2);
16694
- lamNl.push(lNl);
16695
- const n1 = s.aIF / lNl - s.f2 / (s.f1 - s.f2) * nWl[k] - (s.nlBiasCyc ?? 0);
16696
- n1Float.push(n1);
16697
- }
16698
- let ref = 0;
16699
- for (let k = 1; k < kept.length; k++) {
16700
- const e = sats[kept[k]].elevDeg ?? -1;
16701
- if (e > (sats[kept[ref]].elevDeg ?? -1)) ref = k;
16702
- }
16703
- const m = kept.length - 1;
16704
- const sd2 = new Float64Array(m);
16705
- const Qsd = new Float64Array(m * m);
16706
- const others = [];
16707
- for (let k = 0; k < kept.length; k++) if (k !== ref) others.push(k);
16708
- const n = sats.length;
16709
- const qN1 = (ka, kb) => Qaif[kept[ka] * n + kept[kb]] / (lamNl[ka] * lamNl[kb]);
16710
- for (let a = 0; a < m; a++) {
16711
- const ka = others[a];
16712
- sd2[a] = n1Float[ka] - n1Float[ref];
16713
- for (let b = 0; b < m; b++) {
16714
- const kb = others[b];
16715
- Qsd[a * m + b] = qN1(ka, kb) - qN1(ka, ref) - qN1(ref, kb) + qN1(ref, ref);
16716
- }
16717
- }
16718
- const res = lambdaSearch(sd2, Qsd, m, 2);
16719
- const ratio = res ? Math.min(res.ratio, 999.9) : 0;
16720
- const fixed = !!res && ratio >= ratioThreshold;
16721
- const refN1 = Math.round(n1Float[ref]);
16722
- const sdFix = res?.candidates[0];
16723
- const outSats = [];
16724
- for (let k = 0; k < kept.length; k++) {
16725
- const s = sats[kept[k]];
16726
- let n1;
16727
- if (k === ref) {
16728
- n1 = refN1;
16729
- } else {
16730
- const a = others.indexOf(k);
16731
- n1 = fixed && sdFix ? refN1 + Math.round(sdFix[a]) : Math.round(n1Float[k]);
16732
- }
16733
- const lNl = lamNl[k];
16734
- const aIF = lNl * n1 + lNl * (s.f2 / (s.f1 - s.f2)) * nWl[k];
16735
- outSats.push({ prn: s.prn, nWl: nWl[k], n1, aIF });
16736
- }
16737
- return {
16738
- fixed,
16739
- ratio,
16740
- refPrn: sats[kept[ref]].prn,
16741
- sats: outSats,
16742
- rejectedWl
16743
- };
16744
- }
16745
- function solveSpd(A, b) {
16746
- const n = b.length;
16747
- const L = Array.from({ length: n }, () => new Array(n).fill(0));
16748
- for (let i = 0; i < n; i++) {
16749
- for (let j = 0; j <= i; j++) {
16750
- let s = A[i][j];
16751
- for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
16752
- if (i === j) {
16753
- if (s <= 0) return null;
16754
- L[i][j] = Math.sqrt(s);
16755
- } else {
16756
- L[i][j] = s / L[j][j];
16757
- }
16758
- }
16759
- }
16760
- const y = new Array(n).fill(0);
16761
- for (let i = 0; i < n; i++) {
16762
- let s = b[i];
16763
- for (let k = 0; k < i; k++) s -= L[i][k] * y[k];
16764
- y[i] = s / L[i][i];
16765
- }
16766
- const x = new Array(n).fill(0);
16767
- for (let i = n - 1; i >= 0; i--) {
16768
- let s = y[i];
16769
- for (let k = i + 1; k < n; k++) s -= L[k][i] * x[k];
16770
- x[i] = s / L[i][i];
16771
- }
16772
- return x;
16773
- }
16774
- function fixPppPosition(state, fcb, opts = {}) {
16775
- const floatPos = state.position;
16776
- const noFix = {
16777
- fixed: false,
16778
- position: floatPos,
16779
- floatPosition: floatPos,
16780
- shift: [0, 0, 0],
16781
- nFixed: 0,
16782
- ratio: 0,
16783
- refPrn: null
16784
- };
16785
- const amb = state.ambiguities;
16786
- if (amb.length < 3) return noFix;
16787
- const sats = amb.map((a) => ({
16788
- prn: a.prn,
16789
- aIF: a.aIF,
16790
- mwCyc: a.mwCyc,
16791
- f1: a.f1,
16792
- f2: a.f2,
16793
- wlBiasCyc: fcb.satWlFcb.get(a.prn) ?? 0,
16794
- nlBiasCyc: fcb.satNlFcb.get(a.prn) ?? 0,
16795
- elevDeg: a.elevDeg
16796
- }));
16797
- const n = sats.length;
16798
- const Qaif = new Float64Array(n * n);
16799
- for (let i = 0; i < n; i++)
16800
- for (let j = 0; j < n; j++)
16801
- Qaif[i * n + j] = state.covariance[amb[i].index][amb[j].index];
16802
- const res = resolvePppAmbiguities(sats, Qaif, opts);
16803
- if (!res.fixed || res.refPrn === null) {
16804
- return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16805
- }
16806
- const idxOfPrn = new Map(amb.map((a) => [a.prn, a.index]));
16807
- const floatOfPrn = new Map(amb.map((a) => [a.prn, a.aIF]));
16808
- const kept = res.sats.filter((s) => idxOfPrn.has(s.prn));
16809
- const refPos = kept.findIndex((s) => s.prn === res.refPrn);
16810
- if (refPos < 0 || kept.length < 2) {
16811
- return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16812
- }
16813
- const ambIdx = kept.map((s) => idxOfPrn.get(s.prn));
16814
- const aFloat = kept.map((s) => floatOfPrn.get(s.prn));
16815
- const aFixed = kept.map((s) => s.aIF);
16816
- const others = kept.map((_, i) => i).filter((i) => i !== refPos);
16817
- const m = others.length;
16818
- const P = state.covariance;
16819
- const qAA = (i, j) => P[ambIdx[i]][ambIdx[j]];
16820
- const qXA = (r, i) => P[r][ambIdx[i]];
16821
- const Qzz = Array.from({ length: m }, () => new Array(m).fill(0));
16822
- const z = new Array(m).fill(0);
16823
- for (let a = 0; a < m; a++) {
16824
- const ia = others[a];
16825
- z[a] = aFloat[ia] - aFloat[refPos] - (aFixed[ia] - aFixed[refPos]);
16826
- for (let b = 0; b < m; b++) {
16827
- const ib = others[b];
16828
- Qzz[a][b] = qAA(ia, ib) - qAA(ia, refPos) - qAA(refPos, ib) + qAA(refPos, refPos);
16829
- }
16830
- }
16831
- const Qxz = [0, 1, 2].map(
16832
- (r) => others.map((ia) => qXA(r, ia) - qXA(r, refPos))
16833
- );
16834
- const y = solveSpd(Qzz, z);
16835
- if (!y) return { ...noFix, ratio: res.ratio, refPrn: res.refPrn };
16836
- const shift = [0, 0, 0];
16837
- for (let r = 0; r < 3; r++) {
16838
- let s = 0;
16839
- for (let a = 0; a < m; a++) s += Qxz[r][a] * y[a];
16840
- shift[r] = -s;
16841
- }
16842
- return {
16843
- fixed: true,
16844
- position: [
16845
- floatPos[0] + shift[0],
16846
- floatPos[1] + shift[1],
16847
- floatPos[2] + shift[2]
16848
- ],
16849
- floatPosition: floatPos,
16850
- shift,
16851
- nFixed: m,
16852
- ratio: res.ratio,
16853
- refPrn: res.refPrn
16854
- };
16855
- }
16856
-
16857
17710
  // src/positioning/ppp-fcb.ts
16858
17711
  function extractWidelaneArcs(station, epochs, opts = {}) {
16859
17712
  const slipCyc = opts.slipCyc ?? 4;
@@ -17171,7 +18024,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
17171
18024
  let x9 = 0;
17172
18025
  let y2 = 0;
17173
18026
  let z2 = 0;
17174
- const clock2 = new Map(systems.map((s) => [s, 0]));
18027
+ const clock3 = new Map(systems.map((s) => [s, 0]));
17175
18028
  const residuals2 = {};
17176
18029
  let iterations2 = 0;
17177
18030
  let converged2 = false;
@@ -17191,7 +18044,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
17191
18044
  const psr = pseudoranges.get(prn);
17192
18045
  const eph = ephemerides.get(prn);
17193
18046
  const sys = prn[0];
17194
- const clk = clock2.get(sys);
18047
+ const clk = clock3.get(sys);
17195
18048
  const tTx = timeMs - psr / C_LIGHT * 1e3;
17196
18049
  const dtsClock = satClockCorrection(eph, tTx);
17197
18050
  const sat = computeSatPosition(eph, tTx - dtsClock * 1e3);
@@ -17283,13 +18136,13 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
17283
18136
  y2 += dx[1];
17284
18137
  z2 += dx[2];
17285
18138
  for (const s of systems)
17286
- clock2.set(s, clock2.get(s) + dx[sysIndex.get(s)]);
18139
+ clock3.set(s, clock3.get(s) + dx[sysIndex.get(s)]);
17287
18140
  if (Math.hypot(dx[0], dx[1], dx[2]) < convergenceM) {
17288
18141
  converged2 = true;
17289
18142
  break;
17290
18143
  }
17291
18144
  }
17292
- return { x: x9, y: y2, z: z2, clock: clock2, residuals: residuals2, iterations: iterations2, converged: converged2 };
18145
+ return { x: x9, y: y2, z: z2, clock: clock3, residuals: residuals2, iterations: iterations2, converged: converged2 };
17293
18146
  };
17294
18147
  const REJECT_THRESHOLD_M = 50;
17295
18148
  let candidates = all;
@@ -17314,7 +18167,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
17314
18167
  candidates = remaining;
17315
18168
  }
17316
18169
  if (!inner) return null;
17317
- const { x, y, z, clock, residuals, iterations, converged } = inner;
18170
+ const { x, y, z, clock: clock2, residuals, iterations, converged } = inner;
17318
18171
  const used = [];
17319
18172
  const azels = [];
17320
18173
  const [rxLat, rxLon, rxHgt] = ecefToGeodetic(x, y, z);
@@ -17354,7 +18207,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
17354
18207
  const pl = sbas ? sbasProtectionLevels(sbasRows) : null;
17355
18208
  return {
17356
18209
  position: [x, y, z],
17357
- clockBias: Object.fromEntries(clock),
18210
+ clockBias: Object.fromEntries(clock2),
17358
18211
  usedSatellites: used,
17359
18212
  rejectedSatellites: rejected,
17360
18213
  residuals,
@@ -19713,6 +20566,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19713
20566
  SYSTEM_NAMES,
19714
20567
  SYSTEM_ORDER,
19715
20568
  SYS_SHORT,
20569
+ SatOsbTable,
19716
20570
  SbasProcessor,
19717
20571
  Scale,
19718
20572
  Sp3EphemerisSource,
@@ -19895,6 +20749,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
19895
20749
  parseNovatelRange,
19896
20750
  parseRinexStream,
19897
20751
  parseSbfAlmanac,
20752
+ parseSbfBdsCnav,
19898
20753
  parseSbfBdsNav,
19899
20754
  parseSbfCnav,
19900
20755
  parseSbfGalNav,