gnss-js 1.5.0 → 1.6.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/README.md +46 -23
- package/dist/analysis.cjs +130 -28
- package/dist/analysis.d.cts +43 -3
- package/dist/analysis.d.ts +43 -3
- package/dist/analysis.js +5 -1
- package/dist/{chunk-PQYBTE42.js → chunk-CD5YSYCG.js} +11 -17
- package/dist/{chunk-65CQINSB.js → chunk-FGRC3PB3.js} +6 -2
- package/dist/{chunk-PX7TPPTQ.js → chunk-SOJNWF5T.js} +1 -1
- package/dist/{chunk-EJQ6NG5U.js → chunk-Z5UZUNCB.js} +126 -26
- package/dist/index.cjs +145 -45
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -4
- package/dist/orbit.cjs +11 -17
- package/dist/orbit.d.cts +1 -1
- package/dist/orbit.d.ts +1 -1
- package/dist/orbit.js +1 -1
- package/dist/positioning.cjs +2 -0
- package/dist/positioning.js +2 -2
- package/dist/rtcm3.cjs +6 -2
- package/dist/rtcm3.js +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -94,12 +94,16 @@ const itrf = transformFrame(
|
|
|
94
94
|
```ts
|
|
95
95
|
import { parseRinexStream, parseNavFile } from 'gnss-js/rinex';
|
|
96
96
|
|
|
97
|
-
// Streaming observation parser (
|
|
98
|
-
const result = await parseRinexStream(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
// Streaming observation parser (positional arguments)
|
|
98
|
+
const result = await parseRinexStream(
|
|
99
|
+
file, // File (plain, Hatanaka .crx, or .gz)
|
|
100
|
+
(percent) => {}, // optional progress callback
|
|
101
|
+
undefined, // optional AbortSignal
|
|
102
|
+
(time, prn, codes, values) => {
|
|
103
|
+
// per-satellite observation callback
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
// result.header: RinexHeader
|
|
103
107
|
|
|
104
108
|
// Navigation file parser
|
|
105
109
|
const nav = parseNavFile(navFileText);
|
|
@@ -125,28 +129,36 @@ for (const frame of decoder) {
|
|
|
125
129
|
import { computeAllPositions, computeDop } from 'gnss-js/orbit';
|
|
126
130
|
|
|
127
131
|
const positions = computeAllPositions(ephemerides, times, receiverPosition);
|
|
128
|
-
// positions.prns: string[], positions.times: number[]
|
|
132
|
+
// positions.prns: string[], positions.times: number[]
|
|
133
|
+
// positions.positions[prn][epochIdx]: { lat, lon, az, el } | null
|
|
129
134
|
```
|
|
130
135
|
|
|
131
136
|
### Signal analysis
|
|
132
137
|
|
|
133
138
|
```ts
|
|
134
|
-
import {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
import { analyzeQuality } from 'gnss-js/analysis';
|
|
140
|
+
|
|
141
|
+
// One re-parse pass: multipath (per code band, Anubis-style), cycle
|
|
142
|
+
// slips, completeness, and slant-TEC ionosphere series.
|
|
143
|
+
const q = await analyzeQuality(file, header);
|
|
144
|
+
// q.multipath, q.cycleSlips, q.completeness, q.iono
|
|
145
|
+
|
|
146
|
+
// The accumulators (MultipathAccumulator, CycleSlipAccumulator,
|
|
147
|
+
// CompletenessAccumulator, IonoAccumulator) are also exported for
|
|
148
|
+
// wiring into a custom parseRinexStream pass.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Ionosphere and DCBs
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { parseSinexBiasDcb, applyIonoDcb } from 'gnss-js/analysis';
|
|
155
|
+
|
|
156
|
+
// q.iono: slant TEC per satellite from the geometry-free phase
|
|
157
|
+
// levelled to the code — DCB-biased until calibrated:
|
|
158
|
+
const satDcb = parseSinexBiasDcb(sinexBiasText, obsEpochMs); // ESA .BIA / CAS .BSX
|
|
159
|
+
const { result, receiverDcbTecu } = applyIonoDcb(q.iono, satDcb);
|
|
160
|
+
// result.series[n].points: calibrated STEC (TECU); receiver bias
|
|
161
|
+
// estimated from the night-time floor per system/signal pair.
|
|
150
162
|
```
|
|
151
163
|
|
|
152
164
|
### NTRIP client
|
|
@@ -173,6 +185,17 @@ const { reader, abort } = await connectToMountpoint(
|
|
|
173
185
|
);
|
|
174
186
|
```
|
|
175
187
|
|
|
188
|
+
### NMEA and ANTEX
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { parseNmeaFile, computeStats } from 'gnss-js/nmea';
|
|
192
|
+
import { parseAntex } from 'gnss-js/antex';
|
|
193
|
+
|
|
194
|
+
const track = parseNmeaFile(nmeaText); // fixes, satellites, per-sentence records
|
|
195
|
+
const stats = computeStats(track.fixes); // position/HDOP/speed statistics
|
|
196
|
+
const antex = parseAntex(antexText); // antenna PCO/PCV calibrations
|
|
197
|
+
```
|
|
198
|
+
|
|
176
199
|
### Constants
|
|
177
200
|
|
|
178
201
|
```ts
|
package/dist/analysis.cjs
CHANGED
|
@@ -26,10 +26,24 @@ __export(analysis_exports, {
|
|
|
26
26
|
MultipathAccumulator: () => MultipathAccumulator,
|
|
27
27
|
analyzeQuality: () => analyzeQuality,
|
|
28
28
|
applyIonoDcb: () => applyIonoDcb,
|
|
29
|
+
computeIonoRate: () => computeIonoRate,
|
|
30
|
+
detrendIonoArcs: () => detrendIonoArcs,
|
|
29
31
|
parseSinexBiasDcb: () => parseSinexBiasDcb
|
|
30
32
|
});
|
|
31
33
|
module.exports = __toCommonJS(analysis_exports);
|
|
32
34
|
|
|
35
|
+
// src/analysis/stats-util.ts
|
|
36
|
+
var MIN_ARC_LENGTH = 10;
|
|
37
|
+
function median(values) {
|
|
38
|
+
const s = [...values].sort((a, b) => a - b);
|
|
39
|
+
const m = s.length >> 1;
|
|
40
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
41
|
+
}
|
|
42
|
+
function percentile(values, p) {
|
|
43
|
+
const s = [...values].sort((a, b) => a - b);
|
|
44
|
+
return s[Math.min(s.length - 1, Math.floor(p * s.length))] ?? 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
// src/constants/gnss.ts
|
|
34
48
|
var SYSTEM_NAMES = {
|
|
35
49
|
G: "GPS",
|
|
@@ -217,15 +231,9 @@ function buildObsIndices(header) {
|
|
|
217
231
|
var ARC_GAP_FACTOR = 5;
|
|
218
232
|
|
|
219
233
|
// src/analysis/multipath.ts
|
|
220
|
-
var MIN_ARC_LENGTH = 10;
|
|
221
234
|
var MP_JUMP_M = 1.25;
|
|
222
235
|
var MP_JUMP_WINDOW = 5;
|
|
223
236
|
var MP_EDIT_SIGMA = 3;
|
|
224
|
-
function median(values) {
|
|
225
|
-
const s = [...values].sort((a, b) => a - b);
|
|
226
|
-
const m = s.length >> 1;
|
|
227
|
-
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
228
|
-
}
|
|
229
237
|
var MultipathAccumulator = class {
|
|
230
238
|
state = /* @__PURE__ */ new Map();
|
|
231
239
|
results = [];
|
|
@@ -706,15 +714,10 @@ var CompletenessAccumulator = class {
|
|
|
706
714
|
// src/analysis/ionosphere.ts
|
|
707
715
|
var TEC_FACTOR = 403e15;
|
|
708
716
|
var GF_JUMP_M = 0.15;
|
|
709
|
-
var MIN_ARC_LENGTH2 = 10;
|
|
710
|
-
function median2(values) {
|
|
711
|
-
const s = [...values].sort((a, b) => a - b);
|
|
712
|
-
const m = s.length >> 1;
|
|
713
|
-
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
714
|
-
}
|
|
715
717
|
var IonoAccumulator = class {
|
|
716
718
|
state = /* @__PURE__ */ new Map();
|
|
717
719
|
closed = /* @__PURE__ */ new Map();
|
|
720
|
+
closedArcStarts = /* @__PURE__ */ new Map();
|
|
718
721
|
interval;
|
|
719
722
|
obsIndices;
|
|
720
723
|
gloChannels;
|
|
@@ -814,8 +817,8 @@ var IonoAccumulator = class {
|
|
|
814
817
|
}
|
|
815
818
|
closeArc(prn, pairKey, ps) {
|
|
816
819
|
const arc = ps.arc;
|
|
817
|
-
if (arc.times.length >=
|
|
818
|
-
const level =
|
|
820
|
+
if (arc.times.length >= MIN_ARC_LENGTH) {
|
|
821
|
+
const level = median(arc.l4.map((v, k) => v - arc.p4[k]));
|
|
819
822
|
const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
|
|
820
823
|
let satArcs = this.closed.get(prn);
|
|
821
824
|
if (!satArcs) {
|
|
@@ -827,6 +830,17 @@ var IonoAccumulator = class {
|
|
|
827
830
|
points = [];
|
|
828
831
|
satArcs.set(pairKey, points);
|
|
829
832
|
}
|
|
833
|
+
let satStarts = this.closedArcStarts.get(prn);
|
|
834
|
+
if (!satStarts) {
|
|
835
|
+
satStarts = /* @__PURE__ */ new Map();
|
|
836
|
+
this.closedArcStarts.set(prn, satStarts);
|
|
837
|
+
}
|
|
838
|
+
let starts = satStarts.get(pairKey);
|
|
839
|
+
if (!starts) {
|
|
840
|
+
starts = [];
|
|
841
|
+
satStarts.set(pairKey, starts);
|
|
842
|
+
}
|
|
843
|
+
starts.push(points.length);
|
|
830
844
|
for (let k = 0; k < arc.times.length; k++) {
|
|
831
845
|
points.push({
|
|
832
846
|
time: arc.times[k],
|
|
@@ -846,7 +860,7 @@ var IonoAccumulator = class {
|
|
|
846
860
|
const series = [];
|
|
847
861
|
let sum = 0;
|
|
848
862
|
let count = 0;
|
|
849
|
-
let maxStec =
|
|
863
|
+
let maxStec = -Infinity;
|
|
850
864
|
for (const [prn, satArcs] of this.closed) {
|
|
851
865
|
let bestKey = null;
|
|
852
866
|
let bestLen = 0;
|
|
@@ -858,7 +872,7 @@ var IonoAccumulator = class {
|
|
|
858
872
|
}
|
|
859
873
|
if (!bestKey) continue;
|
|
860
874
|
const points = satArcs.get(bestKey);
|
|
861
|
-
|
|
875
|
+
const arcStarts = this.closedArcStarts.get(prn)?.get(bestKey) ?? [0];
|
|
862
876
|
const sys = prn[0];
|
|
863
877
|
for (const p of points) {
|
|
864
878
|
sum += p.stec;
|
|
@@ -873,21 +887,107 @@ var IonoAccumulator = class {
|
|
|
873
887
|
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
874
888
|
codes: this.pairCodes.get(`${sys}:${bestKey}`) ?? ["", ""],
|
|
875
889
|
tecuPerNs,
|
|
876
|
-
points
|
|
890
|
+
points,
|
|
891
|
+
arcStarts
|
|
877
892
|
});
|
|
878
893
|
}
|
|
879
894
|
series.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
880
|
-
return {
|
|
895
|
+
return {
|
|
896
|
+
series,
|
|
897
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
898
|
+
meanStec: count > 0 ? sum / count : 0
|
|
899
|
+
};
|
|
881
900
|
}
|
|
882
901
|
};
|
|
902
|
+
function computeIonoRate(result, intervalSec, order = 1) {
|
|
903
|
+
const out = [];
|
|
904
|
+
for (const s of result.series) {
|
|
905
|
+
const pts = [];
|
|
906
|
+
const bounds = [...s.arcStarts, s.points.length];
|
|
907
|
+
for (let a = 0; a < bounds.length - 1; a++) {
|
|
908
|
+
const lo = bounds[a];
|
|
909
|
+
const hi = bounds[a + 1];
|
|
910
|
+
if (order === 2) {
|
|
911
|
+
for (let i = lo + 2; i < hi; i++) {
|
|
912
|
+
const dt1 = s.points[i - 1].time - s.points[i - 2].time;
|
|
913
|
+
const dt2 = s.points[i].time - s.points[i - 1].time;
|
|
914
|
+
if (Math.abs(dt1 - dt2) > 0.1 * Math.max(dt1, dt2)) continue;
|
|
915
|
+
pts.push({
|
|
916
|
+
time: s.points[i].time,
|
|
917
|
+
value: s.points[i].stec - 2 * s.points[i - 1].stec + s.points[i - 2].stec
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (intervalSec === void 0) {
|
|
923
|
+
for (let i = lo + 1; i < hi; i++) {
|
|
924
|
+
const dtMin = (s.points[i].time - s.points[i - 1].time) / 6e4;
|
|
925
|
+
if (dtMin <= 0) continue;
|
|
926
|
+
pts.push({
|
|
927
|
+
time: s.points[i].time,
|
|
928
|
+
value: (s.points[i].stec - s.points[i - 1].stec) / dtMin
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
} else {
|
|
932
|
+
const targetMs = intervalSec * 1e3;
|
|
933
|
+
let j = lo;
|
|
934
|
+
for (let i = lo; i < hi; i++) {
|
|
935
|
+
if (j <= i) j = i + 1;
|
|
936
|
+
while (j < hi && s.points[j].time - s.points[i].time < targetMs)
|
|
937
|
+
j++;
|
|
938
|
+
if (j >= hi) break;
|
|
939
|
+
const dtMs = s.points[j].time - s.points[i].time;
|
|
940
|
+
if (dtMs > 1.5 * targetMs) continue;
|
|
941
|
+
pts.push({
|
|
942
|
+
time: s.points[j].time,
|
|
943
|
+
value: (s.points[j].stec - s.points[i].stec) / dtMs * 6e4
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (pts.length > 0) out.push({ prn: s.prn, system: s.system, points: pts });
|
|
949
|
+
}
|
|
950
|
+
return out;
|
|
951
|
+
}
|
|
952
|
+
function detrendIonoArcs(result) {
|
|
953
|
+
let sum = 0;
|
|
954
|
+
let count = 0;
|
|
955
|
+
let maxStec = -Infinity;
|
|
956
|
+
const series = result.series.map((s) => {
|
|
957
|
+
const bounds = [...s.arcStarts, s.points.length];
|
|
958
|
+
const points = new Array(s.points.length);
|
|
959
|
+
for (let a = 0; a < bounds.length - 1; a++) {
|
|
960
|
+
const lo = bounds[a];
|
|
961
|
+
const hi = bounds[a + 1];
|
|
962
|
+
const anchor = s.points[lo]?.stec ?? 0;
|
|
963
|
+
for (let i = lo; i < hi; i++) {
|
|
964
|
+
points[i] = {
|
|
965
|
+
time: s.points[i].time,
|
|
966
|
+
stec: s.points[i].stec - anchor
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
for (const p of points) {
|
|
971
|
+
sum += p.stec;
|
|
972
|
+
count++;
|
|
973
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
974
|
+
}
|
|
975
|
+
return { ...s, points };
|
|
976
|
+
});
|
|
977
|
+
return {
|
|
978
|
+
series,
|
|
979
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
980
|
+
meanStec: count > 0 ? sum / count : 0
|
|
981
|
+
};
|
|
982
|
+
}
|
|
883
983
|
|
|
884
984
|
// src/analysis/dcb.ts
|
|
885
985
|
var PRN_RE = /^[A-Z]\d{2}$/;
|
|
886
986
|
var SVN_RE = /^[A-Z]\d{3}$/;
|
|
887
987
|
var OBS_RE = /^[CL]\d[A-Z]$/;
|
|
888
|
-
function sinexEpochMs(s) {
|
|
988
|
+
function sinexEpochMs(s, openValue) {
|
|
889
989
|
const [y, d, sec] = s.split(":").map(Number);
|
|
890
|
-
if (!y) return
|
|
990
|
+
if (!y) return openValue;
|
|
891
991
|
return Date.UTC(y, 0, 1) + ((d ?? 1) - 1) * 864e5 + (sec ?? 0) * 1e3;
|
|
892
992
|
}
|
|
893
993
|
function parseSinexBiasDcb(text, epochMs) {
|
|
@@ -902,8 +1002,8 @@ function parseSinexBiasDcb(text, epochMs) {
|
|
|
902
1002
|
if (unitIdx < 0 || unitIdx + 1 >= t.length) continue;
|
|
903
1003
|
const value = parseFloat(t[unitIdx + 1]);
|
|
904
1004
|
if (!isFinite(value)) continue;
|
|
905
|
-
const start = sinexEpochMs(t[5] ?? "");
|
|
906
|
-
const end = sinexEpochMs(t[6] ?? "");
|
|
1005
|
+
const start = sinexEpochMs(t[5] ?? "", -Infinity);
|
|
1006
|
+
const end = sinexEpochMs(t[6] ?? "", Infinity);
|
|
907
1007
|
const covers = epochMs !== void 0 && epochMs >= start && epochMs < end;
|
|
908
1008
|
const prn = t[2];
|
|
909
1009
|
const pair = `${t[3]}-${t[4]}`;
|
|
@@ -925,10 +1025,6 @@ function parseSinexBiasDcb(text, epochMs) {
|
|
|
925
1025
|
}
|
|
926
1026
|
return out;
|
|
927
1027
|
}
|
|
928
|
-
function percentile(values, p) {
|
|
929
|
-
const s = [...values].sort((a, b) => a - b);
|
|
930
|
-
return s[Math.min(s.length - 1, Math.floor(p * s.length))] ?? 0;
|
|
931
|
-
}
|
|
932
1028
|
function applyIonoDcb(iono, satDcb) {
|
|
933
1029
|
const missing = [];
|
|
934
1030
|
let corrected = 0;
|
|
@@ -992,7 +1088,7 @@ function applyIonoDcb(iono, satDcb) {
|
|
|
992
1088
|
out.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
993
1089
|
let sum = 0;
|
|
994
1090
|
let count = 0;
|
|
995
|
-
let maxStec =
|
|
1091
|
+
let maxStec = -Infinity;
|
|
996
1092
|
for (const s of out) {
|
|
997
1093
|
for (const p of s.points) {
|
|
998
1094
|
sum += p.stec;
|
|
@@ -1001,7 +1097,11 @@ function applyIonoDcb(iono, satDcb) {
|
|
|
1001
1097
|
}
|
|
1002
1098
|
}
|
|
1003
1099
|
return {
|
|
1004
|
-
result: {
|
|
1100
|
+
result: {
|
|
1101
|
+
series: out,
|
|
1102
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
1103
|
+
meanStec: count > 0 ? sum / count : 0
|
|
1104
|
+
},
|
|
1005
1105
|
satellitesCorrected: corrected,
|
|
1006
1106
|
satellitesMissing: [...new Set(missing)].sort(),
|
|
1007
1107
|
receiverDcbTecu
|
|
@@ -1831,5 +1931,7 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
1831
1931
|
MultipathAccumulator,
|
|
1832
1932
|
analyzeQuality,
|
|
1833
1933
|
applyIonoDcb,
|
|
1934
|
+
computeIonoRate,
|
|
1935
|
+
detrendIonoArcs,
|
|
1834
1936
|
parseSinexBiasDcb
|
|
1835
1937
|
});
|
package/dist/analysis.d.cts
CHANGED
|
@@ -26,7 +26,7 @@ interface MultipathSeries {
|
|
|
26
26
|
band: string;
|
|
27
27
|
/** Reference band digit used in dual-frequency combination, e.g. "2". */
|
|
28
28
|
refBand: string;
|
|
29
|
-
/** Human-readable label, e.g. "
|
|
29
|
+
/** Human-readable label, e.g. "G01 MP L1-L2". */
|
|
30
30
|
label: string;
|
|
31
31
|
/** Time series of multipath values. */
|
|
32
32
|
points: MultipathPoint[];
|
|
@@ -34,7 +34,7 @@ interface MultipathSeries {
|
|
|
34
34
|
rms: number;
|
|
35
35
|
}
|
|
36
36
|
interface MultipathSignalStat {
|
|
37
|
-
/** Human-readable label, e.g. "
|
|
37
|
+
/** Human-readable label, e.g. "MP L1-L2 (GPS)". */
|
|
38
38
|
label: string;
|
|
39
39
|
/** System letter, e.g. "G". */
|
|
40
40
|
system: string;
|
|
@@ -239,6 +239,12 @@ interface IonoSeries {
|
|
|
239
239
|
tecuPerNs: number;
|
|
240
240
|
/** Time series of slant TEC values (TECU, DCB-biased). */
|
|
241
241
|
points: IonoPoint[];
|
|
242
|
+
/**
|
|
243
|
+
* Indices into `points` where a new continuous arc begins (gap,
|
|
244
|
+
* cycle slip, or geometry-free jump). Differencing and per-arc
|
|
245
|
+
* detrending must not cross these boundaries.
|
|
246
|
+
*/
|
|
247
|
+
arcStarts: number[];
|
|
242
248
|
}
|
|
243
249
|
interface IonoResult {
|
|
244
250
|
/** Per-satellite slant TEC time series. */
|
|
@@ -251,6 +257,7 @@ interface IonoResult {
|
|
|
251
257
|
declare class IonoAccumulator {
|
|
252
258
|
private state;
|
|
253
259
|
private closed;
|
|
260
|
+
private closedArcStarts;
|
|
254
261
|
private interval;
|
|
255
262
|
private obsIndices;
|
|
256
263
|
private gloChannels;
|
|
@@ -267,6 +274,39 @@ declare class IonoAccumulator {
|
|
|
267
274
|
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
268
275
|
finalize(): IonoResult;
|
|
269
276
|
}
|
|
277
|
+
interface IonoRatePoint {
|
|
278
|
+
/** Epoch time (ms) — the later epoch of the differenced pair. */
|
|
279
|
+
time: number;
|
|
280
|
+
/** ROT in TECU/min (order 1) or undivided ΔΔSTEC in TECU (order 2). */
|
|
281
|
+
value: number;
|
|
282
|
+
}
|
|
283
|
+
interface IonoRateSeries {
|
|
284
|
+
prn: string;
|
|
285
|
+
system: string;
|
|
286
|
+
points: IonoRatePoint[];
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Sequential time differences of the slant TEC series — the biases
|
|
290
|
+
* (ambiguities, DCBs) cancel, leaving ionospheric rate of TEC, phase
|
|
291
|
+
* noise, and scintillation. Differences never cross arc boundaries,
|
|
292
|
+
* so cycle slips do not appear as outliers (arcs split there).
|
|
293
|
+
*
|
|
294
|
+
* @param intervalSec Standardized differencing baseline in seconds.
|
|
295
|
+
* Omit for native-rate sequential differences; set e.g. 60 for a
|
|
296
|
+
* sample-rate-independent picture (all pairs ~1 min apart are used).
|
|
297
|
+
* @param order 1 (default) = first difference in TECU/min;
|
|
298
|
+
* 2 = second undivided difference in TECU (native rate only,
|
|
299
|
+
* gradients removed, noise and scintillation amplified).
|
|
300
|
+
*/
|
|
301
|
+
declare function computeIonoRate(result: IonoResult, intervalSec?: number, order?: 1 | 2): IonoRateSeries[];
|
|
302
|
+
/**
|
|
303
|
+
* Remove the per-arc bias from a slant TEC result — each arc is
|
|
304
|
+
* shifted so its first observation reads zero, leaving only the TEC
|
|
305
|
+
* *variation* along the arc (Hans van der Marel's suggestion; using
|
|
306
|
+
* the first observation keeps the shape untouched at the small risk
|
|
307
|
+
* of anchoring on an outlier).
|
|
308
|
+
*/
|
|
309
|
+
declare function detrendIonoArcs(result: IonoResult): IonoResult;
|
|
270
310
|
|
|
271
311
|
/**
|
|
272
312
|
* Differential code bias (DCB) products and their application to the
|
|
@@ -323,4 +363,4 @@ interface QualityResult {
|
|
|
323
363
|
}
|
|
324
364
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
325
365
|
|
|
326
|
-
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, IonoAccumulator, type IonoDcbResult, type IonoPoint, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, type SatDcbMap, analyzeQuality, applyIonoDcb, parseSinexBiasDcb };
|
|
366
|
+
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, IonoAccumulator, type IonoDcbResult, type IonoPoint, type IonoRatePoint, type IonoRateSeries, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, type SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb };
|
package/dist/analysis.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface MultipathSeries {
|
|
|
26
26
|
band: string;
|
|
27
27
|
/** Reference band digit used in dual-frequency combination, e.g. "2". */
|
|
28
28
|
refBand: string;
|
|
29
|
-
/** Human-readable label, e.g. "
|
|
29
|
+
/** Human-readable label, e.g. "G01 MP L1-L2". */
|
|
30
30
|
label: string;
|
|
31
31
|
/** Time series of multipath values. */
|
|
32
32
|
points: MultipathPoint[];
|
|
@@ -34,7 +34,7 @@ interface MultipathSeries {
|
|
|
34
34
|
rms: number;
|
|
35
35
|
}
|
|
36
36
|
interface MultipathSignalStat {
|
|
37
|
-
/** Human-readable label, e.g. "
|
|
37
|
+
/** Human-readable label, e.g. "MP L1-L2 (GPS)". */
|
|
38
38
|
label: string;
|
|
39
39
|
/** System letter, e.g. "G". */
|
|
40
40
|
system: string;
|
|
@@ -239,6 +239,12 @@ interface IonoSeries {
|
|
|
239
239
|
tecuPerNs: number;
|
|
240
240
|
/** Time series of slant TEC values (TECU, DCB-biased). */
|
|
241
241
|
points: IonoPoint[];
|
|
242
|
+
/**
|
|
243
|
+
* Indices into `points` where a new continuous arc begins (gap,
|
|
244
|
+
* cycle slip, or geometry-free jump). Differencing and per-arc
|
|
245
|
+
* detrending must not cross these boundaries.
|
|
246
|
+
*/
|
|
247
|
+
arcStarts: number[];
|
|
242
248
|
}
|
|
243
249
|
interface IonoResult {
|
|
244
250
|
/** Per-satellite slant TEC time series. */
|
|
@@ -251,6 +257,7 @@ interface IonoResult {
|
|
|
251
257
|
declare class IonoAccumulator {
|
|
252
258
|
private state;
|
|
253
259
|
private closed;
|
|
260
|
+
private closedArcStarts;
|
|
254
261
|
private interval;
|
|
255
262
|
private obsIndices;
|
|
256
263
|
private gloChannels;
|
|
@@ -267,6 +274,39 @@ declare class IonoAccumulator {
|
|
|
267
274
|
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
268
275
|
finalize(): IonoResult;
|
|
269
276
|
}
|
|
277
|
+
interface IonoRatePoint {
|
|
278
|
+
/** Epoch time (ms) — the later epoch of the differenced pair. */
|
|
279
|
+
time: number;
|
|
280
|
+
/** ROT in TECU/min (order 1) or undivided ΔΔSTEC in TECU (order 2). */
|
|
281
|
+
value: number;
|
|
282
|
+
}
|
|
283
|
+
interface IonoRateSeries {
|
|
284
|
+
prn: string;
|
|
285
|
+
system: string;
|
|
286
|
+
points: IonoRatePoint[];
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Sequential time differences of the slant TEC series — the biases
|
|
290
|
+
* (ambiguities, DCBs) cancel, leaving ionospheric rate of TEC, phase
|
|
291
|
+
* noise, and scintillation. Differences never cross arc boundaries,
|
|
292
|
+
* so cycle slips do not appear as outliers (arcs split there).
|
|
293
|
+
*
|
|
294
|
+
* @param intervalSec Standardized differencing baseline in seconds.
|
|
295
|
+
* Omit for native-rate sequential differences; set e.g. 60 for a
|
|
296
|
+
* sample-rate-independent picture (all pairs ~1 min apart are used).
|
|
297
|
+
* @param order 1 (default) = first difference in TECU/min;
|
|
298
|
+
* 2 = second undivided difference in TECU (native rate only,
|
|
299
|
+
* gradients removed, noise and scintillation amplified).
|
|
300
|
+
*/
|
|
301
|
+
declare function computeIonoRate(result: IonoResult, intervalSec?: number, order?: 1 | 2): IonoRateSeries[];
|
|
302
|
+
/**
|
|
303
|
+
* Remove the per-arc bias from a slant TEC result — each arc is
|
|
304
|
+
* shifted so its first observation reads zero, leaving only the TEC
|
|
305
|
+
* *variation* along the arc (Hans van der Marel's suggestion; using
|
|
306
|
+
* the first observation keeps the shape untouched at the small risk
|
|
307
|
+
* of anchoring on an outlier).
|
|
308
|
+
*/
|
|
309
|
+
declare function detrendIonoArcs(result: IonoResult): IonoResult;
|
|
270
310
|
|
|
271
311
|
/**
|
|
272
312
|
* Differential code bias (DCB) products and their application to the
|
|
@@ -323,4 +363,4 @@ interface QualityResult {
|
|
|
323
363
|
}
|
|
324
364
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
325
365
|
|
|
326
|
-
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, IonoAccumulator, type IonoDcbResult, type IonoPoint, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, type SatDcbMap, analyzeQuality, applyIonoDcb, parseSinexBiasDcb };
|
|
366
|
+
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, IonoAccumulator, type IonoDcbResult, type IonoPoint, type IonoRatePoint, type IonoRateSeries, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, type SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb };
|
package/dist/analysis.js
CHANGED
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
MultipathAccumulator,
|
|
6
6
|
analyzeQuality,
|
|
7
7
|
applyIonoDcb,
|
|
8
|
+
computeIonoRate,
|
|
9
|
+
detrendIonoArcs,
|
|
8
10
|
parseSinexBiasDcb
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Z5UZUNCB.js";
|
|
10
12
|
import "./chunk-3U5AX7PY.js";
|
|
11
13
|
import "./chunk-FIEWO4J4.js";
|
|
12
14
|
export {
|
|
@@ -16,5 +18,7 @@ export {
|
|
|
16
18
|
MultipathAccumulator,
|
|
17
19
|
analyzeQuality,
|
|
18
20
|
applyIonoDcb,
|
|
21
|
+
computeIonoRate,
|
|
22
|
+
detrendIonoArcs,
|
|
19
23
|
parseSinexBiasDcb
|
|
20
24
|
};
|
|
@@ -219,20 +219,10 @@ function invert4x4(m) {
|
|
|
219
219
|
return result;
|
|
220
220
|
}
|
|
221
221
|
function selectEphemeris(ephemerides, prn, timeMs) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const ephTime = eph.tocDate.getTime();
|
|
227
|
-
const dt = Math.abs(timeMs - ephTime);
|
|
228
|
-
const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
|
|
229
|
-
if (dt > maxAge) continue;
|
|
230
|
-
if (dt < bestDt) {
|
|
231
|
-
bestDt = dt;
|
|
232
|
-
best = eph;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return best;
|
|
222
|
+
return selectBest(
|
|
223
|
+
ephemerides.filter((e) => e.prn === prn),
|
|
224
|
+
timeMs
|
|
225
|
+
);
|
|
236
226
|
}
|
|
237
227
|
function computeSatPosition(eph, timeMs) {
|
|
238
228
|
if (eph.system === "R" || eph.system === "S") {
|
|
@@ -325,8 +315,9 @@ function selectBest(ephs, timeMs) {
|
|
|
325
315
|
return best;
|
|
326
316
|
}
|
|
327
317
|
var GPS_EPOCH_MS = START_GPS_TIME.getTime();
|
|
318
|
+
var MS_PER_WEEK = 7 * 864e5;
|
|
328
319
|
var BDS_EPOCH_MS = START_BDS_TIME.getTime();
|
|
329
|
-
var GAL_EPOCH_MS = GPS_EPOCH_MS;
|
|
320
|
+
var GAL_EPOCH_MS = GPS_EPOCH_MS + 1024 * MS_PER_WEEK;
|
|
330
321
|
function ephInfoToEphemeris(info) {
|
|
331
322
|
const sys = info.prn.charAt(0);
|
|
332
323
|
if (sys === "R") {
|
|
@@ -370,6 +361,7 @@ function ephInfoToEphemeris(info) {
|
|
|
370
361
|
return null;
|
|
371
362
|
}
|
|
372
363
|
let epochMs;
|
|
364
|
+
let week = info.week;
|
|
373
365
|
const tocSec = info.toc ?? info.toe;
|
|
374
366
|
if (sys === "C") {
|
|
375
367
|
epochMs = BDS_EPOCH_MS;
|
|
@@ -377,8 +369,10 @@ function ephInfoToEphemeris(info) {
|
|
|
377
369
|
epochMs = GAL_EPOCH_MS;
|
|
378
370
|
} else {
|
|
379
371
|
epochMs = GPS_EPOCH_MS;
|
|
372
|
+
const weeksNow = (info.lastReceived - GPS_EPOCH_MS) / MS_PER_WEEK;
|
|
373
|
+
week += 1024 * Math.round((weeksNow - week) / 1024);
|
|
380
374
|
}
|
|
381
|
-
const tocDate = new Date(epochMs +
|
|
375
|
+
const tocDate = new Date(epochMs + week * MS_PER_WEEK + tocSec * 1e3);
|
|
382
376
|
return {
|
|
383
377
|
system: sys,
|
|
384
378
|
prn: info.prn,
|
|
@@ -404,7 +398,7 @@ function ephInfoToEphemeris(info) {
|
|
|
404
398
|
omega: info.argPerigee,
|
|
405
399
|
omegaDot: info.omegaDot ?? 0,
|
|
406
400
|
idot: info.idot ?? 0,
|
|
407
|
-
week
|
|
401
|
+
week,
|
|
408
402
|
svHealth: info.health,
|
|
409
403
|
tgd: 0
|
|
410
404
|
};
|
|
@@ -1345,6 +1345,11 @@ function rtcm3Constellation(msgType) {
|
|
|
1345
1345
|
if (msgType >= 1111 && msgType <= 1117) return "QZSS";
|
|
1346
1346
|
if (msgType >= 1121 && msgType <= 1127) return "BeiDou";
|
|
1347
1347
|
if (msgType >= 1131 && msgType <= 1137) return "NavIC";
|
|
1348
|
+
if (msgType >= 1015 && msgType <= 1017) return "GPS";
|
|
1349
|
+
if (msgType === 1030) return "GPS";
|
|
1350
|
+
if (msgType === 1031) return "GLONASS";
|
|
1351
|
+
if (msgType >= 1057 && msgType <= 1062) return "GPS";
|
|
1352
|
+
if (msgType >= 1063 && msgType <= 1068) return "GLONASS";
|
|
1348
1353
|
if (msgType === 1230) return "GLONASS";
|
|
1349
1354
|
return null;
|
|
1350
1355
|
}
|
|
@@ -1384,7 +1389,6 @@ function updateStreamStats(stats, frames, rawBytes) {
|
|
|
1384
1389
|
entry.totalBytes += frame.length + 6;
|
|
1385
1390
|
const msmEpoch = decodeMsmFull(frame);
|
|
1386
1391
|
if (msmEpoch) {
|
|
1387
|
-
const now2 = Date.now();
|
|
1388
1392
|
for (const obs of msmEpoch.observations) {
|
|
1389
1393
|
const signals = [];
|
|
1390
1394
|
let bestCn0 = 0;
|
|
@@ -1399,7 +1403,7 @@ function updateStreamStats(stats, frames, rawBytes) {
|
|
|
1399
1403
|
prn: obs.prn,
|
|
1400
1404
|
system: obs.system,
|
|
1401
1405
|
cn0: bestCn0,
|
|
1402
|
-
lastSeen:
|
|
1406
|
+
lastSeen: now,
|
|
1403
1407
|
signals
|
|
1404
1408
|
});
|
|
1405
1409
|
}
|