gnss-js 1.4.0 → 1.5.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/analysis.cjs +161 -10
- package/dist/analysis.d.cts +52 -2
- package/dist/analysis.d.ts +52 -2
- package/dist/analysis.js +7 -3
- package/dist/{chunk-3ZAFWOT6.js → chunk-EJQ6NG5U.js} +157 -8
- package/dist/index.cjs +159 -8
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -2
- package/package.json +1 -1
package/dist/analysis.cjs
CHANGED
|
@@ -24,7 +24,9 @@ __export(analysis_exports, {
|
|
|
24
24
|
CycleSlipAccumulator: () => CycleSlipAccumulator,
|
|
25
25
|
IonoAccumulator: () => IonoAccumulator,
|
|
26
26
|
MultipathAccumulator: () => MultipathAccumulator,
|
|
27
|
-
analyzeQuality: () => analyzeQuality
|
|
27
|
+
analyzeQuality: () => analyzeQuality,
|
|
28
|
+
applyIonoDcb: () => applyIonoDcb,
|
|
29
|
+
parseSinexBiasDcb: () => parseSinexBiasDcb
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(analysis_exports);
|
|
30
32
|
|
|
@@ -251,17 +253,24 @@ var MultipathAccumulator = class {
|
|
|
251
253
|
}
|
|
252
254
|
}
|
|
253
255
|
if (bandData.size < 2) return;
|
|
256
|
+
const mpOf = (a, b) => {
|
|
257
|
+
const \u03BBa = C_LIGHT / a.f;
|
|
258
|
+
const \u03BBb = C_LIGHT / b.f;
|
|
259
|
+
const \u03B1 = a.f * a.f / (b.f * b.f);
|
|
260
|
+
const coeff = 2 / (\u03B1 - 1);
|
|
261
|
+
return a.C - (1 + coeff) * a.L * \u03BBa + coeff * b.L * \u03BBb;
|
|
262
|
+
};
|
|
254
263
|
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
264
|
+
let primaryDone = false;
|
|
255
265
|
for (const [bi, bj] of pairs) {
|
|
256
266
|
const di = bandData.get(bi);
|
|
257
267
|
const dj = bandData.get(bj);
|
|
258
268
|
if (!di || !dj) continue;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.pushMp(prn, bi, bj, time, mp);
|
|
269
|
+
if (!primaryDone) {
|
|
270
|
+
this.pushMp(prn, bi, bj, time, mpOf(di, dj));
|
|
271
|
+
primaryDone = true;
|
|
272
|
+
}
|
|
273
|
+
this.pushMp(prn, bj, bi, time, mpOf(dj, di));
|
|
265
274
|
}
|
|
266
275
|
};
|
|
267
276
|
pushMp(prn, band, refBand, time, mp) {
|
|
@@ -710,13 +719,15 @@ var IonoAccumulator = class {
|
|
|
710
719
|
obsIndices;
|
|
711
720
|
gloChannels;
|
|
712
721
|
pairLabel = /* @__PURE__ */ new Map();
|
|
722
|
+
pairCodes = /* @__PURE__ */ new Map();
|
|
723
|
+
pairMeta = /* @__PURE__ */ new Map();
|
|
713
724
|
constructor(header) {
|
|
714
725
|
this.interval = header.interval ?? 30;
|
|
715
726
|
this.obsIndices = buildObsIndices(header);
|
|
716
727
|
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
717
728
|
}
|
|
718
729
|
/** Observation callback — wire this into parseRinexStream. */
|
|
719
|
-
onObservation = (time, prn,
|
|
730
|
+
onObservation = (time, prn, codes, values) => {
|
|
720
731
|
const sys = prn[0];
|
|
721
732
|
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
722
733
|
if (!bandMap) return;
|
|
@@ -727,7 +738,12 @@ var IonoAccumulator = class {
|
|
|
727
738
|
const lVal = values[L];
|
|
728
739
|
const freq = getFreq(this.gloChannels, prn, band);
|
|
729
740
|
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
730
|
-
bandData.set(band, {
|
|
741
|
+
bandData.set(band, {
|
|
742
|
+
C: cVal,
|
|
743
|
+
L: lVal,
|
|
744
|
+
f: freq,
|
|
745
|
+
code: codes[C] ?? `C${band}`
|
|
746
|
+
});
|
|
731
747
|
}
|
|
732
748
|
}
|
|
733
749
|
if (bandData.size < 2) return;
|
|
@@ -746,7 +762,9 @@ var IonoAccumulator = class {
|
|
|
746
762
|
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
747
763
|
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
748
764
|
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
765
|
+
this.pairCodes.set(`${sys}:${pairKey}`, [di.code, dj.code]);
|
|
749
766
|
}
|
|
767
|
+
this.pairMeta.set(`${prn}:${pairKey}`, { fi: di.f, gamma });
|
|
750
768
|
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
751
769
|
break;
|
|
752
770
|
}
|
|
@@ -847,10 +865,14 @@ var IonoAccumulator = class {
|
|
|
847
865
|
count++;
|
|
848
866
|
if (p.stec > maxStec) maxStec = p.stec;
|
|
849
867
|
}
|
|
868
|
+
const meta = this.pairMeta.get(`${prn}:${bestKey}`);
|
|
869
|
+
const tecuPerNs = meta ? C_LIGHT * 1e-9 / (meta.gamma - 1) * (meta.fi * meta.fi / TEC_FACTOR) : 0;
|
|
850
870
|
series.push({
|
|
851
871
|
prn,
|
|
852
872
|
system: sys,
|
|
853
873
|
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
874
|
+
codes: this.pairCodes.get(`${sys}:${bestKey}`) ?? ["", ""],
|
|
875
|
+
tecuPerNs,
|
|
854
876
|
points
|
|
855
877
|
});
|
|
856
878
|
}
|
|
@@ -859,6 +881,133 @@ var IonoAccumulator = class {
|
|
|
859
881
|
}
|
|
860
882
|
};
|
|
861
883
|
|
|
884
|
+
// src/analysis/dcb.ts
|
|
885
|
+
var PRN_RE = /^[A-Z]\d{2}$/;
|
|
886
|
+
var SVN_RE = /^[A-Z]\d{3}$/;
|
|
887
|
+
var OBS_RE = /^[CL]\d[A-Z]$/;
|
|
888
|
+
function sinexEpochMs(s) {
|
|
889
|
+
const [y, d, sec] = s.split(":").map(Number);
|
|
890
|
+
if (!y) return Infinity;
|
|
891
|
+
return Date.UTC(y, 0, 1) + ((d ?? 1) - 1) * 864e5 + (sec ?? 0) * 1e3;
|
|
892
|
+
}
|
|
893
|
+
function parseSinexBiasDcb(text, epochMs) {
|
|
894
|
+
const rows = /* @__PURE__ */ new Map();
|
|
895
|
+
for (const line of text.split("\n")) {
|
|
896
|
+
if (!line.startsWith(" DSB") && !line.startsWith("DSB")) continue;
|
|
897
|
+
const t = line.trim().split(/\s+/);
|
|
898
|
+
if (t.length < 9 || !SVN_RE.test(t[1]) || !PRN_RE.test(t[2]) || !OBS_RE.test(t[3]) || !OBS_RE.test(t[4])) {
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
const unitIdx = t.indexOf("ns");
|
|
902
|
+
if (unitIdx < 0 || unitIdx + 1 >= t.length) continue;
|
|
903
|
+
const value = parseFloat(t[unitIdx + 1]);
|
|
904
|
+
if (!isFinite(value)) continue;
|
|
905
|
+
const start = sinexEpochMs(t[5] ?? "");
|
|
906
|
+
const end = sinexEpochMs(t[6] ?? "");
|
|
907
|
+
const covers = epochMs !== void 0 && epochMs >= start && epochMs < end;
|
|
908
|
+
const prn = t[2];
|
|
909
|
+
const pair = `${t[3]}-${t[4]}`;
|
|
910
|
+
let sat = rows.get(prn);
|
|
911
|
+
if (!sat) {
|
|
912
|
+
sat = /* @__PURE__ */ new Map();
|
|
913
|
+
rows.set(prn, sat);
|
|
914
|
+
}
|
|
915
|
+
const prev = sat.get(pair);
|
|
916
|
+
if (!prev || covers && !prev.covers || covers === prev.covers && end > prev.end) {
|
|
917
|
+
sat.set(pair, { value, end, covers });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const out = /* @__PURE__ */ new Map();
|
|
921
|
+
for (const [prn, sat] of rows) {
|
|
922
|
+
const m = /* @__PURE__ */ new Map();
|
|
923
|
+
for (const [pair, row] of sat) m.set(pair, row.value);
|
|
924
|
+
out.set(prn, m);
|
|
925
|
+
}
|
|
926
|
+
return out;
|
|
927
|
+
}
|
|
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
|
+
function applyIonoDcb(iono, satDcb) {
|
|
933
|
+
const missing = [];
|
|
934
|
+
let corrected = 0;
|
|
935
|
+
const dcbFor = (s) => {
|
|
936
|
+
const sat = satDcb.get(s.prn);
|
|
937
|
+
if (!sat) return null;
|
|
938
|
+
const fwd = sat.get(`${s.codes[0]}-${s.codes[1]}`);
|
|
939
|
+
if (fwd !== void 0) return fwd;
|
|
940
|
+
const rev = sat.get(`${s.codes[1]}-${s.codes[0]}`);
|
|
941
|
+
if (rev !== void 0) return -rev;
|
|
942
|
+
return null;
|
|
943
|
+
};
|
|
944
|
+
const series = iono.series.map((s) => {
|
|
945
|
+
const dcbNs = dcbFor(s);
|
|
946
|
+
if (dcbNs === null) {
|
|
947
|
+
missing.push(s.prn);
|
|
948
|
+
return s;
|
|
949
|
+
}
|
|
950
|
+
corrected++;
|
|
951
|
+
const shift = dcbNs * s.tecuPerNs;
|
|
952
|
+
return {
|
|
953
|
+
...s,
|
|
954
|
+
points: s.points.map((p) => ({ time: p.time, stec: p.stec + shift }))
|
|
955
|
+
};
|
|
956
|
+
});
|
|
957
|
+
const missingSet = new Set(missing);
|
|
958
|
+
const receiverDcbTecu = {};
|
|
959
|
+
const groups = /* @__PURE__ */ new Map();
|
|
960
|
+
for (const s of series) {
|
|
961
|
+
const key = `${s.system} ${s.label}`;
|
|
962
|
+
let g = groups.get(key);
|
|
963
|
+
if (!g) {
|
|
964
|
+
g = [];
|
|
965
|
+
groups.set(key, g);
|
|
966
|
+
}
|
|
967
|
+
g.push(s);
|
|
968
|
+
}
|
|
969
|
+
const out = [];
|
|
970
|
+
for (const [key, group] of groups) {
|
|
971
|
+
const samples = [];
|
|
972
|
+
for (const s of group) {
|
|
973
|
+
if (missingSet.has(s.prn)) continue;
|
|
974
|
+
for (const p of s.points) samples.push(p.stec);
|
|
975
|
+
}
|
|
976
|
+
if (samples.length >= 100) {
|
|
977
|
+
const rx = percentile(samples, 0.01);
|
|
978
|
+
receiverDcbTecu[key] = rx;
|
|
979
|
+
for (const s of group) {
|
|
980
|
+
out.push({
|
|
981
|
+
...s,
|
|
982
|
+
points: s.points.map((p) => ({
|
|
983
|
+
time: p.time,
|
|
984
|
+
stec: p.stec - rx
|
|
985
|
+
}))
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
} else {
|
|
989
|
+
out.push(...group);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
out.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
993
|
+
let sum = 0;
|
|
994
|
+
let count = 0;
|
|
995
|
+
let maxStec = 0;
|
|
996
|
+
for (const s of out) {
|
|
997
|
+
for (const p of s.points) {
|
|
998
|
+
sum += p.stec;
|
|
999
|
+
count++;
|
|
1000
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
result: { series: out, maxStec, meanStec: count > 0 ? sum / count : 0 },
|
|
1005
|
+
satellitesCorrected: corrected,
|
|
1006
|
+
satellitesMissing: [...new Set(missing)].sort(),
|
|
1007
|
+
receiverDcbTecu
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
862
1011
|
// src/rinex/crx.ts
|
|
863
1012
|
function crxRepair(old, diff) {
|
|
864
1013
|
const chars = old.split("");
|
|
@@ -1680,5 +1829,7 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
1680
1829
|
CycleSlipAccumulator,
|
|
1681
1830
|
IonoAccumulator,
|
|
1682
1831
|
MultipathAccumulator,
|
|
1683
|
-
analyzeQuality
|
|
1832
|
+
analyzeQuality,
|
|
1833
|
+
applyIonoDcb,
|
|
1834
|
+
parseSinexBiasDcb
|
|
1684
1835
|
});
|
package/dist/analysis.d.cts
CHANGED
|
@@ -231,6 +231,12 @@ interface IonoSeries {
|
|
|
231
231
|
system: string;
|
|
232
232
|
/** Band pair used, e.g. "L1-L2". */
|
|
233
233
|
label: string;
|
|
234
|
+
/** Observation codes of the pair, e.g. ["C1C", "C2W"] — the key for
|
|
235
|
+
* matching differential code bias products. */
|
|
236
|
+
codes: [string, string];
|
|
237
|
+
/** TECU shift caused by 1 ns of geometry-free code bias (B_i − B_j);
|
|
238
|
+
* multiply a product DCB in ns by this to correct the series. */
|
|
239
|
+
tecuPerNs: number;
|
|
234
240
|
/** Time series of slant TEC values (TECU, DCB-biased). */
|
|
235
241
|
points: IonoPoint[];
|
|
236
242
|
}
|
|
@@ -249,9 +255,11 @@ declare class IonoAccumulator {
|
|
|
249
255
|
private obsIndices;
|
|
250
256
|
private gloChannels;
|
|
251
257
|
private pairLabel;
|
|
258
|
+
private pairCodes;
|
|
259
|
+
private pairMeta;
|
|
252
260
|
constructor(header: RinexHeader);
|
|
253
261
|
/** Observation callback — wire this into parseRinexStream. */
|
|
254
|
-
onObservation: (time: number, prn: string,
|
|
262
|
+
onObservation: (time: number, prn: string, codes: string[], values: (number | null)[]) => void;
|
|
255
263
|
private push;
|
|
256
264
|
/** External cycle-slip notification — close affected arcs. */
|
|
257
265
|
notifySlip(_time: number, prn: string, bands: Set<string>): void;
|
|
@@ -260,6 +268,48 @@ declare class IonoAccumulator {
|
|
|
260
268
|
finalize(): IonoResult;
|
|
261
269
|
}
|
|
262
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Differential code bias (DCB) products and their application to the
|
|
273
|
+
* slant-TEC series from the ionosphere module.
|
|
274
|
+
*
|
|
275
|
+
* Satellite DCBs come from SINEX_BIAS files published by the IGS
|
|
276
|
+
* analysis centres (e.g. CAS `CAS0MGXRAP_*_DCB.BSX`). The levelled
|
|
277
|
+
* geometry-free combination carries B_i − B_j (satellite) plus the
|
|
278
|
+
* receiver bias; subtracting the product value per satellite and then
|
|
279
|
+
* estimating the receiver bias from the night-time floor (the levelled
|
|
280
|
+
* combination is low-noise and TEC is near zero at night — Hans van
|
|
281
|
+
* der Marel's "no negative values" criterion) yields calibrated STEC.
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
/** PRN → "C1C-C2W" → bias (ns), satellite entries only. */
|
|
285
|
+
type SatDcbMap = Map<string, Map<string, number>>;
|
|
286
|
+
/**
|
|
287
|
+
* Parse satellite DSB entries from a SINEX_BIAS file (ESA .BIA,
|
|
288
|
+
* CAS/GFZ .BSX). Station entries (which carry a site name between PRN
|
|
289
|
+
* and the codes) are skipped. Long-history files publish several
|
|
290
|
+
* validity windows per satellite/pair — with values differing by many
|
|
291
|
+
* ns across SVN swaps — so pass the observation epoch to select the
|
|
292
|
+
* covering window; without it, the latest window wins. Values are
|
|
293
|
+
* returned in ns as published.
|
|
294
|
+
*/
|
|
295
|
+
declare function parseSinexBiasDcb(text: string, epochMs?: number): SatDcbMap;
|
|
296
|
+
interface IonoDcbResult {
|
|
297
|
+
result: IonoResult;
|
|
298
|
+
/** Satellites whose product DCB was found and applied. */
|
|
299
|
+
satellitesCorrected: number;
|
|
300
|
+
/** PRNs with no matching product entry (left sat-DCB-biased). */
|
|
301
|
+
satellitesMissing: string[];
|
|
302
|
+
/** Estimated receiver bias per "system label" group (TECU). */
|
|
303
|
+
receiverDcbTecu: Record<string, number>;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Apply satellite DCBs from a product file to an ionosphere result and
|
|
307
|
+
* estimate the receiver bias per system/pair group from the night-time
|
|
308
|
+
* floor (1st percentile → 0). Series without a product entry get the
|
|
309
|
+
* satellite part left in; they are reported in `satellitesMissing`.
|
|
310
|
+
*/
|
|
311
|
+
declare function applyIonoDcb(iono: IonoResult, satDcb: SatDcbMap): IonoDcbResult;
|
|
312
|
+
|
|
263
313
|
/**
|
|
264
314
|
* Combined quality analysis — runs cycle slip detection, data completeness,
|
|
265
315
|
* multipath and ionosphere analysis in a single file re-parse pass.
|
|
@@ -273,4 +323,4 @@ interface QualityResult {
|
|
|
273
323
|
}
|
|
274
324
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
275
325
|
|
|
276
|
-
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, IonoAccumulator, type IonoPoint, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, analyzeQuality };
|
|
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 };
|
package/dist/analysis.d.ts
CHANGED
|
@@ -231,6 +231,12 @@ interface IonoSeries {
|
|
|
231
231
|
system: string;
|
|
232
232
|
/** Band pair used, e.g. "L1-L2". */
|
|
233
233
|
label: string;
|
|
234
|
+
/** Observation codes of the pair, e.g. ["C1C", "C2W"] — the key for
|
|
235
|
+
* matching differential code bias products. */
|
|
236
|
+
codes: [string, string];
|
|
237
|
+
/** TECU shift caused by 1 ns of geometry-free code bias (B_i − B_j);
|
|
238
|
+
* multiply a product DCB in ns by this to correct the series. */
|
|
239
|
+
tecuPerNs: number;
|
|
234
240
|
/** Time series of slant TEC values (TECU, DCB-biased). */
|
|
235
241
|
points: IonoPoint[];
|
|
236
242
|
}
|
|
@@ -249,9 +255,11 @@ declare class IonoAccumulator {
|
|
|
249
255
|
private obsIndices;
|
|
250
256
|
private gloChannels;
|
|
251
257
|
private pairLabel;
|
|
258
|
+
private pairCodes;
|
|
259
|
+
private pairMeta;
|
|
252
260
|
constructor(header: RinexHeader);
|
|
253
261
|
/** Observation callback — wire this into parseRinexStream. */
|
|
254
|
-
onObservation: (time: number, prn: string,
|
|
262
|
+
onObservation: (time: number, prn: string, codes: string[], values: (number | null)[]) => void;
|
|
255
263
|
private push;
|
|
256
264
|
/** External cycle-slip notification — close affected arcs. */
|
|
257
265
|
notifySlip(_time: number, prn: string, bands: Set<string>): void;
|
|
@@ -260,6 +268,48 @@ declare class IonoAccumulator {
|
|
|
260
268
|
finalize(): IonoResult;
|
|
261
269
|
}
|
|
262
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Differential code bias (DCB) products and their application to the
|
|
273
|
+
* slant-TEC series from the ionosphere module.
|
|
274
|
+
*
|
|
275
|
+
* Satellite DCBs come from SINEX_BIAS files published by the IGS
|
|
276
|
+
* analysis centres (e.g. CAS `CAS0MGXRAP_*_DCB.BSX`). The levelled
|
|
277
|
+
* geometry-free combination carries B_i − B_j (satellite) plus the
|
|
278
|
+
* receiver bias; subtracting the product value per satellite and then
|
|
279
|
+
* estimating the receiver bias from the night-time floor (the levelled
|
|
280
|
+
* combination is low-noise and TEC is near zero at night — Hans van
|
|
281
|
+
* der Marel's "no negative values" criterion) yields calibrated STEC.
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
/** PRN → "C1C-C2W" → bias (ns), satellite entries only. */
|
|
285
|
+
type SatDcbMap = Map<string, Map<string, number>>;
|
|
286
|
+
/**
|
|
287
|
+
* Parse satellite DSB entries from a SINEX_BIAS file (ESA .BIA,
|
|
288
|
+
* CAS/GFZ .BSX). Station entries (which carry a site name between PRN
|
|
289
|
+
* and the codes) are skipped. Long-history files publish several
|
|
290
|
+
* validity windows per satellite/pair — with values differing by many
|
|
291
|
+
* ns across SVN swaps — so pass the observation epoch to select the
|
|
292
|
+
* covering window; without it, the latest window wins. Values are
|
|
293
|
+
* returned in ns as published.
|
|
294
|
+
*/
|
|
295
|
+
declare function parseSinexBiasDcb(text: string, epochMs?: number): SatDcbMap;
|
|
296
|
+
interface IonoDcbResult {
|
|
297
|
+
result: IonoResult;
|
|
298
|
+
/** Satellites whose product DCB was found and applied. */
|
|
299
|
+
satellitesCorrected: number;
|
|
300
|
+
/** PRNs with no matching product entry (left sat-DCB-biased). */
|
|
301
|
+
satellitesMissing: string[];
|
|
302
|
+
/** Estimated receiver bias per "system label" group (TECU). */
|
|
303
|
+
receiverDcbTecu: Record<string, number>;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Apply satellite DCBs from a product file to an ionosphere result and
|
|
307
|
+
* estimate the receiver bias per system/pair group from the night-time
|
|
308
|
+
* floor (1st percentile → 0). Series without a product entry get the
|
|
309
|
+
* satellite part left in; they are reported in `satellitesMissing`.
|
|
310
|
+
*/
|
|
311
|
+
declare function applyIonoDcb(iono: IonoResult, satDcb: SatDcbMap): IonoDcbResult;
|
|
312
|
+
|
|
263
313
|
/**
|
|
264
314
|
* Combined quality analysis — runs cycle slip detection, data completeness,
|
|
265
315
|
* multipath and ionosphere analysis in a single file re-parse pass.
|
|
@@ -273,4 +323,4 @@ interface QualityResult {
|
|
|
273
323
|
}
|
|
274
324
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
275
325
|
|
|
276
|
-
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, IonoAccumulator, type IonoPoint, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, analyzeQuality };
|
|
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 };
|
package/dist/analysis.js
CHANGED
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
CycleSlipAccumulator,
|
|
4
4
|
IonoAccumulator,
|
|
5
5
|
MultipathAccumulator,
|
|
6
|
-
analyzeQuality
|
|
7
|
-
|
|
6
|
+
analyzeQuality,
|
|
7
|
+
applyIonoDcb,
|
|
8
|
+
parseSinexBiasDcb
|
|
9
|
+
} from "./chunk-EJQ6NG5U.js";
|
|
8
10
|
import "./chunk-3U5AX7PY.js";
|
|
9
11
|
import "./chunk-FIEWO4J4.js";
|
|
10
12
|
export {
|
|
@@ -12,5 +14,7 @@ export {
|
|
|
12
14
|
CycleSlipAccumulator,
|
|
13
15
|
IonoAccumulator,
|
|
14
16
|
MultipathAccumulator,
|
|
15
|
-
analyzeQuality
|
|
17
|
+
analyzeQuality,
|
|
18
|
+
applyIonoDcb,
|
|
19
|
+
parseSinexBiasDcb
|
|
16
20
|
};
|
|
@@ -50,17 +50,24 @@ var MultipathAccumulator = class {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
if (bandData.size < 2) return;
|
|
53
|
+
const mpOf = (a, b) => {
|
|
54
|
+
const \u03BBa = C_LIGHT / a.f;
|
|
55
|
+
const \u03BBb = C_LIGHT / b.f;
|
|
56
|
+
const \u03B1 = a.f * a.f / (b.f * b.f);
|
|
57
|
+
const coeff = 2 / (\u03B1 - 1);
|
|
58
|
+
return a.C - (1 + coeff) * a.L * \u03BBa + coeff * b.L * \u03BBb;
|
|
59
|
+
};
|
|
53
60
|
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
61
|
+
let primaryDone = false;
|
|
54
62
|
for (const [bi, bj] of pairs) {
|
|
55
63
|
const di = bandData.get(bi);
|
|
56
64
|
const dj = bandData.get(bj);
|
|
57
65
|
if (!di || !dj) continue;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.pushMp(prn, bi, bj, time, mp);
|
|
66
|
+
if (!primaryDone) {
|
|
67
|
+
this.pushMp(prn, bi, bj, time, mpOf(di, dj));
|
|
68
|
+
primaryDone = true;
|
|
69
|
+
}
|
|
70
|
+
this.pushMp(prn, bj, bi, time, mpOf(dj, di));
|
|
64
71
|
}
|
|
65
72
|
};
|
|
66
73
|
pushMp(prn, band, refBand, time, mp) {
|
|
@@ -509,13 +516,15 @@ var IonoAccumulator = class {
|
|
|
509
516
|
obsIndices;
|
|
510
517
|
gloChannels;
|
|
511
518
|
pairLabel = /* @__PURE__ */ new Map();
|
|
519
|
+
pairCodes = /* @__PURE__ */ new Map();
|
|
520
|
+
pairMeta = /* @__PURE__ */ new Map();
|
|
512
521
|
constructor(header) {
|
|
513
522
|
this.interval = header.interval ?? 30;
|
|
514
523
|
this.obsIndices = buildObsIndices(header);
|
|
515
524
|
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
516
525
|
}
|
|
517
526
|
/** Observation callback — wire this into parseRinexStream. */
|
|
518
|
-
onObservation = (time, prn,
|
|
527
|
+
onObservation = (time, prn, codes, values) => {
|
|
519
528
|
const sys = prn[0];
|
|
520
529
|
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
521
530
|
if (!bandMap) return;
|
|
@@ -526,7 +535,12 @@ var IonoAccumulator = class {
|
|
|
526
535
|
const lVal = values[L];
|
|
527
536
|
const freq = getFreq(this.gloChannels, prn, band);
|
|
528
537
|
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
529
|
-
bandData.set(band, {
|
|
538
|
+
bandData.set(band, {
|
|
539
|
+
C: cVal,
|
|
540
|
+
L: lVal,
|
|
541
|
+
f: freq,
|
|
542
|
+
code: codes[C] ?? `C${band}`
|
|
543
|
+
});
|
|
530
544
|
}
|
|
531
545
|
}
|
|
532
546
|
if (bandData.size < 2) return;
|
|
@@ -545,7 +559,9 @@ var IonoAccumulator = class {
|
|
|
545
559
|
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
546
560
|
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
547
561
|
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
562
|
+
this.pairCodes.set(`${sys}:${pairKey}`, [di.code, dj.code]);
|
|
548
563
|
}
|
|
564
|
+
this.pairMeta.set(`${prn}:${pairKey}`, { fi: di.f, gamma });
|
|
549
565
|
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
550
566
|
break;
|
|
551
567
|
}
|
|
@@ -646,10 +662,14 @@ var IonoAccumulator = class {
|
|
|
646
662
|
count++;
|
|
647
663
|
if (p.stec > maxStec) maxStec = p.stec;
|
|
648
664
|
}
|
|
665
|
+
const meta = this.pairMeta.get(`${prn}:${bestKey}`);
|
|
666
|
+
const tecuPerNs = meta ? C_LIGHT * 1e-9 / (meta.gamma - 1) * (meta.fi * meta.fi / TEC_FACTOR) : 0;
|
|
649
667
|
series.push({
|
|
650
668
|
prn,
|
|
651
669
|
system: sys,
|
|
652
670
|
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
671
|
+
codes: this.pairCodes.get(`${sys}:${bestKey}`) ?? ["", ""],
|
|
672
|
+
tecuPerNs,
|
|
653
673
|
points
|
|
654
674
|
});
|
|
655
675
|
}
|
|
@@ -658,6 +678,133 @@ var IonoAccumulator = class {
|
|
|
658
678
|
}
|
|
659
679
|
};
|
|
660
680
|
|
|
681
|
+
// src/analysis/dcb.ts
|
|
682
|
+
var PRN_RE = /^[A-Z]\d{2}$/;
|
|
683
|
+
var SVN_RE = /^[A-Z]\d{3}$/;
|
|
684
|
+
var OBS_RE = /^[CL]\d[A-Z]$/;
|
|
685
|
+
function sinexEpochMs(s) {
|
|
686
|
+
const [y, d, sec] = s.split(":").map(Number);
|
|
687
|
+
if (!y) return Infinity;
|
|
688
|
+
return Date.UTC(y, 0, 1) + ((d ?? 1) - 1) * 864e5 + (sec ?? 0) * 1e3;
|
|
689
|
+
}
|
|
690
|
+
function parseSinexBiasDcb(text, epochMs) {
|
|
691
|
+
const rows = /* @__PURE__ */ new Map();
|
|
692
|
+
for (const line of text.split("\n")) {
|
|
693
|
+
if (!line.startsWith(" DSB") && !line.startsWith("DSB")) continue;
|
|
694
|
+
const t = line.trim().split(/\s+/);
|
|
695
|
+
if (t.length < 9 || !SVN_RE.test(t[1]) || !PRN_RE.test(t[2]) || !OBS_RE.test(t[3]) || !OBS_RE.test(t[4])) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
const unitIdx = t.indexOf("ns");
|
|
699
|
+
if (unitIdx < 0 || unitIdx + 1 >= t.length) continue;
|
|
700
|
+
const value = parseFloat(t[unitIdx + 1]);
|
|
701
|
+
if (!isFinite(value)) continue;
|
|
702
|
+
const start = sinexEpochMs(t[5] ?? "");
|
|
703
|
+
const end = sinexEpochMs(t[6] ?? "");
|
|
704
|
+
const covers = epochMs !== void 0 && epochMs >= start && epochMs < end;
|
|
705
|
+
const prn = t[2];
|
|
706
|
+
const pair = `${t[3]}-${t[4]}`;
|
|
707
|
+
let sat = rows.get(prn);
|
|
708
|
+
if (!sat) {
|
|
709
|
+
sat = /* @__PURE__ */ new Map();
|
|
710
|
+
rows.set(prn, sat);
|
|
711
|
+
}
|
|
712
|
+
const prev = sat.get(pair);
|
|
713
|
+
if (!prev || covers && !prev.covers || covers === prev.covers && end > prev.end) {
|
|
714
|
+
sat.set(pair, { value, end, covers });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const out = /* @__PURE__ */ new Map();
|
|
718
|
+
for (const [prn, sat] of rows) {
|
|
719
|
+
const m = /* @__PURE__ */ new Map();
|
|
720
|
+
for (const [pair, row] of sat) m.set(pair, row.value);
|
|
721
|
+
out.set(prn, m);
|
|
722
|
+
}
|
|
723
|
+
return out;
|
|
724
|
+
}
|
|
725
|
+
function percentile(values, p) {
|
|
726
|
+
const s = [...values].sort((a, b) => a - b);
|
|
727
|
+
return s[Math.min(s.length - 1, Math.floor(p * s.length))] ?? 0;
|
|
728
|
+
}
|
|
729
|
+
function applyIonoDcb(iono, satDcb) {
|
|
730
|
+
const missing = [];
|
|
731
|
+
let corrected = 0;
|
|
732
|
+
const dcbFor = (s) => {
|
|
733
|
+
const sat = satDcb.get(s.prn);
|
|
734
|
+
if (!sat) return null;
|
|
735
|
+
const fwd = sat.get(`${s.codes[0]}-${s.codes[1]}`);
|
|
736
|
+
if (fwd !== void 0) return fwd;
|
|
737
|
+
const rev = sat.get(`${s.codes[1]}-${s.codes[0]}`);
|
|
738
|
+
if (rev !== void 0) return -rev;
|
|
739
|
+
return null;
|
|
740
|
+
};
|
|
741
|
+
const series = iono.series.map((s) => {
|
|
742
|
+
const dcbNs = dcbFor(s);
|
|
743
|
+
if (dcbNs === null) {
|
|
744
|
+
missing.push(s.prn);
|
|
745
|
+
return s;
|
|
746
|
+
}
|
|
747
|
+
corrected++;
|
|
748
|
+
const shift = dcbNs * s.tecuPerNs;
|
|
749
|
+
return {
|
|
750
|
+
...s,
|
|
751
|
+
points: s.points.map((p) => ({ time: p.time, stec: p.stec + shift }))
|
|
752
|
+
};
|
|
753
|
+
});
|
|
754
|
+
const missingSet = new Set(missing);
|
|
755
|
+
const receiverDcbTecu = {};
|
|
756
|
+
const groups = /* @__PURE__ */ new Map();
|
|
757
|
+
for (const s of series) {
|
|
758
|
+
const key = `${s.system} ${s.label}`;
|
|
759
|
+
let g = groups.get(key);
|
|
760
|
+
if (!g) {
|
|
761
|
+
g = [];
|
|
762
|
+
groups.set(key, g);
|
|
763
|
+
}
|
|
764
|
+
g.push(s);
|
|
765
|
+
}
|
|
766
|
+
const out = [];
|
|
767
|
+
for (const [key, group] of groups) {
|
|
768
|
+
const samples = [];
|
|
769
|
+
for (const s of group) {
|
|
770
|
+
if (missingSet.has(s.prn)) continue;
|
|
771
|
+
for (const p of s.points) samples.push(p.stec);
|
|
772
|
+
}
|
|
773
|
+
if (samples.length >= 100) {
|
|
774
|
+
const rx = percentile(samples, 0.01);
|
|
775
|
+
receiverDcbTecu[key] = rx;
|
|
776
|
+
for (const s of group) {
|
|
777
|
+
out.push({
|
|
778
|
+
...s,
|
|
779
|
+
points: s.points.map((p) => ({
|
|
780
|
+
time: p.time,
|
|
781
|
+
stec: p.stec - rx
|
|
782
|
+
}))
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
out.push(...group);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
out.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
790
|
+
let sum = 0;
|
|
791
|
+
let count = 0;
|
|
792
|
+
let maxStec = 0;
|
|
793
|
+
for (const s of out) {
|
|
794
|
+
for (const p of s.points) {
|
|
795
|
+
sum += p.stec;
|
|
796
|
+
count++;
|
|
797
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
result: { series: out, maxStec, meanStec: count > 0 ? sum / count : 0 },
|
|
802
|
+
satellitesCorrected: corrected,
|
|
803
|
+
satellitesMissing: [...new Set(missing)].sort(),
|
|
804
|
+
receiverDcbTecu
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
661
808
|
// src/analysis/quality-analysis.ts
|
|
662
809
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
663
810
|
const mpAccum = new MultipathAccumulator(header);
|
|
@@ -693,5 +840,7 @@ export {
|
|
|
693
840
|
CycleSlipAccumulator,
|
|
694
841
|
CompletenessAccumulator,
|
|
695
842
|
IonoAccumulator,
|
|
843
|
+
parseSinexBiasDcb,
|
|
844
|
+
applyIonoDcb,
|
|
696
845
|
analyzeQuality
|
|
697
846
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -105,6 +105,7 @@ __export(src_exports, {
|
|
|
105
105
|
WarningAccumulator: () => WarningAccumulator,
|
|
106
106
|
analyzeQuality: () => analyzeQuality,
|
|
107
107
|
applyHelmert: () => applyHelmert,
|
|
108
|
+
applyIonoDcb: () => applyIonoDcb,
|
|
108
109
|
buildGloChannelMap: () => buildGloChannelMap,
|
|
109
110
|
buildObsIndices: () => buildObsIndices,
|
|
110
111
|
clampUnit: () => clampUnit,
|
|
@@ -207,6 +208,7 @@ __export(src_exports, {
|
|
|
207
208
|
parseNavFile: () => parseNavFile,
|
|
208
209
|
parseNmeaFile: () => parseNmeaFile,
|
|
209
210
|
parseRinexStream: () => parseRinexStream,
|
|
211
|
+
parseSinexBiasDcb: () => parseSinexBiasDcb,
|
|
210
212
|
parseSourcetable: () => parseSourcetable,
|
|
211
213
|
phiAltBOC: () => phiAltBOC,
|
|
212
214
|
phiBOCc: () => phiBOCc,
|
|
@@ -5079,17 +5081,24 @@ var MultipathAccumulator = class {
|
|
|
5079
5081
|
}
|
|
5080
5082
|
}
|
|
5081
5083
|
if (bandData.size < 2) return;
|
|
5084
|
+
const mpOf = (a, b) => {
|
|
5085
|
+
const \u03BBa = C_LIGHT / a.f;
|
|
5086
|
+
const \u03BBb = C_LIGHT / b.f;
|
|
5087
|
+
const \u03B1 = a.f * a.f / (b.f * b.f);
|
|
5088
|
+
const coeff = 2 / (\u03B1 - 1);
|
|
5089
|
+
return a.C - (1 + coeff) * a.L * \u03BBa + coeff * b.L * \u03BBb;
|
|
5090
|
+
};
|
|
5082
5091
|
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
5092
|
+
let primaryDone = false;
|
|
5083
5093
|
for (const [bi, bj] of pairs) {
|
|
5084
5094
|
const di = bandData.get(bi);
|
|
5085
5095
|
const dj = bandData.get(bj);
|
|
5086
5096
|
if (!di || !dj) continue;
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
this.pushMp(prn, bi, bj, time, mp);
|
|
5097
|
+
if (!primaryDone) {
|
|
5098
|
+
this.pushMp(prn, bi, bj, time, mpOf(di, dj));
|
|
5099
|
+
primaryDone = true;
|
|
5100
|
+
}
|
|
5101
|
+
this.pushMp(prn, bj, bi, time, mpOf(dj, di));
|
|
5093
5102
|
}
|
|
5094
5103
|
};
|
|
5095
5104
|
pushMp(prn, band, refBand, time, mp) {
|
|
@@ -5538,13 +5547,15 @@ var IonoAccumulator = class {
|
|
|
5538
5547
|
obsIndices;
|
|
5539
5548
|
gloChannels;
|
|
5540
5549
|
pairLabel = /* @__PURE__ */ new Map();
|
|
5550
|
+
pairCodes = /* @__PURE__ */ new Map();
|
|
5551
|
+
pairMeta = /* @__PURE__ */ new Map();
|
|
5541
5552
|
constructor(header) {
|
|
5542
5553
|
this.interval = header.interval ?? 30;
|
|
5543
5554
|
this.obsIndices = buildObsIndices(header);
|
|
5544
5555
|
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
5545
5556
|
}
|
|
5546
5557
|
/** Observation callback — wire this into parseRinexStream. */
|
|
5547
|
-
onObservation = (time, prn,
|
|
5558
|
+
onObservation = (time, prn, codes, values) => {
|
|
5548
5559
|
const sys = prn[0];
|
|
5549
5560
|
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
5550
5561
|
if (!bandMap) return;
|
|
@@ -5555,7 +5566,12 @@ var IonoAccumulator = class {
|
|
|
5555
5566
|
const lVal = values[L];
|
|
5556
5567
|
const freq = getFreq(this.gloChannels, prn, band);
|
|
5557
5568
|
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
5558
|
-
bandData.set(band, {
|
|
5569
|
+
bandData.set(band, {
|
|
5570
|
+
C: cVal,
|
|
5571
|
+
L: lVal,
|
|
5572
|
+
f: freq,
|
|
5573
|
+
code: codes[C] ?? `C${band}`
|
|
5574
|
+
});
|
|
5559
5575
|
}
|
|
5560
5576
|
}
|
|
5561
5577
|
if (bandData.size < 2) return;
|
|
@@ -5574,7 +5590,9 @@ var IonoAccumulator = class {
|
|
|
5574
5590
|
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
5575
5591
|
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
5576
5592
|
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
5593
|
+
this.pairCodes.set(`${sys}:${pairKey}`, [di.code, dj.code]);
|
|
5577
5594
|
}
|
|
5595
|
+
this.pairMeta.set(`${prn}:${pairKey}`, { fi: di.f, gamma });
|
|
5578
5596
|
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
5579
5597
|
break;
|
|
5580
5598
|
}
|
|
@@ -5675,10 +5693,14 @@ var IonoAccumulator = class {
|
|
|
5675
5693
|
count++;
|
|
5676
5694
|
if (p.stec > maxStec) maxStec = p.stec;
|
|
5677
5695
|
}
|
|
5696
|
+
const meta = this.pairMeta.get(`${prn}:${bestKey}`);
|
|
5697
|
+
const tecuPerNs = meta ? C_LIGHT * 1e-9 / (meta.gamma - 1) * (meta.fi * meta.fi / TEC_FACTOR) : 0;
|
|
5678
5698
|
series.push({
|
|
5679
5699
|
prn,
|
|
5680
5700
|
system: sys,
|
|
5681
5701
|
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
5702
|
+
codes: this.pairCodes.get(`${sys}:${bestKey}`) ?? ["", ""],
|
|
5703
|
+
tecuPerNs,
|
|
5682
5704
|
points
|
|
5683
5705
|
});
|
|
5684
5706
|
}
|
|
@@ -5687,6 +5709,133 @@ var IonoAccumulator = class {
|
|
|
5687
5709
|
}
|
|
5688
5710
|
};
|
|
5689
5711
|
|
|
5712
|
+
// src/analysis/dcb.ts
|
|
5713
|
+
var PRN_RE = /^[A-Z]\d{2}$/;
|
|
5714
|
+
var SVN_RE = /^[A-Z]\d{3}$/;
|
|
5715
|
+
var OBS_RE = /^[CL]\d[A-Z]$/;
|
|
5716
|
+
function sinexEpochMs(s) {
|
|
5717
|
+
const [y, d, sec] = s.split(":").map(Number);
|
|
5718
|
+
if (!y) return Infinity;
|
|
5719
|
+
return Date.UTC(y, 0, 1) + ((d ?? 1) - 1) * 864e5 + (sec ?? 0) * 1e3;
|
|
5720
|
+
}
|
|
5721
|
+
function parseSinexBiasDcb(text, epochMs) {
|
|
5722
|
+
const rows = /* @__PURE__ */ new Map();
|
|
5723
|
+
for (const line of text.split("\n")) {
|
|
5724
|
+
if (!line.startsWith(" DSB") && !line.startsWith("DSB")) continue;
|
|
5725
|
+
const t = line.trim().split(/\s+/);
|
|
5726
|
+
if (t.length < 9 || !SVN_RE.test(t[1]) || !PRN_RE.test(t[2]) || !OBS_RE.test(t[3]) || !OBS_RE.test(t[4])) {
|
|
5727
|
+
continue;
|
|
5728
|
+
}
|
|
5729
|
+
const unitIdx = t.indexOf("ns");
|
|
5730
|
+
if (unitIdx < 0 || unitIdx + 1 >= t.length) continue;
|
|
5731
|
+
const value = parseFloat(t[unitIdx + 1]);
|
|
5732
|
+
if (!isFinite(value)) continue;
|
|
5733
|
+
const start = sinexEpochMs(t[5] ?? "");
|
|
5734
|
+
const end = sinexEpochMs(t[6] ?? "");
|
|
5735
|
+
const covers = epochMs !== void 0 && epochMs >= start && epochMs < end;
|
|
5736
|
+
const prn = t[2];
|
|
5737
|
+
const pair = `${t[3]}-${t[4]}`;
|
|
5738
|
+
let sat = rows.get(prn);
|
|
5739
|
+
if (!sat) {
|
|
5740
|
+
sat = /* @__PURE__ */ new Map();
|
|
5741
|
+
rows.set(prn, sat);
|
|
5742
|
+
}
|
|
5743
|
+
const prev = sat.get(pair);
|
|
5744
|
+
if (!prev || covers && !prev.covers || covers === prev.covers && end > prev.end) {
|
|
5745
|
+
sat.set(pair, { value, end, covers });
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
const out = /* @__PURE__ */ new Map();
|
|
5749
|
+
for (const [prn, sat] of rows) {
|
|
5750
|
+
const m = /* @__PURE__ */ new Map();
|
|
5751
|
+
for (const [pair, row] of sat) m.set(pair, row.value);
|
|
5752
|
+
out.set(prn, m);
|
|
5753
|
+
}
|
|
5754
|
+
return out;
|
|
5755
|
+
}
|
|
5756
|
+
function percentile(values, p) {
|
|
5757
|
+
const s = [...values].sort((a, b) => a - b);
|
|
5758
|
+
return s[Math.min(s.length - 1, Math.floor(p * s.length))] ?? 0;
|
|
5759
|
+
}
|
|
5760
|
+
function applyIonoDcb(iono, satDcb) {
|
|
5761
|
+
const missing = [];
|
|
5762
|
+
let corrected = 0;
|
|
5763
|
+
const dcbFor = (s) => {
|
|
5764
|
+
const sat = satDcb.get(s.prn);
|
|
5765
|
+
if (!sat) return null;
|
|
5766
|
+
const fwd = sat.get(`${s.codes[0]}-${s.codes[1]}`);
|
|
5767
|
+
if (fwd !== void 0) return fwd;
|
|
5768
|
+
const rev = sat.get(`${s.codes[1]}-${s.codes[0]}`);
|
|
5769
|
+
if (rev !== void 0) return -rev;
|
|
5770
|
+
return null;
|
|
5771
|
+
};
|
|
5772
|
+
const series = iono.series.map((s) => {
|
|
5773
|
+
const dcbNs = dcbFor(s);
|
|
5774
|
+
if (dcbNs === null) {
|
|
5775
|
+
missing.push(s.prn);
|
|
5776
|
+
return s;
|
|
5777
|
+
}
|
|
5778
|
+
corrected++;
|
|
5779
|
+
const shift = dcbNs * s.tecuPerNs;
|
|
5780
|
+
return {
|
|
5781
|
+
...s,
|
|
5782
|
+
points: s.points.map((p) => ({ time: p.time, stec: p.stec + shift }))
|
|
5783
|
+
};
|
|
5784
|
+
});
|
|
5785
|
+
const missingSet = new Set(missing);
|
|
5786
|
+
const receiverDcbTecu = {};
|
|
5787
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5788
|
+
for (const s of series) {
|
|
5789
|
+
const key = `${s.system} ${s.label}`;
|
|
5790
|
+
let g = groups.get(key);
|
|
5791
|
+
if (!g) {
|
|
5792
|
+
g = [];
|
|
5793
|
+
groups.set(key, g);
|
|
5794
|
+
}
|
|
5795
|
+
g.push(s);
|
|
5796
|
+
}
|
|
5797
|
+
const out = [];
|
|
5798
|
+
for (const [key, group] of groups) {
|
|
5799
|
+
const samples = [];
|
|
5800
|
+
for (const s of group) {
|
|
5801
|
+
if (missingSet.has(s.prn)) continue;
|
|
5802
|
+
for (const p of s.points) samples.push(p.stec);
|
|
5803
|
+
}
|
|
5804
|
+
if (samples.length >= 100) {
|
|
5805
|
+
const rx = percentile(samples, 0.01);
|
|
5806
|
+
receiverDcbTecu[key] = rx;
|
|
5807
|
+
for (const s of group) {
|
|
5808
|
+
out.push({
|
|
5809
|
+
...s,
|
|
5810
|
+
points: s.points.map((p) => ({
|
|
5811
|
+
time: p.time,
|
|
5812
|
+
stec: p.stec - rx
|
|
5813
|
+
}))
|
|
5814
|
+
});
|
|
5815
|
+
}
|
|
5816
|
+
} else {
|
|
5817
|
+
out.push(...group);
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
out.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
5821
|
+
let sum = 0;
|
|
5822
|
+
let count = 0;
|
|
5823
|
+
let maxStec = 0;
|
|
5824
|
+
for (const s of out) {
|
|
5825
|
+
for (const p of s.points) {
|
|
5826
|
+
sum += p.stec;
|
|
5827
|
+
count++;
|
|
5828
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
return {
|
|
5832
|
+
result: { series: out, maxStec, meanStec: count > 0 ? sum / count : 0 },
|
|
5833
|
+
satellitesCorrected: corrected,
|
|
5834
|
+
satellitesMissing: [...new Set(missing)].sort(),
|
|
5835
|
+
receiverDcbTecu
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5690
5839
|
// src/analysis/quality-analysis.ts
|
|
5691
5840
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
5692
5841
|
const mpAccum = new MultipathAccumulator(header);
|
|
@@ -6993,6 +7142,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
6993
7142
|
WarningAccumulator,
|
|
6994
7143
|
analyzeQuality,
|
|
6995
7144
|
applyHelmert,
|
|
7145
|
+
applyIonoDcb,
|
|
6996
7146
|
buildGloChannelMap,
|
|
6997
7147
|
buildObsIndices,
|
|
6998
7148
|
clampUnit,
|
|
@@ -7095,6 +7245,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
7095
7245
|
parseNavFile,
|
|
7096
7246
|
parseNmeaFile,
|
|
7097
7247
|
parseRinexStream,
|
|
7248
|
+
parseSinexBiasDcb,
|
|
7098
7249
|
parseSourcetable,
|
|
7099
7250
|
phiAltBOC,
|
|
7100
7251
|
phiBOCc,
|
package/dist/index.d.cts
CHANGED
|
@@ -13,7 +13,7 @@ export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPositi
|
|
|
13
13
|
export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
|
|
14
14
|
export { SppOptions, SppSolution, ionoFree, satClockCorrection, solveSpp } from './positioning.cjs';
|
|
15
15
|
export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
|
|
16
|
-
export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoPoint, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.cjs';
|
|
16
|
+
export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, parseSinexBiasDcb } from './analysis.cjs';
|
|
17
17
|
export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';
|
|
18
18
|
export { NmeaFix, NmeaStats, NmeaTrack, computeStats, nmeaCoordToDecimal, parseNmeaFile, verifyChecksum } from './nmea.cjs';
|
|
19
19
|
export { BAND_PRESETS, BandPreset, CONSTELLATIONS, ConstellationRow, DELTA_GLO_L1, DELTA_GLO_L2, FREQ_BDS_B1A, FREQ_BDS_B1C, FREQ_BDS_B1I, FREQ_BDS_B2I, FREQ_BDS_B3A, FREQ_BDS_B3I, FREQ_GAL_E1, FREQ_GAL_E5, FREQ_GAL_E5a, FREQ_GAL_E5b, FREQ_GAL_E6, FREQ_GLO_L1, FREQ_GLO_L1OC, FREQ_GLO_L2, FREQ_GLO_L2OC, FREQ_GLO_L3OC, FREQ_GPS_L1, FREQ_GPS_L2, FREQ_GPS_L5, FREQ_NAVIC_L5, FREQ_QZS_L1, FREQ_QZS_L2, FREQ_QZS_L5, FREQ_QZS_L6, Modulation, SignalDef, computePsdDb, phiAltBOC, phiBOCc, phiBOCs, phiBPSK } from './signals.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { AllPositionsData, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPositi
|
|
|
13
13
|
export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
|
|
14
14
|
export { SppOptions, SppSolution, ionoFree, satClockCorrection, solveSpp } from './positioning.js';
|
|
15
15
|
export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
|
|
16
|
-
export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoPoint, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.js';
|
|
16
|
+
export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, parseSinexBiasDcb } from './analysis.js';
|
|
17
17
|
export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.js';
|
|
18
18
|
export { NmeaFix, NmeaStats, NmeaTrack, computeStats, nmeaCoordToDecimal, parseNmeaFile, verifyChecksum } from './nmea.js';
|
|
19
19
|
export { BAND_PRESETS, BandPreset, CONSTELLATIONS, ConstellationRow, DELTA_GLO_L1, DELTA_GLO_L2, FREQ_BDS_B1A, FREQ_BDS_B1C, FREQ_BDS_B1I, FREQ_BDS_B2I, FREQ_BDS_B3A, FREQ_BDS_B3I, FREQ_GAL_E1, FREQ_GAL_E5, FREQ_GAL_E5a, FREQ_GAL_E5b, FREQ_GAL_E6, FREQ_GLO_L1, FREQ_GLO_L1OC, FREQ_GLO_L2, FREQ_GLO_L2OC, FREQ_GLO_L3OC, FREQ_GPS_L1, FREQ_GPS_L2, FREQ_GPS_L5, FREQ_NAVIC_L5, FREQ_QZS_L1, FREQ_QZS_L2, FREQ_QZS_L5, FREQ_QZS_L6, Modulation, SignalDef, computePsdDb, phiAltBOC, phiBOCc, phiBOCs, phiBPSK } from './signals.js';
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
CycleSlipAccumulator,
|
|
9
9
|
IonoAccumulator,
|
|
10
10
|
MultipathAccumulator,
|
|
11
|
-
analyzeQuality
|
|
12
|
-
|
|
11
|
+
analyzeQuality,
|
|
12
|
+
applyIonoDcb,
|
|
13
|
+
parseSinexBiasDcb
|
|
14
|
+
} from "./chunk-EJQ6NG5U.js";
|
|
13
15
|
import {
|
|
14
16
|
frequencyLabel,
|
|
15
17
|
parseAntex
|
|
@@ -332,6 +334,7 @@ export {
|
|
|
332
334
|
WarningAccumulator,
|
|
333
335
|
analyzeQuality,
|
|
334
336
|
applyHelmert,
|
|
337
|
+
applyIonoDcb,
|
|
335
338
|
buildGloChannelMap,
|
|
336
339
|
buildObsIndices,
|
|
337
340
|
clampUnit,
|
|
@@ -434,6 +437,7 @@ export {
|
|
|
434
437
|
parseNavFile,
|
|
435
438
|
parseNmeaFile,
|
|
436
439
|
parseRinexStream,
|
|
440
|
+
parseSinexBiasDcb,
|
|
437
441
|
parseSourcetable,
|
|
438
442
|
phiAltBOC,
|
|
439
443
|
phiBOCc,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnss-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|