gnss-js 1.3.2 → 1.4.1
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 +185 -7
- package/dist/analysis.d.cts +67 -2
- package/dist/analysis.d.ts +67 -2
- package/dist/analysis.js +3 -1
- package/dist/{chunk-AW6BORTZ.js → chunk-UQ7D53R2.js} +184 -7
- package/dist/index.cjs +185 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
package/dist/analysis.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var analysis_exports = {};
|
|
|
22
22
|
__export(analysis_exports, {
|
|
23
23
|
CompletenessAccumulator: () => CompletenessAccumulator,
|
|
24
24
|
CycleSlipAccumulator: () => CycleSlipAccumulator,
|
|
25
|
+
IonoAccumulator: () => IonoAccumulator,
|
|
25
26
|
MultipathAccumulator: () => MultipathAccumulator,
|
|
26
27
|
analyzeQuality: () => analyzeQuality
|
|
27
28
|
});
|
|
@@ -250,17 +251,24 @@ var MultipathAccumulator = class {
|
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
if (bandData.size < 2) return;
|
|
254
|
+
const mpOf = (a, b) => {
|
|
255
|
+
const \u03BBa = C_LIGHT / a.f;
|
|
256
|
+
const \u03BBb = C_LIGHT / b.f;
|
|
257
|
+
const \u03B1 = a.f * a.f / (b.f * b.f);
|
|
258
|
+
const coeff = 2 / (\u03B1 - 1);
|
|
259
|
+
return a.C - (1 + coeff) * a.L * \u03BBa + coeff * b.L * \u03BBb;
|
|
260
|
+
};
|
|
253
261
|
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
262
|
+
let primaryDone = false;
|
|
254
263
|
for (const [bi, bj] of pairs) {
|
|
255
264
|
const di = bandData.get(bi);
|
|
256
265
|
const dj = bandData.get(bj);
|
|
257
266
|
if (!di || !dj) continue;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.pushMp(prn, bi, bj, time, mp);
|
|
267
|
+
if (!primaryDone) {
|
|
268
|
+
this.pushMp(prn, bi, bj, time, mpOf(di, dj));
|
|
269
|
+
primaryDone = true;
|
|
270
|
+
}
|
|
271
|
+
this.pushMp(prn, bj, bi, time, mpOf(dj, di));
|
|
264
272
|
}
|
|
265
273
|
};
|
|
266
274
|
pushMp(prn, band, refBand, time, mp) {
|
|
@@ -693,6 +701,171 @@ var CompletenessAccumulator = class {
|
|
|
693
701
|
}
|
|
694
702
|
};
|
|
695
703
|
|
|
704
|
+
// src/analysis/ionosphere.ts
|
|
705
|
+
var TEC_FACTOR = 403e15;
|
|
706
|
+
var GF_JUMP_M = 0.15;
|
|
707
|
+
var MIN_ARC_LENGTH2 = 10;
|
|
708
|
+
function median2(values) {
|
|
709
|
+
const s = [...values].sort((a, b) => a - b);
|
|
710
|
+
const m = s.length >> 1;
|
|
711
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
712
|
+
}
|
|
713
|
+
var IonoAccumulator = class {
|
|
714
|
+
state = /* @__PURE__ */ new Map();
|
|
715
|
+
closed = /* @__PURE__ */ new Map();
|
|
716
|
+
interval;
|
|
717
|
+
obsIndices;
|
|
718
|
+
gloChannels;
|
|
719
|
+
pairLabel = /* @__PURE__ */ new Map();
|
|
720
|
+
constructor(header) {
|
|
721
|
+
this.interval = header.interval ?? 30;
|
|
722
|
+
this.obsIndices = buildObsIndices(header);
|
|
723
|
+
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
724
|
+
}
|
|
725
|
+
/** Observation callback — wire this into parseRinexStream. */
|
|
726
|
+
onObservation = (time, prn, _codes, values) => {
|
|
727
|
+
const sys = prn[0];
|
|
728
|
+
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
729
|
+
if (!bandMap) return;
|
|
730
|
+
const bandData = /* @__PURE__ */ new Map();
|
|
731
|
+
for (const [band, { C, L }] of bandMap) {
|
|
732
|
+
if (C === null) continue;
|
|
733
|
+
const cVal = values[C];
|
|
734
|
+
const lVal = values[L];
|
|
735
|
+
const freq = getFreq(this.gloChannels, prn, band);
|
|
736
|
+
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
737
|
+
bandData.set(band, { C: cVal, L: lVal, f: freq });
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (bandData.size < 2) return;
|
|
741
|
+
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
742
|
+
for (const [bi, bj] of pairs) {
|
|
743
|
+
const di = bandData.get(bi);
|
|
744
|
+
const dj = bandData.get(bj);
|
|
745
|
+
if (!di || !dj) continue;
|
|
746
|
+
const \u03BBi = C_LIGHT / di.f;
|
|
747
|
+
const \u03BBj = C_LIGHT / dj.f;
|
|
748
|
+
const gamma = di.f * di.f / (dj.f * dj.f);
|
|
749
|
+
const l4 = di.L * \u03BBi - dj.L * \u03BBj;
|
|
750
|
+
const p4 = dj.C - di.C;
|
|
751
|
+
const pairKey = `${bi}-${bj}`;
|
|
752
|
+
if (!this.pairLabel.has(`${sys}:${pairKey}`)) {
|
|
753
|
+
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
754
|
+
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
755
|
+
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
756
|
+
}
|
|
757
|
+
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
push(prn, pairKey, time, l4, p4, fi, gamma) {
|
|
762
|
+
if (!isFinite(l4) || !isFinite(p4)) return;
|
|
763
|
+
let satStates = this.state.get(prn);
|
|
764
|
+
if (!satStates) {
|
|
765
|
+
satStates = /* @__PURE__ */ new Map();
|
|
766
|
+
this.state.set(prn, satStates);
|
|
767
|
+
}
|
|
768
|
+
let ps = satStates.get(pairKey);
|
|
769
|
+
if (!ps) {
|
|
770
|
+
ps = {
|
|
771
|
+
arc: { times: [], l4: [], p4: [], fi, gamma },
|
|
772
|
+
lastTime: 0,
|
|
773
|
+
lastL4: 0
|
|
774
|
+
};
|
|
775
|
+
satStates.set(pairKey, ps);
|
|
776
|
+
}
|
|
777
|
+
const gap = ps.lastTime > 0 ? (time - ps.lastTime) / 1e3 : 0;
|
|
778
|
+
if (ps.lastTime > 0 && gap > this.interval * ARC_GAP_FACTOR) {
|
|
779
|
+
this.closeArc(prn, pairKey, ps);
|
|
780
|
+
} else if (ps.arc.times.length > 0 && Math.abs(l4 - ps.lastL4) > GF_JUMP_M) {
|
|
781
|
+
this.closeArc(prn, pairKey, ps);
|
|
782
|
+
}
|
|
783
|
+
if (ps.arc.times.length === 0) {
|
|
784
|
+
ps.arc.fi = fi;
|
|
785
|
+
ps.arc.gamma = gamma;
|
|
786
|
+
}
|
|
787
|
+
ps.arc.times.push(time);
|
|
788
|
+
ps.arc.l4.push(l4);
|
|
789
|
+
ps.arc.p4.push(p4);
|
|
790
|
+
ps.lastTime = time;
|
|
791
|
+
ps.lastL4 = l4;
|
|
792
|
+
}
|
|
793
|
+
/** External cycle-slip notification — close affected arcs. */
|
|
794
|
+
notifySlip(_time, prn, bands) {
|
|
795
|
+
const satStates = this.state.get(prn);
|
|
796
|
+
if (!satStates) return;
|
|
797
|
+
for (const [pairKey, ps] of satStates) {
|
|
798
|
+
const [bi, bj] = pairKey.split("-");
|
|
799
|
+
if (bands.has(bi) || bands.has(bj)) {
|
|
800
|
+
this.closeArc(prn, pairKey, ps);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
closeArc(prn, pairKey, ps) {
|
|
805
|
+
const arc = ps.arc;
|
|
806
|
+
if (arc.times.length >= MIN_ARC_LENGTH2) {
|
|
807
|
+
const level = median2(arc.l4.map((v, k) => v - arc.p4[k]));
|
|
808
|
+
const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
|
|
809
|
+
let satArcs = this.closed.get(prn);
|
|
810
|
+
if (!satArcs) {
|
|
811
|
+
satArcs = /* @__PURE__ */ new Map();
|
|
812
|
+
this.closed.set(prn, satArcs);
|
|
813
|
+
}
|
|
814
|
+
let points = satArcs.get(pairKey);
|
|
815
|
+
if (!points) {
|
|
816
|
+
points = [];
|
|
817
|
+
satArcs.set(pairKey, points);
|
|
818
|
+
}
|
|
819
|
+
for (let k = 0; k < arc.times.length; k++) {
|
|
820
|
+
points.push({
|
|
821
|
+
time: arc.times[k],
|
|
822
|
+
stec: (arc.l4[k] - level) * toTecu
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
ps.arc = { times: [], l4: [], p4: [], fi: arc.fi, gamma: arc.gamma };
|
|
827
|
+
}
|
|
828
|
+
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
829
|
+
finalize() {
|
|
830
|
+
for (const [prn, satStates] of this.state) {
|
|
831
|
+
for (const [pairKey, ps] of satStates) {
|
|
832
|
+
this.closeArc(prn, pairKey, ps);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const series = [];
|
|
836
|
+
let sum = 0;
|
|
837
|
+
let count = 0;
|
|
838
|
+
let maxStec = 0;
|
|
839
|
+
for (const [prn, satArcs] of this.closed) {
|
|
840
|
+
let bestKey = null;
|
|
841
|
+
let bestLen = 0;
|
|
842
|
+
for (const [pairKey, points2] of satArcs) {
|
|
843
|
+
if (points2.length > bestLen) {
|
|
844
|
+
bestLen = points2.length;
|
|
845
|
+
bestKey = pairKey;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (!bestKey) continue;
|
|
849
|
+
const points = satArcs.get(bestKey);
|
|
850
|
+
points.sort((a, b) => a.time - b.time);
|
|
851
|
+
const sys = prn[0];
|
|
852
|
+
for (const p of points) {
|
|
853
|
+
sum += p.stec;
|
|
854
|
+
count++;
|
|
855
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
856
|
+
}
|
|
857
|
+
series.push({
|
|
858
|
+
prn,
|
|
859
|
+
system: sys,
|
|
860
|
+
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
861
|
+
points
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
series.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
865
|
+
return { series, maxStec, meanStec: count > 0 ? sum / count : 0 };
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
696
869
|
// src/rinex/crx.ts
|
|
697
870
|
function crxRepair(old, diff) {
|
|
698
871
|
const chars = old.split("");
|
|
@@ -1482,8 +1655,10 @@ function computeStats(header, epochs, satellitesSeen) {
|
|
|
1482
1655
|
// src/analysis/quality-analysis.ts
|
|
1483
1656
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
1484
1657
|
const mpAccum = new MultipathAccumulator(header);
|
|
1658
|
+
const ionoAccum = new IonoAccumulator(header);
|
|
1485
1659
|
const csAccum = new CycleSlipAccumulator(header, (time, prn, bands) => {
|
|
1486
1660
|
mpAccum.notifySlip(time, prn, bands);
|
|
1661
|
+
ionoAccum.notifySlip(time, prn, bands);
|
|
1487
1662
|
});
|
|
1488
1663
|
const compAccum = new CompletenessAccumulator(header);
|
|
1489
1664
|
await parseRinexStream(
|
|
@@ -1493,6 +1668,7 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
1493
1668
|
(time, prn, codes, values) => {
|
|
1494
1669
|
csAccum.onObservation(time, prn, codes, values);
|
|
1495
1670
|
mpAccum.onObservation(time, prn, codes, values);
|
|
1671
|
+
ionoAccum.onObservation(time, prn, codes, values);
|
|
1496
1672
|
compAccum.onObservation(time, prn, codes, values);
|
|
1497
1673
|
},
|
|
1498
1674
|
true
|
|
@@ -1501,13 +1677,15 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
1501
1677
|
return {
|
|
1502
1678
|
cycleSlips: csAccum.finalize(),
|
|
1503
1679
|
completeness: compAccum.finalize(),
|
|
1504
|
-
multipath: mpAccum.finalize()
|
|
1680
|
+
multipath: mpAccum.finalize(),
|
|
1681
|
+
iono: ionoAccum.finalize()
|
|
1505
1682
|
};
|
|
1506
1683
|
}
|
|
1507
1684
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1508
1685
|
0 && (module.exports = {
|
|
1509
1686
|
CompletenessAccumulator,
|
|
1510
1687
|
CycleSlipAccumulator,
|
|
1688
|
+
IonoAccumulator,
|
|
1511
1689
|
MultipathAccumulator,
|
|
1512
1690
|
analyzeQuality
|
|
1513
1691
|
});
|
package/dist/analysis.d.cts
CHANGED
|
@@ -196,16 +196,81 @@ declare class CompletenessAccumulator {
|
|
|
196
196
|
finalize(): CompletenessResult;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Slant ionospheric delay from dual-frequency observations.
|
|
201
|
+
*
|
|
202
|
+
* Geometry-free phase combination levelled to the geometry-free code,
|
|
203
|
+
* per continuous arc:
|
|
204
|
+
* L4 = λi·Li − λj·Lj = (γ−1)·I_i + B (phase; B = ambiguity bias)
|
|
205
|
+
* P4 = C_j − C_i = (γ−1)·I_i + DCBs (code)
|
|
206
|
+
* with γ = (f_i/f_j)² and I_i the slant ionospheric delay on band i.
|
|
207
|
+
* The arc levelling constant B̂ = median(L4 − P4) transfers the code's
|
|
208
|
+
* absolute level onto the low-noise phase; slant TEC follows as
|
|
209
|
+
* STEC [TECU] = (L4 − B̂)/(γ−1) · f_i² / 40.3e16.
|
|
210
|
+
*
|
|
211
|
+
* The result carries the receiver+satellite differential code biases
|
|
212
|
+
* (not removed here — several TECU per satellite), so series are
|
|
213
|
+
* DCB-biased but shape-faithful: diurnal variation, gradients and
|
|
214
|
+
* disturbances read correctly.
|
|
215
|
+
*
|
|
216
|
+
* The geometry-free phase is also a sensitive cycle-slip detector:
|
|
217
|
+
* arcs split on epoch-to-epoch L4 jumps beyond GF_JUMP_M, in addition
|
|
218
|
+
* to time gaps and external slip notifications.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
interface IonoPoint {
|
|
222
|
+
/** Epoch time in milliseconds. */
|
|
223
|
+
time: number;
|
|
224
|
+
/** Slant TEC in TECU (DCB-biased). */
|
|
225
|
+
stec: number;
|
|
226
|
+
}
|
|
227
|
+
interface IonoSeries {
|
|
228
|
+
/** Satellite PRN, e.g. "G01". */
|
|
229
|
+
prn: string;
|
|
230
|
+
/** System letter, e.g. "G". */
|
|
231
|
+
system: string;
|
|
232
|
+
/** Band pair used, e.g. "L1-L2". */
|
|
233
|
+
label: string;
|
|
234
|
+
/** Time series of slant TEC values (TECU, DCB-biased). */
|
|
235
|
+
points: IonoPoint[];
|
|
236
|
+
}
|
|
237
|
+
interface IonoResult {
|
|
238
|
+
/** Per-satellite slant TEC time series. */
|
|
239
|
+
series: IonoSeries[];
|
|
240
|
+
/** Highest single STEC sample across all series (TECU). */
|
|
241
|
+
maxStec: number;
|
|
242
|
+
/** Mean STEC across all samples (TECU). */
|
|
243
|
+
meanStec: number;
|
|
244
|
+
}
|
|
245
|
+
declare class IonoAccumulator {
|
|
246
|
+
private state;
|
|
247
|
+
private closed;
|
|
248
|
+
private interval;
|
|
249
|
+
private obsIndices;
|
|
250
|
+
private gloChannels;
|
|
251
|
+
private pairLabel;
|
|
252
|
+
constructor(header: RinexHeader);
|
|
253
|
+
/** Observation callback — wire this into parseRinexStream. */
|
|
254
|
+
onObservation: (time: number, prn: string, _codes: string[], values: (number | null)[]) => void;
|
|
255
|
+
private push;
|
|
256
|
+
/** External cycle-slip notification — close affected arcs. */
|
|
257
|
+
notifySlip(_time: number, prn: string, bands: Set<string>): void;
|
|
258
|
+
private closeArc;
|
|
259
|
+
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
260
|
+
finalize(): IonoResult;
|
|
261
|
+
}
|
|
262
|
+
|
|
199
263
|
/**
|
|
200
264
|
* Combined quality analysis — runs cycle slip detection, data completeness,
|
|
201
|
-
* and
|
|
265
|
+
* multipath and ionosphere analysis in a single file re-parse pass.
|
|
202
266
|
*/
|
|
203
267
|
|
|
204
268
|
interface QualityResult {
|
|
205
269
|
cycleSlips: CycleSlipResult;
|
|
206
270
|
completeness: CompletenessResult;
|
|
207
271
|
multipath: MultipathResult;
|
|
272
|
+
iono: IonoResult;
|
|
208
273
|
}
|
|
209
274
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
210
275
|
|
|
211
|
-
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, analyzeQuality };
|
|
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 };
|
package/dist/analysis.d.ts
CHANGED
|
@@ -196,16 +196,81 @@ declare class CompletenessAccumulator {
|
|
|
196
196
|
finalize(): CompletenessResult;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Slant ionospheric delay from dual-frequency observations.
|
|
201
|
+
*
|
|
202
|
+
* Geometry-free phase combination levelled to the geometry-free code,
|
|
203
|
+
* per continuous arc:
|
|
204
|
+
* L4 = λi·Li − λj·Lj = (γ−1)·I_i + B (phase; B = ambiguity bias)
|
|
205
|
+
* P4 = C_j − C_i = (γ−1)·I_i + DCBs (code)
|
|
206
|
+
* with γ = (f_i/f_j)² and I_i the slant ionospheric delay on band i.
|
|
207
|
+
* The arc levelling constant B̂ = median(L4 − P4) transfers the code's
|
|
208
|
+
* absolute level onto the low-noise phase; slant TEC follows as
|
|
209
|
+
* STEC [TECU] = (L4 − B̂)/(γ−1) · f_i² / 40.3e16.
|
|
210
|
+
*
|
|
211
|
+
* The result carries the receiver+satellite differential code biases
|
|
212
|
+
* (not removed here — several TECU per satellite), so series are
|
|
213
|
+
* DCB-biased but shape-faithful: diurnal variation, gradients and
|
|
214
|
+
* disturbances read correctly.
|
|
215
|
+
*
|
|
216
|
+
* The geometry-free phase is also a sensitive cycle-slip detector:
|
|
217
|
+
* arcs split on epoch-to-epoch L4 jumps beyond GF_JUMP_M, in addition
|
|
218
|
+
* to time gaps and external slip notifications.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
interface IonoPoint {
|
|
222
|
+
/** Epoch time in milliseconds. */
|
|
223
|
+
time: number;
|
|
224
|
+
/** Slant TEC in TECU (DCB-biased). */
|
|
225
|
+
stec: number;
|
|
226
|
+
}
|
|
227
|
+
interface IonoSeries {
|
|
228
|
+
/** Satellite PRN, e.g. "G01". */
|
|
229
|
+
prn: string;
|
|
230
|
+
/** System letter, e.g. "G". */
|
|
231
|
+
system: string;
|
|
232
|
+
/** Band pair used, e.g. "L1-L2". */
|
|
233
|
+
label: string;
|
|
234
|
+
/** Time series of slant TEC values (TECU, DCB-biased). */
|
|
235
|
+
points: IonoPoint[];
|
|
236
|
+
}
|
|
237
|
+
interface IonoResult {
|
|
238
|
+
/** Per-satellite slant TEC time series. */
|
|
239
|
+
series: IonoSeries[];
|
|
240
|
+
/** Highest single STEC sample across all series (TECU). */
|
|
241
|
+
maxStec: number;
|
|
242
|
+
/** Mean STEC across all samples (TECU). */
|
|
243
|
+
meanStec: number;
|
|
244
|
+
}
|
|
245
|
+
declare class IonoAccumulator {
|
|
246
|
+
private state;
|
|
247
|
+
private closed;
|
|
248
|
+
private interval;
|
|
249
|
+
private obsIndices;
|
|
250
|
+
private gloChannels;
|
|
251
|
+
private pairLabel;
|
|
252
|
+
constructor(header: RinexHeader);
|
|
253
|
+
/** Observation callback — wire this into parseRinexStream. */
|
|
254
|
+
onObservation: (time: number, prn: string, _codes: string[], values: (number | null)[]) => void;
|
|
255
|
+
private push;
|
|
256
|
+
/** External cycle-slip notification — close affected arcs. */
|
|
257
|
+
notifySlip(_time: number, prn: string, bands: Set<string>): void;
|
|
258
|
+
private closeArc;
|
|
259
|
+
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
260
|
+
finalize(): IonoResult;
|
|
261
|
+
}
|
|
262
|
+
|
|
199
263
|
/**
|
|
200
264
|
* Combined quality analysis — runs cycle slip detection, data completeness,
|
|
201
|
-
* and
|
|
265
|
+
* multipath and ionosphere analysis in a single file re-parse pass.
|
|
202
266
|
*/
|
|
203
267
|
|
|
204
268
|
interface QualityResult {
|
|
205
269
|
cycleSlips: CycleSlipResult;
|
|
206
270
|
completeness: CompletenessResult;
|
|
207
271
|
multipath: MultipathResult;
|
|
272
|
+
iono: IonoResult;
|
|
208
273
|
}
|
|
209
274
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
210
275
|
|
|
211
|
-
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, analyzeQuality };
|
|
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 };
|
package/dist/analysis.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CompletenessAccumulator,
|
|
3
3
|
CycleSlipAccumulator,
|
|
4
|
+
IonoAccumulator,
|
|
4
5
|
MultipathAccumulator,
|
|
5
6
|
analyzeQuality
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UQ7D53R2.js";
|
|
7
8
|
import "./chunk-3U5AX7PY.js";
|
|
8
9
|
import "./chunk-FIEWO4J4.js";
|
|
9
10
|
export {
|
|
10
11
|
CompletenessAccumulator,
|
|
11
12
|
CycleSlipAccumulator,
|
|
13
|
+
IonoAccumulator,
|
|
12
14
|
MultipathAccumulator,
|
|
13
15
|
analyzeQuality
|
|
14
16
|
};
|
|
@@ -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) {
|
|
@@ -493,11 +500,178 @@ var CompletenessAccumulator = class {
|
|
|
493
500
|
}
|
|
494
501
|
};
|
|
495
502
|
|
|
503
|
+
// src/analysis/ionosphere.ts
|
|
504
|
+
var TEC_FACTOR = 403e15;
|
|
505
|
+
var GF_JUMP_M = 0.15;
|
|
506
|
+
var MIN_ARC_LENGTH2 = 10;
|
|
507
|
+
function median2(values) {
|
|
508
|
+
const s = [...values].sort((a, b) => a - b);
|
|
509
|
+
const m = s.length >> 1;
|
|
510
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
511
|
+
}
|
|
512
|
+
var IonoAccumulator = class {
|
|
513
|
+
state = /* @__PURE__ */ new Map();
|
|
514
|
+
closed = /* @__PURE__ */ new Map();
|
|
515
|
+
interval;
|
|
516
|
+
obsIndices;
|
|
517
|
+
gloChannels;
|
|
518
|
+
pairLabel = /* @__PURE__ */ new Map();
|
|
519
|
+
constructor(header) {
|
|
520
|
+
this.interval = header.interval ?? 30;
|
|
521
|
+
this.obsIndices = buildObsIndices(header);
|
|
522
|
+
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
523
|
+
}
|
|
524
|
+
/** Observation callback — wire this into parseRinexStream. */
|
|
525
|
+
onObservation = (time, prn, _codes, values) => {
|
|
526
|
+
const sys = prn[0];
|
|
527
|
+
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
528
|
+
if (!bandMap) return;
|
|
529
|
+
const bandData = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const [band, { C, L }] of bandMap) {
|
|
531
|
+
if (C === null) continue;
|
|
532
|
+
const cVal = values[C];
|
|
533
|
+
const lVal = values[L];
|
|
534
|
+
const freq = getFreq(this.gloChannels, prn, band);
|
|
535
|
+
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
536
|
+
bandData.set(band, { C: cVal, L: lVal, f: freq });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (bandData.size < 2) return;
|
|
540
|
+
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
541
|
+
for (const [bi, bj] of pairs) {
|
|
542
|
+
const di = bandData.get(bi);
|
|
543
|
+
const dj = bandData.get(bj);
|
|
544
|
+
if (!di || !dj) continue;
|
|
545
|
+
const \u03BBi = C_LIGHT / di.f;
|
|
546
|
+
const \u03BBj = C_LIGHT / dj.f;
|
|
547
|
+
const gamma = di.f * di.f / (dj.f * dj.f);
|
|
548
|
+
const l4 = di.L * \u03BBi - dj.L * \u03BBj;
|
|
549
|
+
const p4 = dj.C - di.C;
|
|
550
|
+
const pairKey = `${bi}-${bj}`;
|
|
551
|
+
if (!this.pairLabel.has(`${sys}:${pairKey}`)) {
|
|
552
|
+
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
553
|
+
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
554
|
+
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
555
|
+
}
|
|
556
|
+
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
push(prn, pairKey, time, l4, p4, fi, gamma) {
|
|
561
|
+
if (!isFinite(l4) || !isFinite(p4)) return;
|
|
562
|
+
let satStates = this.state.get(prn);
|
|
563
|
+
if (!satStates) {
|
|
564
|
+
satStates = /* @__PURE__ */ new Map();
|
|
565
|
+
this.state.set(prn, satStates);
|
|
566
|
+
}
|
|
567
|
+
let ps = satStates.get(pairKey);
|
|
568
|
+
if (!ps) {
|
|
569
|
+
ps = {
|
|
570
|
+
arc: { times: [], l4: [], p4: [], fi, gamma },
|
|
571
|
+
lastTime: 0,
|
|
572
|
+
lastL4: 0
|
|
573
|
+
};
|
|
574
|
+
satStates.set(pairKey, ps);
|
|
575
|
+
}
|
|
576
|
+
const gap = ps.lastTime > 0 ? (time - ps.lastTime) / 1e3 : 0;
|
|
577
|
+
if (ps.lastTime > 0 && gap > this.interval * ARC_GAP_FACTOR) {
|
|
578
|
+
this.closeArc(prn, pairKey, ps);
|
|
579
|
+
} else if (ps.arc.times.length > 0 && Math.abs(l4 - ps.lastL4) > GF_JUMP_M) {
|
|
580
|
+
this.closeArc(prn, pairKey, ps);
|
|
581
|
+
}
|
|
582
|
+
if (ps.arc.times.length === 0) {
|
|
583
|
+
ps.arc.fi = fi;
|
|
584
|
+
ps.arc.gamma = gamma;
|
|
585
|
+
}
|
|
586
|
+
ps.arc.times.push(time);
|
|
587
|
+
ps.arc.l4.push(l4);
|
|
588
|
+
ps.arc.p4.push(p4);
|
|
589
|
+
ps.lastTime = time;
|
|
590
|
+
ps.lastL4 = l4;
|
|
591
|
+
}
|
|
592
|
+
/** External cycle-slip notification — close affected arcs. */
|
|
593
|
+
notifySlip(_time, prn, bands) {
|
|
594
|
+
const satStates = this.state.get(prn);
|
|
595
|
+
if (!satStates) return;
|
|
596
|
+
for (const [pairKey, ps] of satStates) {
|
|
597
|
+
const [bi, bj] = pairKey.split("-");
|
|
598
|
+
if (bands.has(bi) || bands.has(bj)) {
|
|
599
|
+
this.closeArc(prn, pairKey, ps);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
closeArc(prn, pairKey, ps) {
|
|
604
|
+
const arc = ps.arc;
|
|
605
|
+
if (arc.times.length >= MIN_ARC_LENGTH2) {
|
|
606
|
+
const level = median2(arc.l4.map((v, k) => v - arc.p4[k]));
|
|
607
|
+
const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
|
|
608
|
+
let satArcs = this.closed.get(prn);
|
|
609
|
+
if (!satArcs) {
|
|
610
|
+
satArcs = /* @__PURE__ */ new Map();
|
|
611
|
+
this.closed.set(prn, satArcs);
|
|
612
|
+
}
|
|
613
|
+
let points = satArcs.get(pairKey);
|
|
614
|
+
if (!points) {
|
|
615
|
+
points = [];
|
|
616
|
+
satArcs.set(pairKey, points);
|
|
617
|
+
}
|
|
618
|
+
for (let k = 0; k < arc.times.length; k++) {
|
|
619
|
+
points.push({
|
|
620
|
+
time: arc.times[k],
|
|
621
|
+
stec: (arc.l4[k] - level) * toTecu
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
ps.arc = { times: [], l4: [], p4: [], fi: arc.fi, gamma: arc.gamma };
|
|
626
|
+
}
|
|
627
|
+
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
628
|
+
finalize() {
|
|
629
|
+
for (const [prn, satStates] of this.state) {
|
|
630
|
+
for (const [pairKey, ps] of satStates) {
|
|
631
|
+
this.closeArc(prn, pairKey, ps);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const series = [];
|
|
635
|
+
let sum = 0;
|
|
636
|
+
let count = 0;
|
|
637
|
+
let maxStec = 0;
|
|
638
|
+
for (const [prn, satArcs] of this.closed) {
|
|
639
|
+
let bestKey = null;
|
|
640
|
+
let bestLen = 0;
|
|
641
|
+
for (const [pairKey, points2] of satArcs) {
|
|
642
|
+
if (points2.length > bestLen) {
|
|
643
|
+
bestLen = points2.length;
|
|
644
|
+
bestKey = pairKey;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (!bestKey) continue;
|
|
648
|
+
const points = satArcs.get(bestKey);
|
|
649
|
+
points.sort((a, b) => a.time - b.time);
|
|
650
|
+
const sys = prn[0];
|
|
651
|
+
for (const p of points) {
|
|
652
|
+
sum += p.stec;
|
|
653
|
+
count++;
|
|
654
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
655
|
+
}
|
|
656
|
+
series.push({
|
|
657
|
+
prn,
|
|
658
|
+
system: sys,
|
|
659
|
+
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
660
|
+
points
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
series.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
664
|
+
return { series, maxStec, meanStec: count > 0 ? sum / count : 0 };
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
496
668
|
// src/analysis/quality-analysis.ts
|
|
497
669
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
498
670
|
const mpAccum = new MultipathAccumulator(header);
|
|
671
|
+
const ionoAccum = new IonoAccumulator(header);
|
|
499
672
|
const csAccum = new CycleSlipAccumulator(header, (time, prn, bands) => {
|
|
500
673
|
mpAccum.notifySlip(time, prn, bands);
|
|
674
|
+
ionoAccum.notifySlip(time, prn, bands);
|
|
501
675
|
});
|
|
502
676
|
const compAccum = new CompletenessAccumulator(header);
|
|
503
677
|
await parseRinexStream(
|
|
@@ -507,6 +681,7 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
507
681
|
(time, prn, codes, values) => {
|
|
508
682
|
csAccum.onObservation(time, prn, codes, values);
|
|
509
683
|
mpAccum.onObservation(time, prn, codes, values);
|
|
684
|
+
ionoAccum.onObservation(time, prn, codes, values);
|
|
510
685
|
compAccum.onObservation(time, prn, codes, values);
|
|
511
686
|
},
|
|
512
687
|
true
|
|
@@ -515,7 +690,8 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
515
690
|
return {
|
|
516
691
|
cycleSlips: csAccum.finalize(),
|
|
517
692
|
completeness: compAccum.finalize(),
|
|
518
|
-
multipath: mpAccum.finalize()
|
|
693
|
+
multipath: mpAccum.finalize(),
|
|
694
|
+
iono: ionoAccum.finalize()
|
|
519
695
|
};
|
|
520
696
|
}
|
|
521
697
|
|
|
@@ -523,5 +699,6 @@ export {
|
|
|
523
699
|
MultipathAccumulator,
|
|
524
700
|
CycleSlipAccumulator,
|
|
525
701
|
CompletenessAccumulator,
|
|
702
|
+
IonoAccumulator,
|
|
526
703
|
analyzeQuality
|
|
527
704
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -67,6 +67,7 @@ __export(src_exports, {
|
|
|
67
67
|
GLO_F2_BASE: () => GLO_F2_BASE,
|
|
68
68
|
GLO_F2_STEP: () => GLO_F2_STEP,
|
|
69
69
|
GLO_F3: () => GLO_F3,
|
|
70
|
+
IonoAccumulator: () => IonoAccumulator,
|
|
70
71
|
MILLISECONDS_GPS_TAI: () => MILLISECONDS_GPS_TAI,
|
|
71
72
|
MILLISECONDS_IN_DAY: () => MILLISECONDS_IN_DAY,
|
|
72
73
|
MILLISECONDS_IN_HOUR: () => MILLISECONDS_IN_HOUR,
|
|
@@ -5078,17 +5079,24 @@ var MultipathAccumulator = class {
|
|
|
5078
5079
|
}
|
|
5079
5080
|
}
|
|
5080
5081
|
if (bandData.size < 2) return;
|
|
5082
|
+
const mpOf = (a, b) => {
|
|
5083
|
+
const \u03BBa = C_LIGHT / a.f;
|
|
5084
|
+
const \u03BBb = C_LIGHT / b.f;
|
|
5085
|
+
const \u03B1 = a.f * a.f / (b.f * b.f);
|
|
5086
|
+
const coeff = 2 / (\u03B1 - 1);
|
|
5087
|
+
return a.C - (1 + coeff) * a.L * \u03BBa + coeff * b.L * \u03BBb;
|
|
5088
|
+
};
|
|
5081
5089
|
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
5090
|
+
let primaryDone = false;
|
|
5082
5091
|
for (const [bi, bj] of pairs) {
|
|
5083
5092
|
const di = bandData.get(bi);
|
|
5084
5093
|
const dj = bandData.get(bj);
|
|
5085
5094
|
if (!di || !dj) continue;
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
this.pushMp(prn, bi, bj, time, mp);
|
|
5095
|
+
if (!primaryDone) {
|
|
5096
|
+
this.pushMp(prn, bi, bj, time, mpOf(di, dj));
|
|
5097
|
+
primaryDone = true;
|
|
5098
|
+
}
|
|
5099
|
+
this.pushMp(prn, bj, bi, time, mpOf(dj, di));
|
|
5092
5100
|
}
|
|
5093
5101
|
};
|
|
5094
5102
|
pushMp(prn, band, refBand, time, mp) {
|
|
@@ -5521,11 +5529,178 @@ var CompletenessAccumulator = class {
|
|
|
5521
5529
|
}
|
|
5522
5530
|
};
|
|
5523
5531
|
|
|
5532
|
+
// src/analysis/ionosphere.ts
|
|
5533
|
+
var TEC_FACTOR = 403e15;
|
|
5534
|
+
var GF_JUMP_M = 0.15;
|
|
5535
|
+
var MIN_ARC_LENGTH2 = 10;
|
|
5536
|
+
function median2(values) {
|
|
5537
|
+
const s = [...values].sort((a, b) => a - b);
|
|
5538
|
+
const m = s.length >> 1;
|
|
5539
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
5540
|
+
}
|
|
5541
|
+
var IonoAccumulator = class {
|
|
5542
|
+
state = /* @__PURE__ */ new Map();
|
|
5543
|
+
closed = /* @__PURE__ */ new Map();
|
|
5544
|
+
interval;
|
|
5545
|
+
obsIndices;
|
|
5546
|
+
gloChannels;
|
|
5547
|
+
pairLabel = /* @__PURE__ */ new Map();
|
|
5548
|
+
constructor(header) {
|
|
5549
|
+
this.interval = header.interval ?? 30;
|
|
5550
|
+
this.obsIndices = buildObsIndices(header);
|
|
5551
|
+
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
5552
|
+
}
|
|
5553
|
+
/** Observation callback — wire this into parseRinexStream. */
|
|
5554
|
+
onObservation = (time, prn, _codes, values) => {
|
|
5555
|
+
const sys = prn[0];
|
|
5556
|
+
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
5557
|
+
if (!bandMap) return;
|
|
5558
|
+
const bandData = /* @__PURE__ */ new Map();
|
|
5559
|
+
for (const [band, { C, L }] of bandMap) {
|
|
5560
|
+
if (C === null) continue;
|
|
5561
|
+
const cVal = values[C];
|
|
5562
|
+
const lVal = values[L];
|
|
5563
|
+
const freq = getFreq(this.gloChannels, prn, band);
|
|
5564
|
+
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
5565
|
+
bandData.set(band, { C: cVal, L: lVal, f: freq });
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
if (bandData.size < 2) return;
|
|
5569
|
+
const pairs = DUAL_FREQ_PAIRS[sys] ?? [];
|
|
5570
|
+
for (const [bi, bj] of pairs) {
|
|
5571
|
+
const di = bandData.get(bi);
|
|
5572
|
+
const dj = bandData.get(bj);
|
|
5573
|
+
if (!di || !dj) continue;
|
|
5574
|
+
const \u03BBi = C_LIGHT / di.f;
|
|
5575
|
+
const \u03BBj = C_LIGHT / dj.f;
|
|
5576
|
+
const gamma = di.f * di.f / (dj.f * dj.f);
|
|
5577
|
+
const l4 = di.L * \u03BBi - dj.L * \u03BBj;
|
|
5578
|
+
const p4 = dj.C - di.C;
|
|
5579
|
+
const pairKey = `${bi}-${bj}`;
|
|
5580
|
+
if (!this.pairLabel.has(`${sys}:${pairKey}`)) {
|
|
5581
|
+
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
5582
|
+
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
5583
|
+
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
5584
|
+
}
|
|
5585
|
+
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
5586
|
+
break;
|
|
5587
|
+
}
|
|
5588
|
+
};
|
|
5589
|
+
push(prn, pairKey, time, l4, p4, fi, gamma) {
|
|
5590
|
+
if (!isFinite(l4) || !isFinite(p4)) return;
|
|
5591
|
+
let satStates = this.state.get(prn);
|
|
5592
|
+
if (!satStates) {
|
|
5593
|
+
satStates = /* @__PURE__ */ new Map();
|
|
5594
|
+
this.state.set(prn, satStates);
|
|
5595
|
+
}
|
|
5596
|
+
let ps = satStates.get(pairKey);
|
|
5597
|
+
if (!ps) {
|
|
5598
|
+
ps = {
|
|
5599
|
+
arc: { times: [], l4: [], p4: [], fi, gamma },
|
|
5600
|
+
lastTime: 0,
|
|
5601
|
+
lastL4: 0
|
|
5602
|
+
};
|
|
5603
|
+
satStates.set(pairKey, ps);
|
|
5604
|
+
}
|
|
5605
|
+
const gap = ps.lastTime > 0 ? (time - ps.lastTime) / 1e3 : 0;
|
|
5606
|
+
if (ps.lastTime > 0 && gap > this.interval * ARC_GAP_FACTOR) {
|
|
5607
|
+
this.closeArc(prn, pairKey, ps);
|
|
5608
|
+
} else if (ps.arc.times.length > 0 && Math.abs(l4 - ps.lastL4) > GF_JUMP_M) {
|
|
5609
|
+
this.closeArc(prn, pairKey, ps);
|
|
5610
|
+
}
|
|
5611
|
+
if (ps.arc.times.length === 0) {
|
|
5612
|
+
ps.arc.fi = fi;
|
|
5613
|
+
ps.arc.gamma = gamma;
|
|
5614
|
+
}
|
|
5615
|
+
ps.arc.times.push(time);
|
|
5616
|
+
ps.arc.l4.push(l4);
|
|
5617
|
+
ps.arc.p4.push(p4);
|
|
5618
|
+
ps.lastTime = time;
|
|
5619
|
+
ps.lastL4 = l4;
|
|
5620
|
+
}
|
|
5621
|
+
/** External cycle-slip notification — close affected arcs. */
|
|
5622
|
+
notifySlip(_time, prn, bands) {
|
|
5623
|
+
const satStates = this.state.get(prn);
|
|
5624
|
+
if (!satStates) return;
|
|
5625
|
+
for (const [pairKey, ps] of satStates) {
|
|
5626
|
+
const [bi, bj] = pairKey.split("-");
|
|
5627
|
+
if (bands.has(bi) || bands.has(bj)) {
|
|
5628
|
+
this.closeArc(prn, pairKey, ps);
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
closeArc(prn, pairKey, ps) {
|
|
5633
|
+
const arc = ps.arc;
|
|
5634
|
+
if (arc.times.length >= MIN_ARC_LENGTH2) {
|
|
5635
|
+
const level = median2(arc.l4.map((v, k) => v - arc.p4[k]));
|
|
5636
|
+
const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
|
|
5637
|
+
let satArcs = this.closed.get(prn);
|
|
5638
|
+
if (!satArcs) {
|
|
5639
|
+
satArcs = /* @__PURE__ */ new Map();
|
|
5640
|
+
this.closed.set(prn, satArcs);
|
|
5641
|
+
}
|
|
5642
|
+
let points = satArcs.get(pairKey);
|
|
5643
|
+
if (!points) {
|
|
5644
|
+
points = [];
|
|
5645
|
+
satArcs.set(pairKey, points);
|
|
5646
|
+
}
|
|
5647
|
+
for (let k = 0; k < arc.times.length; k++) {
|
|
5648
|
+
points.push({
|
|
5649
|
+
time: arc.times[k],
|
|
5650
|
+
stec: (arc.l4[k] - level) * toTecu
|
|
5651
|
+
});
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
ps.arc = { times: [], l4: [], p4: [], fi: arc.fi, gamma: arc.gamma };
|
|
5655
|
+
}
|
|
5656
|
+
/** Finalize: close remaining arcs, keep one pair per satellite. */
|
|
5657
|
+
finalize() {
|
|
5658
|
+
for (const [prn, satStates] of this.state) {
|
|
5659
|
+
for (const [pairKey, ps] of satStates) {
|
|
5660
|
+
this.closeArc(prn, pairKey, ps);
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
const series = [];
|
|
5664
|
+
let sum = 0;
|
|
5665
|
+
let count = 0;
|
|
5666
|
+
let maxStec = 0;
|
|
5667
|
+
for (const [prn, satArcs] of this.closed) {
|
|
5668
|
+
let bestKey = null;
|
|
5669
|
+
let bestLen = 0;
|
|
5670
|
+
for (const [pairKey, points2] of satArcs) {
|
|
5671
|
+
if (points2.length > bestLen) {
|
|
5672
|
+
bestLen = points2.length;
|
|
5673
|
+
bestKey = pairKey;
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
if (!bestKey) continue;
|
|
5677
|
+
const points = satArcs.get(bestKey);
|
|
5678
|
+
points.sort((a, b) => a.time - b.time);
|
|
5679
|
+
const sys = prn[0];
|
|
5680
|
+
for (const p of points) {
|
|
5681
|
+
sum += p.stec;
|
|
5682
|
+
count++;
|
|
5683
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
5684
|
+
}
|
|
5685
|
+
series.push({
|
|
5686
|
+
prn,
|
|
5687
|
+
system: sys,
|
|
5688
|
+
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
5689
|
+
points
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
series.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
5693
|
+
return { series, maxStec, meanStec: count > 0 ? sum / count : 0 };
|
|
5694
|
+
}
|
|
5695
|
+
};
|
|
5696
|
+
|
|
5524
5697
|
// src/analysis/quality-analysis.ts
|
|
5525
5698
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
5526
5699
|
const mpAccum = new MultipathAccumulator(header);
|
|
5700
|
+
const ionoAccum = new IonoAccumulator(header);
|
|
5527
5701
|
const csAccum = new CycleSlipAccumulator(header, (time, prn, bands) => {
|
|
5528
5702
|
mpAccum.notifySlip(time, prn, bands);
|
|
5703
|
+
ionoAccum.notifySlip(time, prn, bands);
|
|
5529
5704
|
});
|
|
5530
5705
|
const compAccum = new CompletenessAccumulator(header);
|
|
5531
5706
|
await parseRinexStream(
|
|
@@ -5535,6 +5710,7 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
5535
5710
|
(time, prn, codes, values) => {
|
|
5536
5711
|
csAccum.onObservation(time, prn, codes, values);
|
|
5537
5712
|
mpAccum.onObservation(time, prn, codes, values);
|
|
5713
|
+
ionoAccum.onObservation(time, prn, codes, values);
|
|
5538
5714
|
compAccum.onObservation(time, prn, codes, values);
|
|
5539
5715
|
},
|
|
5540
5716
|
true
|
|
@@ -5543,7 +5719,8 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
5543
5719
|
return {
|
|
5544
5720
|
cycleSlips: csAccum.finalize(),
|
|
5545
5721
|
completeness: compAccum.finalize(),
|
|
5546
|
-
multipath: mpAccum.finalize()
|
|
5722
|
+
multipath: mpAccum.finalize(),
|
|
5723
|
+
iono: ionoAccum.finalize()
|
|
5547
5724
|
};
|
|
5548
5725
|
}
|
|
5549
5726
|
|
|
@@ -6785,6 +6962,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
6785
6962
|
GLO_F2_BASE,
|
|
6786
6963
|
GLO_F2_STEP,
|
|
6787
6964
|
GLO_F3,
|
|
6965
|
+
IonoAccumulator,
|
|
6788
6966
|
MILLISECONDS_GPS_TAI,
|
|
6789
6967
|
MILLISECONDS_IN_DAY,
|
|
6790
6968
|
MILLISECONDS_IN_HOUR,
|
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, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.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';
|
|
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, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, analyzeQuality } from './analysis.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';
|
|
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
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
CompletenessAccumulator,
|
|
8
8
|
CycleSlipAccumulator,
|
|
9
|
+
IonoAccumulator,
|
|
9
10
|
MultipathAccumulator,
|
|
10
11
|
analyzeQuality
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-UQ7D53R2.js";
|
|
12
13
|
import {
|
|
13
14
|
frequencyLabel,
|
|
14
15
|
parseAntex
|
|
@@ -293,6 +294,7 @@ export {
|
|
|
293
294
|
GLO_F2_BASE,
|
|
294
295
|
GLO_F2_STEP,
|
|
295
296
|
GLO_F3,
|
|
297
|
+
IonoAccumulator,
|
|
296
298
|
MILLISECONDS_GPS_TAI,
|
|
297
299
|
MILLISECONDS_IN_DAY,
|
|
298
300
|
MILLISECONDS_IN_HOUR,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnss-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
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,
|