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
package/dist/positioning.cjs
CHANGED
|
@@ -25,8 +25,10 @@ __export(positioning_exports, {
|
|
|
25
25
|
RtkFloatEngine: () => RtkFloatEngine,
|
|
26
26
|
SbasProcessor: () => SbasProcessor,
|
|
27
27
|
Sp3EphemerisSource: () => Sp3EphemerisSource,
|
|
28
|
+
antexFreqOfGroup: () => antexFreqOfGroup,
|
|
28
29
|
applyOrbitClock: () => applyOrbitClock,
|
|
29
30
|
buildPppAntenna: () => buildPppAntenna,
|
|
31
|
+
buildRtkAntenna: () => buildRtkAntenna,
|
|
30
32
|
createPppCorrections: () => createPppCorrections,
|
|
31
33
|
estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
|
|
32
34
|
estimateNetworkFcbs: () => estimateNetworkFcbs,
|
|
@@ -45,6 +47,7 @@ __export(positioning_exports, {
|
|
|
45
47
|
niellMapping: () => niellMapping,
|
|
46
48
|
nlWavelength: () => nlWavelength,
|
|
47
49
|
postProcessRtk: () => postProcessRtk,
|
|
50
|
+
rcvAntennaRangeM: () => rcvAntennaRangeM,
|
|
48
51
|
resolvePppAmbiguities: () => resolvePppAmbiguities,
|
|
49
52
|
satClockCorrection: () => satClockCorrection,
|
|
50
53
|
sbasIonoDeg: () => sbasIonoDeg,
|
|
@@ -759,6 +762,211 @@ function lambdaReduction(Q, n) {
|
|
|
759
762
|
return Z;
|
|
760
763
|
}
|
|
761
764
|
|
|
765
|
+
// src/positioning/ppp-antenna.ts
|
|
766
|
+
function parseAntexTime(s) {
|
|
767
|
+
const p = s.trim().split(/\s+/).map(Number);
|
|
768
|
+
if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
|
|
769
|
+
return Date.UTC(
|
|
770
|
+
p[0],
|
|
771
|
+
(p[1] || 1) - 1,
|
|
772
|
+
p[2] || 1,
|
|
773
|
+
p[3] ?? 0,
|
|
774
|
+
p[4] ?? 0,
|
|
775
|
+
Math.floor(p[5] ?? 0)
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
function freqOf(entry, code) {
|
|
779
|
+
return entry.frequencies.find((f) => f.frequency === code);
|
|
780
|
+
}
|
|
781
|
+
function combineIf(fA, fB, f1, f2) {
|
|
782
|
+
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
783
|
+
const mm = 1e-3;
|
|
784
|
+
const pco = [
|
|
785
|
+
(g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
|
|
786
|
+
(g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
|
|
787
|
+
(g * fA.pcoU - (g - 1) * fB.pcoU) * mm
|
|
788
|
+
];
|
|
789
|
+
const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
|
|
790
|
+
const pcvNoazi = new Array(nz);
|
|
791
|
+
for (let i = 0; i < nz; i++) {
|
|
792
|
+
pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
|
|
793
|
+
}
|
|
794
|
+
return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
|
|
795
|
+
}
|
|
796
|
+
function buildPppAntenna(antex) {
|
|
797
|
+
const sats = /* @__PURE__ */ new Map();
|
|
798
|
+
const rcvs = /* @__PURE__ */ new Map();
|
|
799
|
+
for (const a of antex.antennas) {
|
|
800
|
+
if (a.isSatellite) {
|
|
801
|
+
const list = sats.get(a.serialNo) ?? [];
|
|
802
|
+
list.push(a);
|
|
803
|
+
sats.set(a.serialNo, list);
|
|
804
|
+
} else if (!rcvs.has(a.type)) {
|
|
805
|
+
rcvs.set(a.type, a);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const withZen = (entry, off) => ({
|
|
809
|
+
...off,
|
|
810
|
+
pcvZen1Deg: entry.zen1,
|
|
811
|
+
pcvDzenDeg: entry.dzen
|
|
812
|
+
});
|
|
813
|
+
return {
|
|
814
|
+
satOffset(prn, dateMs, band1, band2, f1, f2) {
|
|
815
|
+
const list = sats.get(prn);
|
|
816
|
+
if (!list) return null;
|
|
817
|
+
const entry = list.find((e) => {
|
|
818
|
+
const from = parseAntexTime(e.validFrom);
|
|
819
|
+
const until = parseAntexTime(e.validUntil);
|
|
820
|
+
return (from == null || dateMs >= from) && (until == null || dateMs <= until);
|
|
821
|
+
});
|
|
822
|
+
if (!entry) return null;
|
|
823
|
+
const a = freqOf(entry, band1);
|
|
824
|
+
const b = freqOf(entry, band2);
|
|
825
|
+
if (!a || !b) return null;
|
|
826
|
+
return withZen(entry, combineIf(a, b, f1, f2));
|
|
827
|
+
},
|
|
828
|
+
rcvOffset(antType, band1, band2, f1, f2) {
|
|
829
|
+
const entry = rcvs.get(antType);
|
|
830
|
+
if (!entry) return null;
|
|
831
|
+
const a = freqOf(entry, band1);
|
|
832
|
+
const b = freqOf(entry, band2);
|
|
833
|
+
if (!a || !b) return null;
|
|
834
|
+
return withZen(entry, combineIf(a, b, f1, f2));
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function cross(a, b) {
|
|
839
|
+
return [
|
|
840
|
+
a[1] * b[2] - a[2] * b[1],
|
|
841
|
+
a[2] * b[0] - a[0] * b[2],
|
|
842
|
+
a[0] * b[1] - a[1] * b[0]
|
|
843
|
+
];
|
|
844
|
+
}
|
|
845
|
+
function norm(a) {
|
|
846
|
+
const n = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
847
|
+
return [a[0] / n, a[1] / n, a[2] / n];
|
|
848
|
+
}
|
|
849
|
+
function dot(a, b) {
|
|
850
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
851
|
+
}
|
|
852
|
+
function satBodyFrame(satEcef, sunEcef2) {
|
|
853
|
+
const z = norm([-satEcef[0], -satEcef[1], -satEcef[2]]);
|
|
854
|
+
const eSun = norm([
|
|
855
|
+
sunEcef2[0] - satEcef[0],
|
|
856
|
+
sunEcef2[1] - satEcef[1],
|
|
857
|
+
sunEcef2[2] - satEcef[2]
|
|
858
|
+
]);
|
|
859
|
+
const y = norm(cross(z, eSun));
|
|
860
|
+
const x = norm(cross(y, z));
|
|
861
|
+
return { x, y, z };
|
|
862
|
+
}
|
|
863
|
+
function satPcoToEcef(pcoBody, frame) {
|
|
864
|
+
return [
|
|
865
|
+
pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
|
|
866
|
+
pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
|
|
867
|
+
pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
|
|
868
|
+
];
|
|
869
|
+
}
|
|
870
|
+
function enuToEcef(enu, latRad, lonRad) {
|
|
871
|
+
const sl = Math.sin(latRad);
|
|
872
|
+
const cl = Math.cos(latRad);
|
|
873
|
+
const so = Math.sin(lonRad);
|
|
874
|
+
const co = Math.cos(lonRad);
|
|
875
|
+
const [e, n, u] = enu;
|
|
876
|
+
return [
|
|
877
|
+
-so * e - sl * co * n + cl * co * u,
|
|
878
|
+
co * e - sl * so * n + cl * so * u,
|
|
879
|
+
cl * n + sl * u
|
|
880
|
+
];
|
|
881
|
+
}
|
|
882
|
+
function interpPcv(off, angleDeg) {
|
|
883
|
+
const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
|
|
884
|
+
if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
|
|
885
|
+
const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
|
|
886
|
+
if (x <= 0) return pcvNoazi[0];
|
|
887
|
+
if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
|
|
888
|
+
const i = Math.floor(x);
|
|
889
|
+
const t = x - i;
|
|
890
|
+
return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
|
|
891
|
+
}
|
|
892
|
+
function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
|
|
893
|
+
const k = [
|
|
894
|
+
-losRcvToSat[0],
|
|
895
|
+
-losRcvToSat[1],
|
|
896
|
+
-losRcvToSat[2]
|
|
897
|
+
];
|
|
898
|
+
const kx = satFrame.x;
|
|
899
|
+
const ky = satFrame.y;
|
|
900
|
+
const kdotx = dot(k, kx);
|
|
901
|
+
const kcrossY = cross(k, ky);
|
|
902
|
+
const dS = [
|
|
903
|
+
kx[0] - k[0] * kdotx - kcrossY[0],
|
|
904
|
+
kx[1] - k[1] * kdotx - kcrossY[1],
|
|
905
|
+
kx[2] - k[2] * kdotx - kcrossY[2]
|
|
906
|
+
];
|
|
907
|
+
const north = enuToEcef([0, 1, 0], latRad, lonRad);
|
|
908
|
+
const east = enuToEcef([1, 0, 0], latRad, lonRad);
|
|
909
|
+
const kdotn = dot(k, north);
|
|
910
|
+
const kcrossE = cross(k, east);
|
|
911
|
+
const dR = [
|
|
912
|
+
north[0] - k[0] * kdotn + kcrossE[0],
|
|
913
|
+
north[1] - k[1] * kdotn + kcrossE[1],
|
|
914
|
+
north[2] - k[2] * kdotn + kcrossE[2]
|
|
915
|
+
];
|
|
916
|
+
const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
|
|
917
|
+
const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
|
|
918
|
+
let cosPhi = dot(dS, dR) / (nS * nR);
|
|
919
|
+
cosPhi = Math.max(-1, Math.min(1, cosPhi));
|
|
920
|
+
const zeta = cross(dS, dR);
|
|
921
|
+
const sign = dot(k, zeta) >= 0 ? 1 : -1;
|
|
922
|
+
const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
|
|
923
|
+
const prev = state.get(prn);
|
|
924
|
+
const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
|
|
925
|
+
state.set(prn, wu);
|
|
926
|
+
return wu;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/positioning/rtk-antenna.ts
|
|
930
|
+
function freqOf2(entry, code) {
|
|
931
|
+
return entry.frequencies.find((f) => f.frequency === code);
|
|
932
|
+
}
|
|
933
|
+
function buildRtkAntenna(antex) {
|
|
934
|
+
const rcvs = /* @__PURE__ */ new Map();
|
|
935
|
+
for (const a of antex.antennas) {
|
|
936
|
+
if (!a.isSatellite && !rcvs.has(a.type)) rcvs.set(a.type, a);
|
|
937
|
+
}
|
|
938
|
+
const mm = 1e-3;
|
|
939
|
+
return {
|
|
940
|
+
rcvOffset(antType, freq) {
|
|
941
|
+
const entry = rcvs.get(antType);
|
|
942
|
+
if (!entry) return null;
|
|
943
|
+
const f = freqOf2(entry, freq);
|
|
944
|
+
if (!f) return null;
|
|
945
|
+
return {
|
|
946
|
+
pco: [f.pcoN * mm, f.pcoE * mm, f.pcoU * mm],
|
|
947
|
+
pcvZen1Deg: entry.zen1,
|
|
948
|
+
pcvDzenDeg: entry.dzen,
|
|
949
|
+
pcvNoazi: f.pcvNoazi.map((v) => v * mm)
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function rcvAntennaRangeM(off, losRcvToSat, latRad, lonRad, elRad, antDeltaEnu = [0, 0, 0]) {
|
|
955
|
+
const total = [
|
|
956
|
+
off.pco[0] + antDeltaEnu[0],
|
|
957
|
+
off.pco[1] + antDeltaEnu[1],
|
|
958
|
+
off.pco[2] + antDeltaEnu[2]
|
|
959
|
+
];
|
|
960
|
+
const ecef = enuToEcef(total, latRad, lonRad);
|
|
961
|
+
let m = -dot(losRcvToSat, ecef);
|
|
962
|
+
const zenithDeg = 90 - elRad * 180 / Math.PI;
|
|
963
|
+
m += interpPcv(off, zenithDeg);
|
|
964
|
+
return m;
|
|
965
|
+
}
|
|
966
|
+
function antexFreqOfGroup(group) {
|
|
967
|
+
return `${group[0]}0${group[1]}`;
|
|
968
|
+
}
|
|
969
|
+
|
|
762
970
|
// src/positioning/rtk.ts
|
|
763
971
|
var L1_CODES = {
|
|
764
972
|
G: ["1C"],
|
|
@@ -824,8 +1032,16 @@ function resolveEphemeris(src, prn, timeMs) {
|
|
|
824
1032
|
return selectEphemeris(src, prn, timeMs);
|
|
825
1033
|
return src.get(prn) ?? null;
|
|
826
1034
|
}
|
|
827
|
-
|
|
1035
|
+
var ZERO_DELTA = [0, 0, 0];
|
|
1036
|
+
function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, troposphere, antenna = null) {
|
|
828
1037
|
const out = [];
|
|
1038
|
+
let baseLatRad = 0;
|
|
1039
|
+
let baseLonRad = 0;
|
|
1040
|
+
if (antenna?.base) {
|
|
1041
|
+
const [latDeg, lonDeg] = ecefToGeodetic(basePos[0], basePos[1], basePos[2]);
|
|
1042
|
+
baseLatRad = latDeg * Math.PI / 180;
|
|
1043
|
+
baseLonRad = lonDeg * Math.PI / 180;
|
|
1044
|
+
}
|
|
829
1045
|
for (const [prn, mR] of rover) {
|
|
830
1046
|
const sys = prn[0];
|
|
831
1047
|
if (!"GERCJ".includes(sys)) continue;
|
|
@@ -850,14 +1066,43 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
|
|
|
850
1066
|
satB.z - basePos[2]
|
|
851
1067
|
) / C_LIGHT;
|
|
852
1068
|
const [bx, by, bz] = sagnac(satB, travelB);
|
|
853
|
-
|
|
1069
|
+
let rhoB = Math.hypot(bx - basePos[0], by - basePos[1], bz - basePos[2]);
|
|
854
1070
|
const elB = ecefToAzEl(basePos[0], basePos[1], basePos[2], bx, by, bz).el;
|
|
855
1071
|
if (elB < maskRad) continue;
|
|
1072
|
+
const group = sys + mR.code;
|
|
1073
|
+
let offR = null;
|
|
1074
|
+
let roverDelta = ZERO_DELTA;
|
|
1075
|
+
if (antenna) {
|
|
1076
|
+
const antexFreq = antexFreqOfGroup(group);
|
|
1077
|
+
if (antenna.base) {
|
|
1078
|
+
const offB = antenna.model.rcvOffset(antenna.base.type, antexFreq);
|
|
1079
|
+
if (offB) {
|
|
1080
|
+
const inv = 1 / rhoB;
|
|
1081
|
+
const losB = [
|
|
1082
|
+
(bx - basePos[0]) * inv,
|
|
1083
|
+
(by - basePos[1]) * inv,
|
|
1084
|
+
(bz - basePos[2]) * inv
|
|
1085
|
+
];
|
|
1086
|
+
rhoB += rcvAntennaRangeM(
|
|
1087
|
+
offB,
|
|
1088
|
+
losB,
|
|
1089
|
+
baseLatRad,
|
|
1090
|
+
baseLonRad,
|
|
1091
|
+
elB,
|
|
1092
|
+
antenna.base.deltaEnu
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (antenna.rover) {
|
|
1097
|
+
offR = antenna.model.rcvOffset(antenna.rover.type, antexFreq);
|
|
1098
|
+
roverDelta = antenna.rover.deltaEnu ?? ZERO_DELTA;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
856
1101
|
const gloK = sys === "R" ? mR.gloChannel ?? mB.gloChannel ?? eph.freqNum ?? null : null;
|
|
857
1102
|
const freq = carrierFreqHz(sys, mR.code, gloK);
|
|
858
1103
|
out.push({
|
|
859
1104
|
prn,
|
|
860
|
-
group
|
|
1105
|
+
group,
|
|
861
1106
|
lambda: freq > 0 ? C_LIGHT / freq : 0,
|
|
862
1107
|
satR,
|
|
863
1108
|
rhoB,
|
|
@@ -868,7 +1113,9 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
|
|
|
868
1113
|
cpR: mR.cp ?? null,
|
|
869
1114
|
cpB: mB.cp ?? null,
|
|
870
1115
|
lockR: mR.lockTimeMs,
|
|
871
|
-
lockB: mB.lockTimeMs
|
|
1116
|
+
lockB: mB.lockTimeMs,
|
|
1117
|
+
offR,
|
|
1118
|
+
roverDelta
|
|
872
1119
|
});
|
|
873
1120
|
}
|
|
874
1121
|
return out;
|
|
@@ -876,17 +1123,29 @@ function buildGeometry(rover, base, basePos, ephemerides, timeMs, maskRad, tropo
|
|
|
876
1123
|
function roverTerms(g, x, y, z, troposphere) {
|
|
877
1124
|
const travel = Math.hypot(g.satR.x - x, g.satR.y - y, g.satR.z - z) / C_LIGHT;
|
|
878
1125
|
const [sx, sy, sz] = sagnac(g.satR, travel);
|
|
879
|
-
|
|
1126
|
+
let rho = Math.hypot(sx - x, sy - y, sz - z);
|
|
880
1127
|
const u = [
|
|
881
1128
|
(x - sx) / rho,
|
|
882
1129
|
(y - sy) / rho,
|
|
883
1130
|
(z - sz) / rho
|
|
884
1131
|
];
|
|
1132
|
+
const needEl = troposphere || g.offR != null;
|
|
1133
|
+
const elR = needEl ? ecefToAzEl(x, y, z, sx, sy, sz).el : 0;
|
|
885
1134
|
let dTropo = 0;
|
|
886
1135
|
if (troposphere) {
|
|
887
|
-
const elR = ecefToAzEl(x, y, z, sx, sy, sz).el;
|
|
888
1136
|
dTropo = tropoDelay(Math.max(elR, 0.05)) - g.tropoB;
|
|
889
1137
|
}
|
|
1138
|
+
if (g.offR) {
|
|
1139
|
+
const [latDeg, lonDeg] = ecefToGeodetic(x, y, z);
|
|
1140
|
+
rho += rcvAntennaRangeM(
|
|
1141
|
+
g.offR,
|
|
1142
|
+
[-u[0], -u[1], -u[2]],
|
|
1143
|
+
latDeg * Math.PI / 180,
|
|
1144
|
+
lonDeg * Math.PI / 180,
|
|
1145
|
+
elR,
|
|
1146
|
+
g.roverDelta
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
890
1149
|
return { rho, u, dTropo };
|
|
891
1150
|
}
|
|
892
1151
|
function sdVariance(sigmaM, elRad) {
|
|
@@ -1125,7 +1384,8 @@ var RtkFloatEngine = class {
|
|
|
1125
1384
|
arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
|
|
1126
1385
|
glonassAr: opts.glonassAr ?? false,
|
|
1127
1386
|
partialFixing: opts.partialFixing ?? true,
|
|
1128
|
-
minFixAmbiguities: opts.minFixAmbiguities ?? 4
|
|
1387
|
+
minFixAmbiguities: opts.minFixAmbiguities ?? 4,
|
|
1388
|
+
antenna: opts.antenna ?? null
|
|
1129
1389
|
};
|
|
1130
1390
|
}
|
|
1131
1391
|
/** Clear all filter state (position, ambiguities, lock history). */
|
|
@@ -1248,7 +1508,8 @@ var RtkFloatEngine = class {
|
|
|
1248
1508
|
this.ephemerides,
|
|
1249
1509
|
timeMs,
|
|
1250
1510
|
o.elevationMaskDeg * Math.PI / 180,
|
|
1251
|
-
o.troposphere
|
|
1511
|
+
o.troposphere,
|
|
1512
|
+
o.antenna
|
|
1252
1513
|
);
|
|
1253
1514
|
const byPrn = new Map(geom.map((g) => [g.prn, g]));
|
|
1254
1515
|
const dtS = this.lastMs !== null ? (timeMs - this.lastMs) / 1e3 : 0;
|
|
@@ -1871,6 +2132,10 @@ var SbasProcessor = class {
|
|
|
1871
2132
|
tlat = 0;
|
|
1872
2133
|
degr = null;
|
|
1873
2134
|
mt27 = null;
|
|
2135
|
+
/** MT28 clock-ephemeris covariance per PRN: the upper-triangular Cholesky
|
|
2136
|
+
* factor E (10 elements [E11,E22,E33,E44,E12,E13,E14,E23,E24,E34]) and its
|
|
2137
|
+
* scale exponent. Keyed by PRN; cleared on a mask change. */
|
|
2138
|
+
mt28 = /* @__PURE__ */ new Map();
|
|
1874
2139
|
ion = [];
|
|
1875
2140
|
/** GEO PRN(s) whose messages have been ingested. */
|
|
1876
2141
|
geoPrns = /* @__PURE__ */ new Set();
|
|
@@ -1901,6 +2166,8 @@ var SbasProcessor = class {
|
|
|
1901
2166
|
return this.decodeMt10(msg) ? type : -1;
|
|
1902
2167
|
case 27:
|
|
1903
2168
|
return this.decodeMt27(msg) ? type : -1;
|
|
2169
|
+
case 28:
|
|
2170
|
+
return this.decodeMt28(msg) ? type : -1;
|
|
1904
2171
|
case 18:
|
|
1905
2172
|
return this.decodeIgpMask(msg) ? type : -1;
|
|
1906
2173
|
case 24:
|
|
@@ -1927,6 +2194,7 @@ var SbasProcessor = class {
|
|
|
1927
2194
|
return true;
|
|
1928
2195
|
this.iodp = iodp;
|
|
1929
2196
|
this.sats = prns.map((prn) => ({ prn }));
|
|
2197
|
+
this.mt28.clear();
|
|
1930
2198
|
this.satIdx.clear();
|
|
1931
2199
|
this.sats.forEach((s, k) => {
|
|
1932
2200
|
if (s.prn) this.satIdx.set(s.prn, k);
|
|
@@ -1992,7 +2260,9 @@ var SbasProcessor = class {
|
|
|
1992
2260
|
iiono: u(117, 9) || 1,
|
|
1993
2261
|
cionoRamp: u(126, 10) * 5e-6,
|
|
1994
2262
|
rssUdre: u(136, 1) === 1,
|
|
1995
|
-
rssIono: u(137, 1) === 1
|
|
2263
|
+
rssIono: u(137, 1) === 1,
|
|
2264
|
+
ccovariance: u(138, 7) * 0.1
|
|
2265
|
+
// Table B-49: 0..12.7 m, 0.1 m
|
|
1996
2266
|
};
|
|
1997
2267
|
return true;
|
|
1998
2268
|
}
|
|
@@ -2044,6 +2314,51 @@ var SbasProcessor = class {
|
|
|
2044
2314
|
}
|
|
2045
2315
|
return best ?? DELTA_UDRE[s.udreOutside] ?? 1;
|
|
2046
2316
|
}
|
|
2317
|
+
/** MT28 clock-ephemeris covariance (ICAO Annex 10 Vol I §3.5.4.7 / Table B-51):
|
|
2318
|
+
* two satellites per message, each an upper-triangular Cholesky factor E and a
|
|
2319
|
+
* scale exponent that together define the relative clock-ephemeris covariance
|
|
2320
|
+
* used for a geometry-dependent δUDRE (§3.5.5.6.2.5). Slots are indexed into
|
|
2321
|
+
* the current MT1 mask, so a mismatched IODP is dropped. */
|
|
2322
|
+
decodeMt28(msg) {
|
|
2323
|
+
if (this.iodp !== getBitU(msg, 14, 2)) return false;
|
|
2324
|
+
for (const base of [16, 121]) {
|
|
2325
|
+
const slot = getBitU(msg, base, 6);
|
|
2326
|
+
const prn = this.sats[slot]?.prn;
|
|
2327
|
+
const scaleExp = getBitU(msg, base + 6, 3);
|
|
2328
|
+
const e = [
|
|
2329
|
+
getBitU(msg, base + 9, 9),
|
|
2330
|
+
getBitU(msg, base + 18, 9),
|
|
2331
|
+
getBitU(msg, base + 27, 9),
|
|
2332
|
+
getBitU(msg, base + 36, 9),
|
|
2333
|
+
getBitS(msg, base + 45, 10),
|
|
2334
|
+
getBitS(msg, base + 55, 10),
|
|
2335
|
+
getBitS(msg, base + 65, 10),
|
|
2336
|
+
getBitS(msg, base + 75, 10),
|
|
2337
|
+
getBitS(msg, base + 85, 10),
|
|
2338
|
+
getBitS(msg, base + 95, 10)
|
|
2339
|
+
];
|
|
2340
|
+
if (prn && e.some((v) => v !== 0)) this.mt28.set(prn, { scaleExp, e });
|
|
2341
|
+
}
|
|
2342
|
+
return true;
|
|
2343
|
+
}
|
|
2344
|
+
/** Geometry-dependent δUDRE multiplier from MT28 (§3.5.5.6.2.5):
|
|
2345
|
+
* δUDRE = √(Iᵀ·C·I) + ε_c, with C = Rᵀ·R, R = E·2^(scaleExp−5),
|
|
2346
|
+
* I = [î_user→sat, 1], ε_c = C_covariance·2^(scaleExp−5). Since C = RᵀR,
|
|
2347
|
+
* Iᵀ·C·I = ‖R·I‖², so we form R·I directly. Null when no MT28 for `prn`. */
|
|
2348
|
+
mt28DeltaUdre(prn, los) {
|
|
2349
|
+
const m = this.mt28.get(prn);
|
|
2350
|
+
if (!m) return null;
|
|
2351
|
+
const sf = 2 ** (m.scaleExp - 5);
|
|
2352
|
+
const [e11, e22, e33, e44, e12, e13, e14, e23, e24, e34] = m.e;
|
|
2353
|
+
const [ix, iy, iz] = los;
|
|
2354
|
+
const v0 = (e11 * ix + e12 * iy + e13 * iz + e14) * sf;
|
|
2355
|
+
const v1 = (e22 * iy + e23 * iz + e24) * sf;
|
|
2356
|
+
const v2 = (e33 * iz + e34) * sf;
|
|
2357
|
+
const v3 = e44 * sf;
|
|
2358
|
+
const iCi = v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
|
|
2359
|
+
const ec = (this.degr?.ccovariance ?? 0) * sf;
|
|
2360
|
+
return Math.sqrt(iCi) + ec;
|
|
2361
|
+
}
|
|
2047
2362
|
decodeMixed(msg, week, tow) {
|
|
2048
2363
|
if (this.iodp !== getBitU(msg, 110, 2)) return false;
|
|
2049
2364
|
const blk = getBitU(msg, 112, 2);
|
|
@@ -2248,7 +2563,7 @@ var SbasProcessor = class {
|
|
|
2248
2563
|
* no valid (unexpired) long-term + fast correction is available. Add `dPos`
|
|
2249
2564
|
* to the satellite ECEF position and `dClkS` to its clock bias.
|
|
2250
2565
|
*/
|
|
2251
|
-
satCorrection(prn, week, tow, userLatRad, userLonRad) {
|
|
2566
|
+
satCorrection(prn, week, tow, userLatRad, userLonRad, losUnitEcef) {
|
|
2252
2567
|
const idx = this.satIdx.get(prn);
|
|
2253
2568
|
if (idx === void 0) return null;
|
|
2254
2569
|
const s = this.sats[idx];
|
|
@@ -2270,7 +2585,8 @@ var SbasProcessor = class {
|
|
|
2270
2585
|
const tf = now - s.fcorr.t0s + this.tlat;
|
|
2271
2586
|
if (Math.abs(tf) > MAXSBSAGEF || s.fcorr.udre >= 15) return null;
|
|
2272
2587
|
const prc = s.fcorr.prc;
|
|
2273
|
-
const
|
|
2588
|
+
const mt28 = losUnitEcef ? this.mt28DeltaUdre(prn, losUnitEcef) : null;
|
|
2589
|
+
const dudre = mt28 != null ? mt28 : userLatRad != null && userLonRad != null ? this.deltaUdre(
|
|
2274
2590
|
userLatRad * 180 / Math.PI,
|
|
2275
2591
|
userLonRad * 180 / Math.PI
|
|
2276
2592
|
) : 1;
|
|
@@ -2399,14 +2715,14 @@ var SbasProcessor = class {
|
|
|
2399
2715
|
};
|
|
2400
2716
|
|
|
2401
2717
|
// src/positioning/ssr-apply.ts
|
|
2402
|
-
var
|
|
2718
|
+
var cross2 = (a, b) => [
|
|
2403
2719
|
a[1] * b[2] - a[2] * b[1],
|
|
2404
2720
|
a[2] * b[0] - a[0] * b[2],
|
|
2405
2721
|
a[0] * b[1] - a[1] * b[0]
|
|
2406
2722
|
];
|
|
2407
|
-
var
|
|
2723
|
+
var norm2 = (a) => Math.hypot(a[0], a[1], a[2]);
|
|
2408
2724
|
var unit = (a) => {
|
|
2409
|
-
const n =
|
|
2725
|
+
const n = norm2(a);
|
|
2410
2726
|
return n > 0 ? [a[0] / n, a[1] / n, a[2] / n] : [0, 0, 0];
|
|
2411
2727
|
};
|
|
2412
2728
|
function applyOrbitClock(eph, corr, timeMs) {
|
|
@@ -2420,8 +2736,8 @@ function applyOrbitClock(eph, corr, timeMs) {
|
|
|
2420
2736
|
const p = [s.x, s.y, s.z];
|
|
2421
2737
|
const v = [s.vx, s.vy, s.vz];
|
|
2422
2738
|
const er = unit(p);
|
|
2423
|
-
const en = unit(
|
|
2424
|
-
const ea =
|
|
2739
|
+
const en = unit(cross2(p, v));
|
|
2740
|
+
const ea = cross2(en, er);
|
|
2425
2741
|
const dr = corr.radial + (corr.dotRadial ?? 0) * age;
|
|
2426
2742
|
const da = corr.along + (corr.dotAlong ?? 0) * age;
|
|
2427
2743
|
const dc = corr.cross + (corr.dotCross ?? 0) * age;
|
|
@@ -2703,170 +3019,6 @@ function solidEarthTide(rcv, sun, moon) {
|
|
|
2703
3019
|
return disp;
|
|
2704
3020
|
}
|
|
2705
3021
|
|
|
2706
|
-
// src/positioning/ppp-antenna.ts
|
|
2707
|
-
function parseAntexTime(s) {
|
|
2708
|
-
const p = s.trim().split(/\s+/).map(Number);
|
|
2709
|
-
if (p.length < 3 || p.some((v) => Number.isNaN(v))) return null;
|
|
2710
|
-
return Date.UTC(
|
|
2711
|
-
p[0],
|
|
2712
|
-
(p[1] || 1) - 1,
|
|
2713
|
-
p[2] || 1,
|
|
2714
|
-
p[3] ?? 0,
|
|
2715
|
-
p[4] ?? 0,
|
|
2716
|
-
Math.floor(p[5] ?? 0)
|
|
2717
|
-
);
|
|
2718
|
-
}
|
|
2719
|
-
function freqOf(entry, code) {
|
|
2720
|
-
return entry.frequencies.find((f) => f.frequency === code);
|
|
2721
|
-
}
|
|
2722
|
-
function combineIf(fA, fB, f1, f2) {
|
|
2723
|
-
const g = f1 * f1 / (f1 * f1 - f2 * f2);
|
|
2724
|
-
const mm = 1e-3;
|
|
2725
|
-
const pco = [
|
|
2726
|
-
(g * fA.pcoN - (g - 1) * fB.pcoN) * mm,
|
|
2727
|
-
(g * fA.pcoE - (g - 1) * fB.pcoE) * mm,
|
|
2728
|
-
(g * fA.pcoU - (g - 1) * fB.pcoU) * mm
|
|
2729
|
-
];
|
|
2730
|
-
const nz = Math.min(fA.pcvNoazi.length, fB.pcvNoazi.length);
|
|
2731
|
-
const pcvNoazi = new Array(nz);
|
|
2732
|
-
for (let i = 0; i < nz; i++) {
|
|
2733
|
-
pcvNoazi[i] = (g * fA.pcvNoazi[i] - (g - 1) * fB.pcvNoazi[i]) * mm;
|
|
2734
|
-
}
|
|
2735
|
-
return { pco, pcvZen1Deg: 0, pcvDzenDeg: 0, pcvNoazi };
|
|
2736
|
-
}
|
|
2737
|
-
function buildPppAntenna(antex) {
|
|
2738
|
-
const sats = /* @__PURE__ */ new Map();
|
|
2739
|
-
const rcvs = /* @__PURE__ */ new Map();
|
|
2740
|
-
for (const a of antex.antennas) {
|
|
2741
|
-
if (a.isSatellite) {
|
|
2742
|
-
const list = sats.get(a.serialNo) ?? [];
|
|
2743
|
-
list.push(a);
|
|
2744
|
-
sats.set(a.serialNo, list);
|
|
2745
|
-
} else if (!rcvs.has(a.type)) {
|
|
2746
|
-
rcvs.set(a.type, a);
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
const withZen = (entry, off) => ({
|
|
2750
|
-
...off,
|
|
2751
|
-
pcvZen1Deg: entry.zen1,
|
|
2752
|
-
pcvDzenDeg: entry.dzen
|
|
2753
|
-
});
|
|
2754
|
-
return {
|
|
2755
|
-
satOffset(prn, dateMs, band1, band2, f1, f2) {
|
|
2756
|
-
const list = sats.get(prn);
|
|
2757
|
-
if (!list) return null;
|
|
2758
|
-
const entry = list.find((e) => {
|
|
2759
|
-
const from = parseAntexTime(e.validFrom);
|
|
2760
|
-
const until = parseAntexTime(e.validUntil);
|
|
2761
|
-
return (from == null || dateMs >= from) && (until == null || dateMs <= until);
|
|
2762
|
-
});
|
|
2763
|
-
if (!entry) return null;
|
|
2764
|
-
const a = freqOf(entry, band1);
|
|
2765
|
-
const b = freqOf(entry, band2);
|
|
2766
|
-
if (!a || !b) return null;
|
|
2767
|
-
return withZen(entry, combineIf(a, b, f1, f2));
|
|
2768
|
-
},
|
|
2769
|
-
rcvOffset(antType, band1, band2, f1, f2) {
|
|
2770
|
-
const entry = rcvs.get(antType);
|
|
2771
|
-
if (!entry) return null;
|
|
2772
|
-
const a = freqOf(entry, band1);
|
|
2773
|
-
const b = freqOf(entry, band2);
|
|
2774
|
-
if (!a || !b) return null;
|
|
2775
|
-
return withZen(entry, combineIf(a, b, f1, f2));
|
|
2776
|
-
}
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
function cross2(a, b) {
|
|
2780
|
-
return [
|
|
2781
|
-
a[1] * b[2] - a[2] * b[1],
|
|
2782
|
-
a[2] * b[0] - a[0] * b[2],
|
|
2783
|
-
a[0] * b[1] - a[1] * b[0]
|
|
2784
|
-
];
|
|
2785
|
-
}
|
|
2786
|
-
function norm2(a) {
|
|
2787
|
-
const n = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
2788
|
-
return [a[0] / n, a[1] / n, a[2] / n];
|
|
2789
|
-
}
|
|
2790
|
-
function dot(a, b) {
|
|
2791
|
-
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
2792
|
-
}
|
|
2793
|
-
function satBodyFrame(satEcef, sunEcef2) {
|
|
2794
|
-
const z = norm2([-satEcef[0], -satEcef[1], -satEcef[2]]);
|
|
2795
|
-
const eSun = norm2([
|
|
2796
|
-
sunEcef2[0] - satEcef[0],
|
|
2797
|
-
sunEcef2[1] - satEcef[1],
|
|
2798
|
-
sunEcef2[2] - satEcef[2]
|
|
2799
|
-
]);
|
|
2800
|
-
const y = norm2(cross2(z, eSun));
|
|
2801
|
-
const x = norm2(cross2(y, z));
|
|
2802
|
-
return { x, y, z };
|
|
2803
|
-
}
|
|
2804
|
-
function satPcoToEcef(pcoBody, frame) {
|
|
2805
|
-
return [
|
|
2806
|
-
pcoBody[0] * frame.x[0] + pcoBody[1] * frame.y[0] + pcoBody[2] * frame.z[0],
|
|
2807
|
-
pcoBody[0] * frame.x[1] + pcoBody[1] * frame.y[1] + pcoBody[2] * frame.z[1],
|
|
2808
|
-
pcoBody[0] * frame.x[2] + pcoBody[1] * frame.y[2] + pcoBody[2] * frame.z[2]
|
|
2809
|
-
];
|
|
2810
|
-
}
|
|
2811
|
-
function enuToEcef(enu, latRad, lonRad) {
|
|
2812
|
-
const sl = Math.sin(latRad);
|
|
2813
|
-
const cl = Math.cos(latRad);
|
|
2814
|
-
const so = Math.sin(lonRad);
|
|
2815
|
-
const co = Math.cos(lonRad);
|
|
2816
|
-
const [e, n, u] = enu;
|
|
2817
|
-
return [
|
|
2818
|
-
-so * e - sl * co * n + cl * co * u,
|
|
2819
|
-
co * e - sl * so * n + cl * so * u,
|
|
2820
|
-
cl * n + sl * u
|
|
2821
|
-
];
|
|
2822
|
-
}
|
|
2823
|
-
function interpPcv(off, angleDeg) {
|
|
2824
|
-
const { pcvZen1Deg, pcvDzenDeg, pcvNoazi } = off;
|
|
2825
|
-
if (pcvDzenDeg <= 0 || pcvNoazi.length < 2) return 0;
|
|
2826
|
-
const x = (angleDeg - pcvZen1Deg) / pcvDzenDeg;
|
|
2827
|
-
if (x <= 0) return pcvNoazi[0];
|
|
2828
|
-
if (x >= pcvNoazi.length - 1) return pcvNoazi[pcvNoazi.length - 1];
|
|
2829
|
-
const i = Math.floor(x);
|
|
2830
|
-
const t = x - i;
|
|
2831
|
-
return pcvNoazi[i] * (1 - t) + pcvNoazi[i + 1] * t;
|
|
2832
|
-
}
|
|
2833
|
-
function phaseWindup(prn, losRcvToSat, satFrame, latRad, lonRad, state) {
|
|
2834
|
-
const k = [
|
|
2835
|
-
-losRcvToSat[0],
|
|
2836
|
-
-losRcvToSat[1],
|
|
2837
|
-
-losRcvToSat[2]
|
|
2838
|
-
];
|
|
2839
|
-
const kx = satFrame.x;
|
|
2840
|
-
const ky = satFrame.y;
|
|
2841
|
-
const kdotx = dot(k, kx);
|
|
2842
|
-
const kcrossY = cross2(k, ky);
|
|
2843
|
-
const dS = [
|
|
2844
|
-
kx[0] - k[0] * kdotx - kcrossY[0],
|
|
2845
|
-
kx[1] - k[1] * kdotx - kcrossY[1],
|
|
2846
|
-
kx[2] - k[2] * kdotx - kcrossY[2]
|
|
2847
|
-
];
|
|
2848
|
-
const north = enuToEcef([0, 1, 0], latRad, lonRad);
|
|
2849
|
-
const east = enuToEcef([1, 0, 0], latRad, lonRad);
|
|
2850
|
-
const kdotn = dot(k, north);
|
|
2851
|
-
const kcrossE = cross2(k, east);
|
|
2852
|
-
const dR = [
|
|
2853
|
-
north[0] - k[0] * kdotn + kcrossE[0],
|
|
2854
|
-
north[1] - k[1] * kdotn + kcrossE[1],
|
|
2855
|
-
north[2] - k[2] * kdotn + kcrossE[2]
|
|
2856
|
-
];
|
|
2857
|
-
const nS = Math.hypot(dS[0], dS[1], dS[2]) || 1;
|
|
2858
|
-
const nR = Math.hypot(dR[0], dR[1], dR[2]) || 1;
|
|
2859
|
-
let cosPhi = dot(dS, dR) / (nS * nR);
|
|
2860
|
-
cosPhi = Math.max(-1, Math.min(1, cosPhi));
|
|
2861
|
-
const zeta = cross2(dS, dR);
|
|
2862
|
-
const sign = dot(k, zeta) >= 0 ? 1 : -1;
|
|
2863
|
-
const dphi = sign * Math.acos(cosPhi) / (2 * Math.PI);
|
|
2864
|
-
const prev = state.get(prn);
|
|
2865
|
-
const wu = prev == null ? dphi : dphi + Math.round(prev - dphi);
|
|
2866
|
-
state.set(prn, wu);
|
|
2867
|
-
return wu;
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
3022
|
// src/positioning/ppp-corrections.ts
|
|
2871
3023
|
function applyCorrections(corrections, geom, ctx) {
|
|
2872
3024
|
if (corrections.evaluate) return corrections.evaluate(geom, ctx);
|
|
@@ -4226,7 +4378,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
4226
4378
|
used.push(prn);
|
|
4227
4379
|
azels.push(azel);
|
|
4228
4380
|
if (sbas) {
|
|
4229
|
-
const
|
|
4381
|
+
const dx = sat.x - x, dy = sat.y - y, dz = sat.z - z;
|
|
4382
|
+
const dr = Math.hypot(dx, dy, dz) || 1;
|
|
4383
|
+
const los = [dx / dr, dy / dr, dz / dr];
|
|
4384
|
+
const sc = sbas.satCorrection(prn, gpsWeek, gpsTow, rxLat, rxLon, los);
|
|
4230
4385
|
const si = sbas.ionoDelay(
|
|
4231
4386
|
gpsWeek,
|
|
4232
4387
|
gpsTow,
|
|
@@ -4265,8 +4420,10 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
4265
4420
|
RtkFloatEngine,
|
|
4266
4421
|
SbasProcessor,
|
|
4267
4422
|
Sp3EphemerisSource,
|
|
4423
|
+
antexFreqOfGroup,
|
|
4268
4424
|
applyOrbitClock,
|
|
4269
4425
|
buildPppAntenna,
|
|
4426
|
+
buildRtkAntenna,
|
|
4270
4427
|
createPppCorrections,
|
|
4271
4428
|
estimateNarrowlaneFcb,
|
|
4272
4429
|
estimateNetworkFcbs,
|
|
@@ -4285,6 +4442,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
|
|
|
4285
4442
|
niellMapping,
|
|
4286
4443
|
nlWavelength,
|
|
4287
4444
|
postProcessRtk,
|
|
4445
|
+
rcvAntennaRangeM,
|
|
4288
4446
|
resolvePppAmbiguities,
|
|
4289
4447
|
satClockCorrection,
|
|
4290
4448
|
sbasIonoDeg,
|