gnss-js 2.2.0 → 2.4.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.
- package/dist/{chunk-DWUQRJ37.js → chunk-B2XWO4RP.js} +337 -182
- package/dist/index.cjs +339 -181
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -1
- package/dist/positioning.cjs +339 -181
- package/dist/positioning.d.cts +117 -25
- package/dist/positioning.d.ts +117 -25
- package/dist/positioning.js +7 -1
- package/package.json +1 -1
|
@@ -302,6 +302,211 @@ function lambdaReduction(Q, n) {
|
|
|
302
302
|
return Z;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
// src/positioning/ppp-antenna.ts
|
|
306
|
+
function parseAntexTime(s) {
|
|
307
|
+
const p = s.trim().split(/\s+/).map(Number);
|
|
308
|
+
if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
|
|
309
|
+
return Date.UTC(
|
|
310
|
+
p[0],
|
|
311
|
+
(p[1] || 1) - 1,
|
|
312
|
+
p[2] || 1,
|
|
313
|
+
p[3] ?? 0,
|
|
314
|
+
p[4] ?? 0,
|
|
315
|
+
Math.floor(p[5] ?? 0)
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
function freqOf(entry, code) {
|
|
319
|
+
return entry.frequencies.find((f) => f.frequency === code);
|
|
320
|
+
}
|
|
321
|
+
function combineIf(fA, fB, f1, f2) {
|
|
322
|
+
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
323
|
+
const mm = 1e-3;
|
|
324
|
+
const pco = [
|
|
325
|
+
(g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
|
|
326
|
+
(g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
|
|
327
|
+
(g * fA.pcoU - (g - 1) * fB.pcoU) * mm
|
|
328
|
+
];
|
|
329
|
+
const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
|
|
330
|
+
const pcvNoazi = new Array(nz);
|
|
331
|
+
for (let i = 0; i < nz; i++) {
|
|
332
|
+
pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
|
|
333
|
+
}
|
|
334
|
+
return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
|
|
335
|
+
}
|
|
336
|
+
function buildPppAntenna(antex) {
|
|
337
|
+
const sats = /* @__PURE__ */ new Map();
|
|
338
|
+
const rcvs = /* @__PURE__ */ new Map();
|
|
339
|
+
for (const a of antex.antennas) {
|
|
340
|
+
if (a.isSatellite) {
|
|
341
|
+
const list = sats.get(a.serialNo) ?? [];
|
|
342
|
+
list.push(a);
|
|
343
|
+
sats.set(a.serialNo, list);
|
|
344
|
+
} else if (!rcvs.has(a.type)) {
|
|
345
|
+
rcvs.set(a.type, a);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const withZen = (entry, off) => ({
|
|
349
|
+
...off,
|
|
350
|
+
pcvZen1Deg: entry.zen1,
|
|
351
|
+
pcvDzenDeg: entry.dzen
|
|
352
|
+
});
|
|
353
|
+
return {
|
|
354
|
+
satOffset(prn, dateMs, band1, band2, f1, f2) {
|
|
355
|
+
const list = sats.get(prn);
|
|
356
|
+
if (!list) return null;
|
|
357
|
+
const entry = list.find((e) => {
|
|
358
|
+
const from = parseAntexTime(e.validFrom);
|
|
359
|
+
const until = parseAntexTime(e.validUntil);
|
|
360
|
+
return (from == null || dateMs >= from) && (until == null || dateMs <= until);
|
|
361
|
+
});
|
|
362
|
+
if (!entry) return null;
|
|
363
|
+
const a = freqOf(entry, band1);
|
|
364
|
+
const b = freqOf(entry, band2);
|
|
365
|
+
if (!a || !b) return null;
|
|
366
|
+
return withZen(entry, combineIf(a, b, f1, f2));
|
|
367
|
+
},
|
|
368
|
+
rcvOffset(antType, band1, band2, f1, f2) {
|
|
369
|
+
const entry = rcvs.get(antType);
|
|
370
|
+
if (!entry) return null;
|
|
371
|
+
const a = freqOf(entry, band1);
|
|
372
|
+
const b = freqOf(entry, band2);
|
|
373
|
+
if (!a || !b) return null;
|
|
374
|
+
return withZen(entry, combineIf(a, b, f1, f2));
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function cross(a, b) {
|
|
379
|
+
return [
|
|
380
|
+
a[1] * b[2] - a[2] * b[1],
|
|
381
|
+
a[2] * b[0] - a[0] * b[2],
|
|
382
|
+
a[0] * b[1] - a[1] * b[0]
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
function norm(a) {
|
|
386
|
+
const n = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
387
|
+
return [a[0] / n, a[1] / n, a[2] / n];
|
|
388
|
+
}
|
|
389
|
+
function dot(a, b) {
|
|
390
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
391
|
+
}
|
|
392
|
+
function satBodyFrame(satEcef, sunEcef2) {
|
|
393
|
+
const z = norm([-satEcef[0], -satEcef[1], -satEcef[2]]);
|
|
394
|
+
const eSun = norm([
|
|
395
|
+
sunEcef2[0] - satEcef[0],
|
|
396
|
+
sunEcef2[1] - satEcef[1],
|
|
397
|
+
sunEcef2[2] - satEcef[2]
|
|
398
|
+
]);
|
|
399
|
+
const y = norm(cross(z, eSun));
|
|
400
|
+
const x = norm(cross(y, z));
|
|
401
|
+
return { x, y, z };
|
|
402
|
+
}
|
|
403
|
+
function satPcoToEcef(pcoBody, frame) {
|
|
404
|
+
return [
|
|
405
|
+
pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
|
|
406
|
+
pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
|
|
407
|
+
pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
function enuToEcef(enu, latRad, lonRad) {
|
|
411
|
+
const sl = Math.sin(latRad);
|
|
412
|
+
const cl = Math.cos(latRad);
|
|
413
|
+
const so = Math.sin(lonRad);
|
|
414
|
+
const co = Math.cos(lonRad);
|
|
415
|
+
const [e, n, u] = enu;
|
|
416
|
+
return [
|
|
417
|
+
-so * e - sl * co * n + cl * co * u,
|
|
418
|
+
co * e - sl * so * n + cl * so * u,
|
|
419
|
+
cl * n + sl * u
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
function interpPcv(off, angleDeg) {
|
|
423
|
+
const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
|
|
424
|
+
if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
|
|
425
|
+
const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
|
|
426
|
+
if (x <= 0) return pcvNoazi[0];
|
|
427
|
+
if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
|
|
428
|
+
const i = Math.floor(x);
|
|
429
|
+
const t = x - i;
|
|
430
|
+
return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
|
|
431
|
+
}
|
|
432
|
+
function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
|
|
433
|
+
const k = [
|
|
434
|
+
-losRcvToSat[0],
|
|
435
|
+
-losRcvToSat[1],
|
|
436
|
+
-losRcvToSat[2]
|
|
437
|
+
];
|
|
438
|
+
const kx = satFrame.x;
|
|
439
|
+
const ky = satFrame.y;
|
|
440
|
+
const kdotx = dot(k, kx);
|
|
441
|
+
const kcrossY = cross(k, ky);
|
|
442
|
+
const dS = [
|
|
443
|
+
kx[0] - k[0] * kdotx - kcrossY[0],
|
|
444
|
+
kx[1] - k[1] * kdotx - kcrossY[1],
|
|
445
|
+
kx[2] - k[2] * kdotx - kcrossY[2]
|
|
446
|
+
];
|
|
447
|
+
const north = enuToEcef([0, 1, 0], latRad, lonRad);
|
|
448
|
+
const east = enuToEcef([1, 0, 0], latRad, lonRad);
|
|
449
|
+
const kdotn = dot(k, north);
|
|
450
|
+
const kcrossE = cross(k, east);
|
|
451
|
+
const dR = [
|
|
452
|
+
north[0] - k[0] * kdotn + kcrossE[0],
|
|
453
|
+
north[1] - k[1] * kdotn + kcrossE[1],
|
|
454
|
+
north[2] - k[2] * kdotn + kcrossE[2]
|
|
455
|
+
];
|
|
456
|
+
const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
|
|
457
|
+
const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
|
|
458
|
+
let cosPhi = dot(dS, dR) / (nS * nR);
|
|
459
|
+
cosPhi = Math.max(-1, Math.min(1, cosPhi));
|
|
460
|
+
const zeta = cross(dS, dR);
|
|
461
|
+
const sign = dot(k, zeta) >= 0 ? 1 : -1;
|
|
462
|
+
const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
|
|
463
|
+
const prev = state.get(prn);
|
|
464
|
+
const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
|
|
465
|
+
state.set(prn, wu);
|
|
466
|
+
return wu;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/positioning/rtk-antenna.ts
|
|
470
|
+
function freqOf2(entry, code) {
|
|
471
|
+
return entry.frequencies.find((f) => f.frequency === code);
|
|
472
|
+
}
|
|
473
|
+
function buildRtkAntenna(antex) {
|
|
474
|
+
const rcvs = /* @__PURE__ */ new Map();
|
|
475
|
+
for (const a of antex.antennas) {
|
|
476
|
+
if (!a.isSatellite && !rcvs.has(a.type)) rcvs.set(a.type, a);
|
|
477
|
+
}
|
|
478
|
+
const mm = 1e-3;
|
|
479
|
+
return {
|
|
480
|
+
rcvOffset(antType, freq) {
|
|
481
|
+
const entry = rcvs.get(antType);
|
|
482
|
+
if (!entry) return null;
|
|
483
|
+
const f = freqOf2(entry, freq);
|
|
484
|
+
if (!f) return null;
|
|
485
|
+
return {
|
|
486
|
+
pco: [f.pcoN * mm, f.pcoE * mm, f.pcoU * mm],
|
|
487
|
+
pcvZen1Deg: entry.zen1,
|
|
488
|
+
pcvDzenDeg: entry.dzen,
|
|
489
|
+
pcvNoazi: f.pcvNoazi.map((v) => v * mm)
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function rcvAntennaRangeM(off, losRcvToSat, latRad, lonRad, elRad, antDeltaEnu = [0, 0, 0]) {
|
|
495
|
+
const total = [
|
|
496
|
+
off.pco[0] + antDeltaEnu[0],
|
|
497
|
+
off.pco[1] + antDeltaEnu[1],
|
|
498
|
+
off.pco[2] + antDeltaEnu[2]
|
|
499
|
+
];
|
|
500
|
+
const ecef = enuToEcef(total, latRad, lonRad);
|
|
501
|
+
let m = -dot(losRcvToSat, ecef);
|
|
502
|
+
const zenithDeg = 90 - elRad * 180 / Math.PI;
|
|
503
|
+
m += interpPcv(off, zenithDeg);
|
|
504
|
+
return m;
|
|
505
|
+
}
|
|
506
|
+
function antexFreqOfGroup(group) {
|
|
507
|
+
return `${group[0]}0${group[1]}`;
|
|
508
|
+
}
|
|
509
|
+
|
|
305
510
|
// src/positioning/rtk.ts
|
|
306
511
|
var L1_CODES = {
|
|
307
512
|
G: ["1C"],
|
|
@@ -367,8 +572,16 @@ function resolveEphemeris(src, prn, timeMs) {
|
|
|
367
572
|
return selectEphemeris(src, prn, timeMs);
|
|
368
573
|
return src.get(prn) ?? null;
|
|
369
574
|
}
|
|
370
|
-
|
|
575
|
+
var ZERO_DELTA = [0, 0, 0];
|
|
576
|
+
function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere, antenna = null) {
|
|
371
577
|
const out = [];
|
|
578
|
+
let baseLatRad = 0;
|
|
579
|
+
let baseLonRad = 0;
|
|
580
|
+
if (antenna?.base) {
|
|
581
|
+
const [latDeg, lonDeg] = ecefToGeodetic(basePos[0], basePos[1], basePos[2]);
|
|
582
|
+
baseLatRad = latDeg * Math.PI / 180;
|
|
583
|
+
baseLonRad = lonDeg * Math.PI / 180;
|
|
584
|
+
}
|
|
372
585
|
for (const [prn, mR] of rover) {
|
|
373
586
|
const sys = prn[0];
|
|
374
587
|
if (!"GERCJ".includes(sys)) continue;
|
|
@@ -393,14 +606,43 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
|
|
|
393
606
|
satB.z - basePos[2]
|
|
394
607
|
) / C_LIGHT;
|
|
395
608
|
const [bx, by, bz] = sagnac(satB, travelB);
|
|
396
|
-
|
|
609
|
+
let rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
|
|
397
610
|
const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
|
|
398
611
|
if (elB < maskRad) continue;
|
|
612
|
+
const group = sys + mR.code;
|
|
613
|
+
let offR = null;
|
|
614
|
+
let roverDelta = ZERO_DELTA;
|
|
615
|
+
if (antenna) {
|
|
616
|
+
const antexFreq = antexFreqOfGroup(group);
|
|
617
|
+
if (antenna.base) {
|
|
618
|
+
const offB = antenna.model.rcvOffset(antenna.base.type, antexFreq);
|
|
619
|
+
if (offB) {
|
|
620
|
+
const inv = 1 / rhoB;
|
|
621
|
+
const losB = [
|
|
622
|
+
(bx - basePos[0]) * inv,
|
|
623
|
+
(by - basePos[1]) * inv,
|
|
624
|
+
(bz - basePos[2]) * inv
|
|
625
|
+
];
|
|
626
|
+
rhoB += rcvAntennaRangeM(
|
|
627
|
+
offB,
|
|
628
|
+
losB,
|
|
629
|
+
baseLatRad,
|
|
630
|
+
baseLonRad,
|
|
631
|
+
elB,
|
|
632
|
+
antenna.base.deltaEnu
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (antenna.rover) {
|
|
637
|
+
offR = antenna.model.rcvOffset(antenna.rover.type, antexFreq);
|
|
638
|
+
roverDelta = antenna.rover.deltaEnu ?? ZERO_DELTA;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
399
641
|
const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
|
|
400
642
|
const freq = carrierFreqHz(sys, mR.code, gloK);
|
|
401
643
|
out.push({
|
|
402
644
|
prn,
|
|
403
|
-
group
|
|
645
|
+
group,
|
|
404
646
|
lambda: freq > 0 ? C_LIGHT / freq : 0,
|
|
405
647
|
satR,
|
|
406
648
|
rhoB,
|
|
@@ -411,7 +653,9 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
|
|
|
411
653
|
cpR: mR.cp ?? null,
|
|
412
654
|
cpB: mB.cp ?? null,
|
|
413
655
|
lockR: mR.lockTimeMs,
|
|
414
|
-
lockB: mB.lockTimeMs
|
|
656
|
+
lockB: mB.lockTimeMs,
|
|
657
|
+
offR,
|
|
658
|
+
roverDelta
|
|
415
659
|
});
|
|
416
660
|
}
|
|
417
661
|
return out;
|
|
@@ -419,17 +663,29 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
|
|
|
419
663
|
function roverTerms(g, x, y, z, troposphere) {
|
|
420
664
|
const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
|
|
421
665
|
const [sx, sy, sz] = sagnac(g.satR, travel);
|
|
422
|
-
|
|
666
|
+
let rho = Math.hypot(sx - x, sy - y, sz - z);
|
|
423
667
|
const u = [
|
|
424
668
|
(x - sx) / rho,
|
|
425
669
|
(y - sy) / rho,
|
|
426
670
|
(z - sz) / rho
|
|
427
671
|
];
|
|
672
|
+
const needEl = troposphere || g.offR != null;
|
|
673
|
+
const elR = needEl ? ecefToAzEl(x, y, z, sx, sy, sz).el : 0;
|
|
428
674
|
let dTropo = 0;
|
|
429
675
|
if (troposphere) {
|
|
430
|
-
const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
|
|
431
676
|
dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
|
|
432
677
|
}
|
|
678
|
+
if (g.offR) {
|
|
679
|
+
const [latDeg, lonDeg] = ecefToGeodetic(x, y, z);
|
|
680
|
+
rho += rcvAntennaRangeM(
|
|
681
|
+
g.offR,
|
|
682
|
+
[-u[0], -u[1], -u[2]],
|
|
683
|
+
latDeg * Math.PI / 180,
|
|
684
|
+
lonDeg * Math.PI / 180,
|
|
685
|
+
elR,
|
|
686
|
+
g.roverDelta
|
|
687
|
+
);
|
|
688
|
+
}
|
|
433
689
|
return { rho, u, dTropo };
|
|
434
690
|
}
|
|
435
691
|
function sdVariance(sigmaM, elRad) {
|
|
@@ -668,7 +924,8 @@ var RtkFloatEngine = class {
|
|
|
668
924
|
arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
|
|
669
925
|
glonassAr: opts.glonassAr ?? false,
|
|
670
926
|
partialFixing: opts.partialFixing ?? true,
|
|
671
|
-
minFixAmbiguities: opts.minFixAmbiguities ?? 4
|
|
927
|
+
minFixAmbiguities: opts.minFixAmbiguities ?? 4,
|
|
928
|
+
antenna: opts.antenna ?? null
|
|
672
929
|
};
|
|
673
930
|
}
|
|
674
931
|
/** Clear all filter state (position, ambiguities, lock history). */
|
|
@@ -791,7 +1048,8 @@ var RtkFloatEngine = class {
|
|
|
791
1048
|
this.ephemerides,
|
|
792
1049
|
timeMs,
|
|
793
1050
|
o.elevationMaskDeg * Math.PI / 180,
|
|
794
|
-
o.troposphere
|
|
1051
|
+
o.troposphere,
|
|
1052
|
+
o.antenna
|
|
795
1053
|
);
|
|
796
1054
|
const byPrn = new Map(geom.map((g) => [g.prn, g]));
|
|
797
1055
|
const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
|
|
@@ -1391,6 +1649,10 @@ var SbasProcessor = class {
|
|
|
1391
1649
|
tlat = 0;
|
|
1392
1650
|
degr = null;
|
|
1393
1651
|
mt27 = null;
|
|
1652
|
+
/** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
|
|
1653
|
+
* factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
|
|
1654
|
+
* scale exponent. Keyed by PRN; cleared on a mask change. */
|
|
1655
|
+
mt28 = /* @__PURE__ */ new Map();
|
|
1394
1656
|
ion = [];
|
|
1395
1657
|
/** GEO PRN(s) whose messages have been ingested. */
|
|
1396
1658
|
geoPrns = /* @__PURE__ */ new Set();
|
|
@@ -1421,6 +1683,8 @@ var SbasProcessor = class {
|
|
|
1421
1683
|
return this.decodeMt10(msg) ? type : -1;
|
|
1422
1684
|
case 27:
|
|
1423
1685
|
return this.decodeMt27(msg) ? type : -1;
|
|
1686
|
+
case 28:
|
|
1687
|
+
return this.decodeMt28(msg) ? type : -1;
|
|
1424
1688
|
case 18:
|
|
1425
1689
|
return this.decodeIgpMask(msg) ? type : -1;
|
|
1426
1690
|
case 24:
|
|
@@ -1447,6 +1711,7 @@ var SbasProcessor = class {
|
|
|
1447
1711
|
return true;
|
|
1448
1712
|
this.iodp = iodp;
|
|
1449
1713
|
this.sats = prns.map((prn) => ({ prn }));
|
|
1714
|
+
this.mt28.clear();
|
|
1450
1715
|
this.satIdx.clear();
|
|
1451
1716
|
this.sats.forEach((s, k) => {
|
|
1452
1717
|
if (s.prn) this.satIdx.set(s.prn, k);
|
|
@@ -1512,7 +1777,9 @@ var SbasProcessor = class {
|
|
|
1512
1777
|
iiono: u(117, 9) || 1,
|
|
1513
1778
|
cionoRamp: u(126, 10) * 5e-6,
|
|
1514
1779
|
rssUdre: u(136, 1) === 1,
|
|
1515
|
-
rssIono: u(137, 1) === 1
|
|
1780
|
+
rssIono: u(137, 1) === 1,
|
|
1781
|
+
ccovariance: u(138, 7) * 0.1
|
|
1782
|
+
// Table B-49: 0..12.7 m, 0.1 m
|
|
1516
1783
|
};
|
|
1517
1784
|
return true;
|
|
1518
1785
|
}
|
|
@@ -1564,6 +1831,51 @@ var SbasProcessor = class {
|
|
|
1564
1831
|
}
|
|
1565
1832
|
return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
|
|
1566
1833
|
}
|
|
1834
|
+
/** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
|
|
1835
|
+
* two satellites per message, each an upper-triangular Cholesky factor E and a
|
|
1836
|
+
* scale exponent that together define the relative clock-ephemeris covariance
|
|
1837
|
+
* used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
|
|
1838
|
+
* the current MT1 mask, so a mismatched IODP is dropped. */
|
|
1839
|
+
decodeMt28(msg) {
|
|
1840
|
+
if (this.iodp !== getBitU(msg, 14, 2)) return false;
|
|
1841
|
+
for (const base of [16, 121]) {
|
|
1842
|
+
const slot = getBitU(msg, base, 6);
|
|
1843
|
+
const prn = this.sats[slot]?.prn;
|
|
1844
|
+
const scaleExp = getBitU(msg, base + 6, 3);
|
|
1845
|
+
const e = [
|
|
1846
|
+
getBitU(msg, base + 9, 9),
|
|
1847
|
+
getBitU(msg, base + 18, 9),
|
|
1848
|
+
getBitU(msg, base + 27, 9),
|
|
1849
|
+
getBitU(msg, base + 36, 9),
|
|
1850
|
+
getBitS(msg, base + 45, 10),
|
|
1851
|
+
getBitS(msg, base + 55, 10),
|
|
1852
|
+
getBitS(msg, base + 65, 10),
|
|
1853
|
+
getBitS(msg, base + 75, 10),
|
|
1854
|
+
getBitS(msg, base + 85, 10),
|
|
1855
|
+
getBitS(msg, base + 95, 10)
|
|
1856
|
+
];
|
|
1857
|
+
if (prn && e.some((v) => v !== 0)) this.mt28.set(prn, { scaleExp, e });
|
|
1858
|
+
}
|
|
1859
|
+
return true;
|
|
1860
|
+
}
|
|
1861
|
+
/** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
|
|
1862
|
+
* δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
|
|
1863
|
+
* I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
|
|
1864
|
+
* Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
|
|
1865
|
+
mt28DeltaUdre(prn, los) {
|
|
1866
|
+
const m = this.mt28.get(prn);
|
|
1867
|
+
if (!m) return null;
|
|
1868
|
+
const sf = 2 ** (m.scaleExp - 5);
|
|
1869
|
+
const [e11, e22, e33, e44, e12, e13, e14, e23, e24, e34] = m.e;
|
|
1870
|
+
const [ix, iy, iz] = los;
|
|
1871
|
+
const v0 = (e11 * ix + e12 * iy + e13 * iz + e14) * sf;
|
|
1872
|
+
const v1 = (e22 * iy + e23 * iz + e24) * sf;
|
|
1873
|
+
const v2 = (e33 * iz + e34) * sf;
|
|
1874
|
+
const v3 = e44 * sf;
|
|
1875
|
+
const iCi = v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
|
|
1876
|
+
const ec = (this.degr?.ccovariance ?? 0) * sf;
|
|
1877
|
+
return Math.sqrt(iCi) + ec;
|
|
1878
|
+
}
|
|
1567
1879
|
decodeMixed(msg, week, tow) {
|
|
1568
1880
|
if (this.iodp !== getBitU(msg, 110, 2)) return false;
|
|
1569
1881
|
const blk = getBitU(msg, 112, 2);
|
|
@@ -1768,7 +2080,7 @@ var SbasProcessor = class {
|
|
|
1768
2080
|
* no valid (unexpired) long-term + fast correction is available. Add `dPos`
|
|
1769
2081
|
* to the satellite ECEF position and `dClkS` to its clock bias.
|
|
1770
2082
|
*/
|
|
1771
|
-
satCorrection(prn, week, tow, userLatRad, userLonRad) {
|
|
2083
|
+
satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
|
|
1772
2084
|
const idx = this.satIdx.get(prn);
|
|
1773
2085
|
if (idx === void 0) return null;
|
|
1774
2086
|
const s = this.sats[idx];
|
|
@@ -1790,7 +2102,8 @@ var SbasProcessor = class {
|
|
|
1790
2102
|
const tf = now - s.fcorr.t0s + this.tlat;
|
|
1791
2103
|
if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
|
|
1792
2104
|
const prc = s.fcorr.prc;
|
|
1793
|
-
const
|
|
2105
|
+
const mt28 = losUnitEcef ? this.mt28DeltaUdre(prn, losUnitEcef) : null;
|
|
2106
|
+
const dudre = mt28 != null ? mt28 : userLatRad != null && userLonRad != null ? this.deltaUdre(
|
|
1794
2107
|
userLatRad * 180 / Math.PI,
|
|
1795
2108
|
userLonRad * 180 / Math.PI
|
|
1796
2109
|
) : 1;
|
|
@@ -1919,14 +2232,14 @@ var SbasProcessor = class {
|
|
|
1919
2232
|
};
|
|
1920
2233
|
|
|
1921
2234
|
// src/positioning/ssr-apply.ts
|
|
1922
|
-
var
|
|
2235
|
+
var cross2 = (a, b) => [
|
|
1923
2236
|
a[1] * b[2] - a[2] * b[1],
|
|
1924
2237
|
a[2] * b[0] - a[0] * b[2],
|
|
1925
2238
|
a[0] * b[1] - a[1] * b[0]
|
|
1926
2239
|
];
|
|
1927
|
-
var
|
|
2240
|
+
var norm2 = (a) => Math.hypot(a[0], a[1], a[2]);
|
|
1928
2241
|
var unit = (a) => {
|
|
1929
|
-
const n =
|
|
2242
|
+
const n = norm2(a);
|
|
1930
2243
|
return n > 0 ? [a[0] / n, a[1] / n, a[2] / n] : [0, 0, 0];
|
|
1931
2244
|
};
|
|
1932
2245
|
function applyOrbitClock(eph, corr, timeMs) {
|
|
@@ -1940,8 +2253,8 @@ function applyOrbitClock(eph, corr, timeMs) {
|
|
|
1940
2253
|
const p = [s.x, s.y, s.z];
|
|
1941
2254
|
const v = [s.vx, s.vy, s.vz];
|
|
1942
2255
|
const er = unit(p);
|
|
1943
|
-
const en = unit(
|
|
1944
|
-
const ea =
|
|
2256
|
+
const en = unit(cross2(p, v));
|
|
2257
|
+
const ea = cross2(en, er);
|
|
1945
2258
|
const dr = corr.radial + (corr.dotRadial ?? 0) * age;
|
|
1946
2259
|
const da = corr.along + (corr.dotAlong ?? 0) * age;
|
|
1947
2260
|
const dc = corr.cross + (corr.dotCross ?? 0) * age;
|
|
@@ -2151,170 +2464,6 @@ function solidEarthTide(rcv, sun, moon) {
|
|
|
2151
2464
|
return disp;
|
|
2152
2465
|
}
|
|
2153
2466
|
|
|
2154
|
-
// src/positioning/ppp-antenna.ts
|
|
2155
|
-
function parseAntexTime(s) {
|
|
2156
|
-
const p = s.trim().split(/\s+/).map(Number);
|
|
2157
|
-
if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
|
|
2158
|
-
return Date.UTC(
|
|
2159
|
-
p[0],
|
|
2160
|
-
(p[1] || 1) - 1,
|
|
2161
|
-
p[2] || 1,
|
|
2162
|
-
p[3] ?? 0,
|
|
2163
|
-
p[4] ?? 0,
|
|
2164
|
-
Math.floor(p[5] ?? 0)
|
|
2165
|
-
);
|
|
2166
|
-
}
|
|
2167
|
-
function freqOf(entry, code) {
|
|
2168
|
-
return entry.frequencies.find((f) => f.frequency === code);
|
|
2169
|
-
}
|
|
2170
|
-
function combineIf(fA, fB, f1, f2) {
|
|
2171
|
-
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
2172
|
-
const mm = 1e-3;
|
|
2173
|
-
const pco = [
|
|
2174
|
-
(g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
|
|
2175
|
-
(g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
|
|
2176
|
-
(g * fA.pcoU - (g - 1) * fB.pcoU) * mm
|
|
2177
|
-
];
|
|
2178
|
-
const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
|
|
2179
|
-
const pcvNoazi = new Array(nz);
|
|
2180
|
-
for (let i = 0; i < nz; i++) {
|
|
2181
|
-
pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
|
|
2182
|
-
}
|
|
2183
|
-
return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
|
|
2184
|
-
}
|
|
2185
|
-
function buildPppAntenna(antex) {
|
|
2186
|
-
const sats = /* @__PURE__ */ new Map();
|
|
2187
|
-
const rcvs = /* @__PURE__ */ new Map();
|
|
2188
|
-
for (const a of antex.antennas) {
|
|
2189
|
-
if (a.isSatellite) {
|
|
2190
|
-
const list = sats.get(a.serialNo) ?? [];
|
|
2191
|
-
list.push(a);
|
|
2192
|
-
sats.set(a.serialNo, list);
|
|
2193
|
-
} else if (!rcvs.has(a.type)) {
|
|
2194
|
-
rcvs.set(a.type, a);
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
const withZen = (entry, off) => ({
|
|
2198
|
-
...off,
|
|
2199
|
-
pcvZen1Deg: entry.zen1,
|
|
2200
|
-
pcvDzenDeg: entry.dzen
|
|
2201
|
-
});
|
|
2202
|
-
return {
|
|
2203
|
-
satOffset(prn, dateMs, band1, band2, f1, f2) {
|
|
2204
|
-
const list = sats.get(prn);
|
|
2205
|
-
if (!list) return null;
|
|
2206
|
-
const entry = list.find((e) => {
|
|
2207
|
-
const from = parseAntexTime(e.validFrom);
|
|
2208
|
-
const until = parseAntexTime(e.validUntil);
|
|
2209
|
-
return (from == null || dateMs >= from) && (until == null || dateMs <= until);
|
|
2210
|
-
});
|
|
2211
|
-
if (!entry) return null;
|
|
2212
|
-
const a = freqOf(entry, band1);
|
|
2213
|
-
const b = freqOf(entry, band2);
|
|
2214
|
-
if (!a || !b) return null;
|
|
2215
|
-
return withZen(entry, combineIf(a, b, f1, f2));
|
|
2216
|
-
},
|
|
2217
|
-
rcvOffset(antType, band1, band2, f1, f2) {
|
|
2218
|
-
const entry = rcvs.get(antType);
|
|
2219
|
-
if (!entry) return null;
|
|
2220
|
-
const a = freqOf(entry, band1);
|
|
2221
|
-
const b = freqOf(entry, band2);
|
|
2222
|
-
if (!a || !b) return null;
|
|
2223
|
-
return withZen(entry, combineIf(a, b, f1, f2));
|
|
2224
|
-
}
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
function cross2(a, b) {
|
|
2228
|
-
return [
|
|
2229
|
-
a[1] * b[2] - a[2] * b[1],
|
|
2230
|
-
a[2] * b[0] - a[0] * b[2],
|
|
2231
|
-
a[0] * b[1] - a[1] * b[0]
|
|
2232
|
-
];
|
|
2233
|
-
}
|
|
2234
|
-
function norm2(a) {
|
|
2235
|
-
const n = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
2236
|
-
return [a[0] / n, a[1] / n, a[2] / n];
|
|
2237
|
-
}
|
|
2238
|
-
function dot(a, b) {
|
|
2239
|
-
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
2240
|
-
}
|
|
2241
|
-
function satBodyFrame(satEcef, sunEcef2) {
|
|
2242
|
-
const z = norm2([-satEcef[0], -satEcef[1], -satEcef[2]]);
|
|
2243
|
-
const eSun = norm2([
|
|
2244
|
-
sunEcef2[0] - satEcef[0],
|
|
2245
|
-
sunEcef2[1] - satEcef[1],
|
|
2246
|
-
sunEcef2[2] - satEcef[2]
|
|
2247
|
-
]);
|
|
2248
|
-
const y = norm2(cross2(z, eSun));
|
|
2249
|
-
const x = norm2(cross2(y, z));
|
|
2250
|
-
return { x, y, z };
|
|
2251
|
-
}
|
|
2252
|
-
function satPcoToEcef(pcoBody, frame) {
|
|
2253
|
-
return [
|
|
2254
|
-
pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
|
|
2255
|
-
pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
|
|
2256
|
-
pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
|
|
2257
|
-
];
|
|
2258
|
-
}
|
|
2259
|
-
function enuToEcef(enu, latRad, lonRad) {
|
|
2260
|
-
const sl = Math.sin(latRad);
|
|
2261
|
-
const cl = Math.cos(latRad);
|
|
2262
|
-
const so = Math.sin(lonRad);
|
|
2263
|
-
const co = Math.cos(lonRad);
|
|
2264
|
-
const [e, n, u] = enu;
|
|
2265
|
-
return [
|
|
2266
|
-
-so * e - sl * co * n + cl * co * u,
|
|
2267
|
-
co * e - sl * so * n + cl * so * u,
|
|
2268
|
-
cl * n + sl * u
|
|
2269
|
-
];
|
|
2270
|
-
}
|
|
2271
|
-
function interpPcv(off, angleDeg) {
|
|
2272
|
-
const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
|
|
2273
|
-
if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
|
|
2274
|
-
const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
|
|
2275
|
-
if (x <= 0) return pcvNoazi[0];
|
|
2276
|
-
if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
|
|
2277
|
-
const i = Math.floor(x);
|
|
2278
|
-
const t = x - i;
|
|
2279
|
-
return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
|
|
2280
|
-
}
|
|
2281
|
-
function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
|
|
2282
|
-
const k = [
|
|
2283
|
-
-losRcvToSat[0],
|
|
2284
|
-
-losRcvToSat[1],
|
|
2285
|
-
-losRcvToSat[2]
|
|
2286
|
-
];
|
|
2287
|
-
const kx = satFrame.x;
|
|
2288
|
-
const ky = satFrame.y;
|
|
2289
|
-
const kdotx = dot(k, kx);
|
|
2290
|
-
const kcrossY = cross2(k, ky);
|
|
2291
|
-
const dS = [
|
|
2292
|
-
kx[0] - k[0] * kdotx - kcrossY[0],
|
|
2293
|
-
kx[1] - k[1] * kdotx - kcrossY[1],
|
|
2294
|
-
kx[2] - k[2] * kdotx - kcrossY[2]
|
|
2295
|
-
];
|
|
2296
|
-
const north = enuToEcef([0, 1, 0], latRad, lonRad);
|
|
2297
|
-
const east = enuToEcef([1, 0, 0], latRad, lonRad);
|
|
2298
|
-
const kdotn = dot(k, north);
|
|
2299
|
-
const kcrossE = cross2(k, east);
|
|
2300
|
-
const dR = [
|
|
2301
|
-
north[0] - k[0] * kdotn + kcrossE[0],
|
|
2302
|
-
north[1] - k[1] * kdotn + kcrossE[1],
|
|
2303
|
-
north[2] - k[2] * kdotn + kcrossE[2]
|
|
2304
|
-
];
|
|
2305
|
-
const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
|
|
2306
|
-
const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
|
|
2307
|
-
let cosPhi = dot(dS, dR) / (nS * nR);
|
|
2308
|
-
cosPhi = Math.max(-1, Math.min(1, cosPhi));
|
|
2309
|
-
const zeta = cross2(dS, dR);
|
|
2310
|
-
const sign = dot(k, zeta) >= 0 ? 1 : -1;
|
|
2311
|
-
const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
|
|
2312
|
-
const prev = state.get(prn);
|
|
2313
|
-
const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
|
|
2314
|
-
state.set(prn, wu);
|
|
2315
|
-
return wu;
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
2467
|
// src/positioning/ppp-corrections.ts
|
|
2319
2468
|
function applyCorrections(corrections, geom, ctx) {
|
|
2320
2469
|
if (corrections.evaluate) return corrections.evaluate(geom, ctx);
|
|
@@ -3674,7 +3823,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
3674
3823
|
used.push(prn);
|
|
3675
3824
|
azels.push(azel);
|
|
3676
3825
|
if (sbas) {
|
|
3677
|
-
const
|
|
3826
|
+
const dx = sat.x - x, dy = sat.y - y, dz = sat.z - z;
|
|
3827
|
+
const dr = Math.hypot(dx, dy, dz) || 1;
|
|
3828
|
+
const los = [dx / dr, dy / dr, dz / dr];
|
|
3829
|
+
const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon, los);
|
|
3678
3830
|
const si = sbas.ionoDelay(
|
|
3679
3831
|
gpsWeek,
|
|
3680
3832
|
gpsTow,
|
|
@@ -3714,6 +3866,10 @@ export {
|
|
|
3714
3866
|
gimSlantIonoDelayL1,
|
|
3715
3867
|
lambdaSearch,
|
|
3716
3868
|
lambdaReduction,
|
|
3869
|
+
buildPppAntenna,
|
|
3870
|
+
buildRtkAntenna,
|
|
3871
|
+
rcvAntennaRangeM,
|
|
3872
|
+
antexFreqOfGroup,
|
|
3717
3873
|
toRtkEpoch,
|
|
3718
3874
|
solveDgnss,
|
|
3719
3875
|
RtkFloatEngine,
|
|
@@ -3728,7 +3884,6 @@ export {
|
|
|
3728
3884
|
sunEcef,
|
|
3729
3885
|
moonEcef,
|
|
3730
3886
|
solidEarthTide,
|
|
3731
|
-
buildPppAntenna,
|
|
3732
3887
|
createPppCorrections,
|
|
3733
3888
|
Sp3EphemerisSource,
|
|
3734
3889
|
PppEngine,
|