gnss-js 1.4.1 → 1.5.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/README.md +46 -23
- package/dist/analysis.cjs +168 -20
- package/dist/analysis.d.cts +54 -4
- package/dist/analysis.d.ts +54 -4
- package/dist/analysis.js +7 -3
- package/dist/{chunk-PQYBTE42.js → chunk-CD5YSYCG.js} +11 -17
- package/dist/{chunk-65CQINSB.js → chunk-FGRC3PB3.js} +6 -2
- package/dist/{chunk-UQ7D53R2.js → chunk-GDGHTC3E.js} +162 -16
- package/dist/{chunk-PX7TPPTQ.js → chunk-SOJNWF5T.js} +1 -1
- package/dist/index.cjs +181 -35
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -5
- 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 +3 -3
|
@@ -13,16 +13,22 @@ import {
|
|
|
13
13
|
getFreq
|
|
14
14
|
} from "./chunk-FIEWO4J4.js";
|
|
15
15
|
|
|
16
|
-
// src/analysis/
|
|
16
|
+
// src/analysis/stats-util.ts
|
|
17
17
|
var MIN_ARC_LENGTH = 10;
|
|
18
|
-
var MP_JUMP_M = 1.25;
|
|
19
|
-
var MP_JUMP_WINDOW = 5;
|
|
20
|
-
var MP_EDIT_SIGMA = 3;
|
|
21
18
|
function median(values) {
|
|
22
19
|
const s = [...values].sort((a, b) => a - b);
|
|
23
20
|
const m = s.length >> 1;
|
|
24
21
|
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
25
22
|
}
|
|
23
|
+
function percentile(values, p) {
|
|
24
|
+
const s = [...values].sort((a, b) => a - b);
|
|
25
|
+
return s[Math.min(s.length - 1, Math.floor(p * s.length))] ?? 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/analysis/multipath.ts
|
|
29
|
+
var MP_JUMP_M = 1.25;
|
|
30
|
+
var MP_JUMP_WINDOW = 5;
|
|
31
|
+
var MP_EDIT_SIGMA = 3;
|
|
26
32
|
var MultipathAccumulator = class {
|
|
27
33
|
state = /* @__PURE__ */ new Map();
|
|
28
34
|
results = [];
|
|
@@ -503,12 +509,6 @@ var CompletenessAccumulator = class {
|
|
|
503
509
|
// src/analysis/ionosphere.ts
|
|
504
510
|
var TEC_FACTOR = 403e15;
|
|
505
511
|
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
512
|
var IonoAccumulator = class {
|
|
513
513
|
state = /* @__PURE__ */ new Map();
|
|
514
514
|
closed = /* @__PURE__ */ new Map();
|
|
@@ -516,13 +516,15 @@ var IonoAccumulator = class {
|
|
|
516
516
|
obsIndices;
|
|
517
517
|
gloChannels;
|
|
518
518
|
pairLabel = /* @__PURE__ */ new Map();
|
|
519
|
+
pairCodes = /* @__PURE__ */ new Map();
|
|
520
|
+
pairMeta = /* @__PURE__ */ new Map();
|
|
519
521
|
constructor(header) {
|
|
520
522
|
this.interval = header.interval ?? 30;
|
|
521
523
|
this.obsIndices = buildObsIndices(header);
|
|
522
524
|
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
523
525
|
}
|
|
524
526
|
/** Observation callback — wire this into parseRinexStream. */
|
|
525
|
-
onObservation = (time, prn,
|
|
527
|
+
onObservation = (time, prn, codes, values) => {
|
|
526
528
|
const sys = prn[0];
|
|
527
529
|
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
528
530
|
if (!bandMap) return;
|
|
@@ -533,7 +535,12 @@ var IonoAccumulator = class {
|
|
|
533
535
|
const lVal = values[L];
|
|
534
536
|
const freq = getFreq(this.gloChannels, prn, band);
|
|
535
537
|
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
536
|
-
bandData.set(band, {
|
|
538
|
+
bandData.set(band, {
|
|
539
|
+
C: cVal,
|
|
540
|
+
L: lVal,
|
|
541
|
+
f: freq,
|
|
542
|
+
code: codes[C] ?? `C${band}`
|
|
543
|
+
});
|
|
537
544
|
}
|
|
538
545
|
}
|
|
539
546
|
if (bandData.size < 2) return;
|
|
@@ -552,7 +559,9 @@ var IonoAccumulator = class {
|
|
|
552
559
|
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
553
560
|
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
554
561
|
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
562
|
+
this.pairCodes.set(`${sys}:${pairKey}`, [di.code, dj.code]);
|
|
555
563
|
}
|
|
564
|
+
this.pairMeta.set(`${prn}:${pairKey}`, { fi: di.f, gamma });
|
|
556
565
|
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
557
566
|
break;
|
|
558
567
|
}
|
|
@@ -602,8 +611,8 @@ var IonoAccumulator = class {
|
|
|
602
611
|
}
|
|
603
612
|
closeArc(prn, pairKey, ps) {
|
|
604
613
|
const arc = ps.arc;
|
|
605
|
-
if (arc.times.length >=
|
|
606
|
-
const level =
|
|
614
|
+
if (arc.times.length >= MIN_ARC_LENGTH) {
|
|
615
|
+
const level = median(arc.l4.map((v, k) => v - arc.p4[k]));
|
|
607
616
|
const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
|
|
608
617
|
let satArcs = this.closed.get(prn);
|
|
609
618
|
if (!satArcs) {
|
|
@@ -634,7 +643,7 @@ var IonoAccumulator = class {
|
|
|
634
643
|
const series = [];
|
|
635
644
|
let sum = 0;
|
|
636
645
|
let count = 0;
|
|
637
|
-
let maxStec =
|
|
646
|
+
let maxStec = -Infinity;
|
|
638
647
|
for (const [prn, satArcs] of this.closed) {
|
|
639
648
|
let bestKey = null;
|
|
640
649
|
let bestLen = 0;
|
|
@@ -653,18 +662,153 @@ var IonoAccumulator = class {
|
|
|
653
662
|
count++;
|
|
654
663
|
if (p.stec > maxStec) maxStec = p.stec;
|
|
655
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;
|
|
656
667
|
series.push({
|
|
657
668
|
prn,
|
|
658
669
|
system: sys,
|
|
659
670
|
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
671
|
+
codes: this.pairCodes.get(`${sys}:${bestKey}`) ?? ["", ""],
|
|
672
|
+
tecuPerNs,
|
|
660
673
|
points
|
|
661
674
|
});
|
|
662
675
|
}
|
|
663
676
|
series.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
664
|
-
return {
|
|
677
|
+
return {
|
|
678
|
+
series,
|
|
679
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
680
|
+
meanStec: count > 0 ? sum / count : 0
|
|
681
|
+
};
|
|
665
682
|
}
|
|
666
683
|
};
|
|
667
684
|
|
|
685
|
+
// src/analysis/dcb.ts
|
|
686
|
+
var PRN_RE = /^[A-Z]\d{2}$/;
|
|
687
|
+
var SVN_RE = /^[A-Z]\d{3}$/;
|
|
688
|
+
var OBS_RE = /^[CL]\d[A-Z]$/;
|
|
689
|
+
function sinexEpochMs(s, openValue) {
|
|
690
|
+
const [y, d, sec] = s.split(":").map(Number);
|
|
691
|
+
if (!y) return openValue;
|
|
692
|
+
return Date.UTC(y, 0, 1) + ((d ?? 1) - 1) * 864e5 + (sec ?? 0) * 1e3;
|
|
693
|
+
}
|
|
694
|
+
function parseSinexBiasDcb(text, epochMs) {
|
|
695
|
+
const rows = /* @__PURE__ */ new Map();
|
|
696
|
+
for (const line of text.split("\n")) {
|
|
697
|
+
if (!line.startsWith(" DSB") && !line.startsWith("DSB")) continue;
|
|
698
|
+
const t = line.trim().split(/\s+/);
|
|
699
|
+
if (t.length < 9 || !SVN_RE.test(t[1]) || !PRN_RE.test(t[2]) || !OBS_RE.test(t[3]) || !OBS_RE.test(t[4])) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const unitIdx = t.indexOf("ns");
|
|
703
|
+
if (unitIdx < 0 || unitIdx + 1 >= t.length) continue;
|
|
704
|
+
const value = parseFloat(t[unitIdx + 1]);
|
|
705
|
+
if (!isFinite(value)) continue;
|
|
706
|
+
const start = sinexEpochMs(t[5] ?? "", -Infinity);
|
|
707
|
+
const end = sinexEpochMs(t[6] ?? "", Infinity);
|
|
708
|
+
const covers = epochMs !== void 0 && epochMs >= start && epochMs < end;
|
|
709
|
+
const prn = t[2];
|
|
710
|
+
const pair = `${t[3]}-${t[4]}`;
|
|
711
|
+
let sat = rows.get(prn);
|
|
712
|
+
if (!sat) {
|
|
713
|
+
sat = /* @__PURE__ */ new Map();
|
|
714
|
+
rows.set(prn, sat);
|
|
715
|
+
}
|
|
716
|
+
const prev = sat.get(pair);
|
|
717
|
+
if (!prev || covers && !prev.covers || covers === prev.covers && end > prev.end) {
|
|
718
|
+
sat.set(pair, { value, end, covers });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const out = /* @__PURE__ */ new Map();
|
|
722
|
+
for (const [prn, sat] of rows) {
|
|
723
|
+
const m = /* @__PURE__ */ new Map();
|
|
724
|
+
for (const [pair, row] of sat) m.set(pair, row.value);
|
|
725
|
+
out.set(prn, m);
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
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 = -Infinity;
|
|
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: {
|
|
802
|
+
series: out,
|
|
803
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
804
|
+
meanStec: count > 0 ? sum / count : 0
|
|
805
|
+
},
|
|
806
|
+
satellitesCorrected: corrected,
|
|
807
|
+
satellitesMissing: [...new Set(missing)].sort(),
|
|
808
|
+
receiverDcbTecu
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
668
812
|
// src/analysis/quality-analysis.ts
|
|
669
813
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
670
814
|
const mpAccum = new MultipathAccumulator(header);
|
|
@@ -700,5 +844,7 @@ export {
|
|
|
700
844
|
CycleSlipAccumulator,
|
|
701
845
|
CompletenessAccumulator,
|
|
702
846
|
IonoAccumulator,
|
|
847
|
+
parseSinexBiasDcb,
|
|
848
|
+
applyIonoDcb,
|
|
703
849
|
analyzeQuality
|
|
704
850
|
};
|
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,
|
|
@@ -3904,6 +3906,11 @@ function rtcm3Constellation(msgType) {
|
|
|
3904
3906
|
if (msgType >= 1111 && msgType <= 1117) return "QZSS";
|
|
3905
3907
|
if (msgType >= 1121 && msgType <= 1127) return "BeiDou";
|
|
3906
3908
|
if (msgType >= 1131 && msgType <= 1137) return "NavIC";
|
|
3909
|
+
if (msgType >= 1015 && msgType <= 1017) return "GPS";
|
|
3910
|
+
if (msgType === 1030) return "GPS";
|
|
3911
|
+
if (msgType === 1031) return "GLONASS";
|
|
3912
|
+
if (msgType >= 1057 && msgType <= 1062) return "GPS";
|
|
3913
|
+
if (msgType >= 1063 && msgType <= 1068) return "GLONASS";
|
|
3907
3914
|
if (msgType === 1230) return "GLONASS";
|
|
3908
3915
|
return null;
|
|
3909
3916
|
}
|
|
@@ -3943,7 +3950,6 @@ function updateStreamStats(stats, frames, rawBytes) {
|
|
|
3943
3950
|
entry.totalBytes += frame.length + 6;
|
|
3944
3951
|
const msmEpoch = decodeMsmFull(frame);
|
|
3945
3952
|
if (msmEpoch) {
|
|
3946
|
-
const now2 = Date.now();
|
|
3947
3953
|
for (const obs of msmEpoch.observations) {
|
|
3948
3954
|
const signals = [];
|
|
3949
3955
|
let bestCn0 = 0;
|
|
@@ -3958,7 +3964,7 @@ function updateStreamStats(stats, frames, rawBytes) {
|
|
|
3958
3964
|
prn: obs.prn,
|
|
3959
3965
|
system: obs.system,
|
|
3960
3966
|
cn0: bestCn0,
|
|
3961
|
-
lastSeen:
|
|
3967
|
+
lastSeen: now,
|
|
3962
3968
|
signals
|
|
3963
3969
|
});
|
|
3964
3970
|
}
|
|
@@ -4201,20 +4207,10 @@ function invert4x4(m) {
|
|
|
4201
4207
|
return result;
|
|
4202
4208
|
}
|
|
4203
4209
|
function selectEphemeris(ephemerides, prn, timeMs) {
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
const ephTime = eph.tocDate.getTime();
|
|
4209
|
-
const dt = Math.abs(timeMs - ephTime);
|
|
4210
|
-
const maxAge = eph.system === "R" || eph.system === "S" ? 18e5 : 4 * 36e5;
|
|
4211
|
-
if (dt > maxAge) continue;
|
|
4212
|
-
if (dt < bestDt) {
|
|
4213
|
-
bestDt = dt;
|
|
4214
|
-
best = eph;
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
return best;
|
|
4210
|
+
return selectBest(
|
|
4211
|
+
ephemerides.filter((e) => e.prn === prn),
|
|
4212
|
+
timeMs
|
|
4213
|
+
);
|
|
4218
4214
|
}
|
|
4219
4215
|
function computeSatPosition(eph, timeMs) {
|
|
4220
4216
|
if (eph.system === "R" || eph.system === "S") {
|
|
@@ -4307,8 +4303,9 @@ function selectBest(ephs, timeMs) {
|
|
|
4307
4303
|
return best;
|
|
4308
4304
|
}
|
|
4309
4305
|
var GPS_EPOCH_MS = START_GPS_TIME.getTime();
|
|
4306
|
+
var MS_PER_WEEK2 = 7 * 864e5;
|
|
4310
4307
|
var BDS_EPOCH_MS = START_BDS_TIME.getTime();
|
|
4311
|
-
var GAL_EPOCH_MS = GPS_EPOCH_MS;
|
|
4308
|
+
var GAL_EPOCH_MS = GPS_EPOCH_MS + 1024 * MS_PER_WEEK2;
|
|
4312
4309
|
function ephInfoToEphemeris(info) {
|
|
4313
4310
|
const sys = info.prn.charAt(0);
|
|
4314
4311
|
if (sys === "R") {
|
|
@@ -4352,6 +4349,7 @@ function ephInfoToEphemeris(info) {
|
|
|
4352
4349
|
return null;
|
|
4353
4350
|
}
|
|
4354
4351
|
let epochMs;
|
|
4352
|
+
let week = info.week;
|
|
4355
4353
|
const tocSec = info.toc ?? info.toe;
|
|
4356
4354
|
if (sys === "C") {
|
|
4357
4355
|
epochMs = BDS_EPOCH_MS;
|
|
@@ -4359,8 +4357,10 @@ function ephInfoToEphemeris(info) {
|
|
|
4359
4357
|
epochMs = GAL_EPOCH_MS;
|
|
4360
4358
|
} else {
|
|
4361
4359
|
epochMs = GPS_EPOCH_MS;
|
|
4360
|
+
const weeksNow = (info.lastReceived - GPS_EPOCH_MS) / MS_PER_WEEK2;
|
|
4361
|
+
week += 1024 * Math.round((weeksNow - week) / 1024);
|
|
4362
4362
|
}
|
|
4363
|
-
const tocDate = new Date(epochMs +
|
|
4363
|
+
const tocDate = new Date(epochMs + week * MS_PER_WEEK2 + tocSec * 1e3);
|
|
4364
4364
|
return {
|
|
4365
4365
|
system: sys,
|
|
4366
4366
|
prn: info.prn,
|
|
@@ -4386,7 +4386,7 @@ function ephInfoToEphemeris(info) {
|
|
|
4386
4386
|
omega: info.argPerigee,
|
|
4387
4387
|
omegaDot: info.omegaDot ?? 0,
|
|
4388
4388
|
idot: info.idot ?? 0,
|
|
4389
|
-
week
|
|
4389
|
+
week,
|
|
4390
4390
|
svHealth: info.health,
|
|
4391
4391
|
tgd: 0
|
|
4392
4392
|
};
|
|
@@ -5042,16 +5042,22 @@ async function connectToMountpoint(proxyUrl, info, signal) {
|
|
|
5042
5042
|
};
|
|
5043
5043
|
}
|
|
5044
5044
|
|
|
5045
|
-
// src/analysis/
|
|
5045
|
+
// src/analysis/stats-util.ts
|
|
5046
5046
|
var MIN_ARC_LENGTH = 10;
|
|
5047
|
-
var MP_JUMP_M = 1.25;
|
|
5048
|
-
var MP_JUMP_WINDOW = 5;
|
|
5049
|
-
var MP_EDIT_SIGMA = 3;
|
|
5050
5047
|
function median(values) {
|
|
5051
5048
|
const s = [...values].sort((a, b) => a - b);
|
|
5052
5049
|
const m = s.length >> 1;
|
|
5053
5050
|
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
5054
5051
|
}
|
|
5052
|
+
function percentile(values, p) {
|
|
5053
|
+
const s = [...values].sort((a, b) => a - b);
|
|
5054
|
+
return s[Math.min(s.length - 1, Math.floor(p * s.length))] ?? 0;
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
// src/analysis/multipath.ts
|
|
5058
|
+
var MP_JUMP_M = 1.25;
|
|
5059
|
+
var MP_JUMP_WINDOW = 5;
|
|
5060
|
+
var MP_EDIT_SIGMA = 3;
|
|
5055
5061
|
var MultipathAccumulator = class {
|
|
5056
5062
|
state = /* @__PURE__ */ new Map();
|
|
5057
5063
|
results = [];
|
|
@@ -5532,12 +5538,6 @@ var CompletenessAccumulator = class {
|
|
|
5532
5538
|
// src/analysis/ionosphere.ts
|
|
5533
5539
|
var TEC_FACTOR = 403e15;
|
|
5534
5540
|
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
5541
|
var IonoAccumulator = class {
|
|
5542
5542
|
state = /* @__PURE__ */ new Map();
|
|
5543
5543
|
closed = /* @__PURE__ */ new Map();
|
|
@@ -5545,13 +5545,15 @@ var IonoAccumulator = class {
|
|
|
5545
5545
|
obsIndices;
|
|
5546
5546
|
gloChannels;
|
|
5547
5547
|
pairLabel = /* @__PURE__ */ new Map();
|
|
5548
|
+
pairCodes = /* @__PURE__ */ new Map();
|
|
5549
|
+
pairMeta = /* @__PURE__ */ new Map();
|
|
5548
5550
|
constructor(header) {
|
|
5549
5551
|
this.interval = header.interval ?? 30;
|
|
5550
5552
|
this.obsIndices = buildObsIndices(header);
|
|
5551
5553
|
this.gloChannels = buildGloChannelMap(header.glonassSlots);
|
|
5552
5554
|
}
|
|
5553
5555
|
/** Observation callback — wire this into parseRinexStream. */
|
|
5554
|
-
onObservation = (time, prn,
|
|
5556
|
+
onObservation = (time, prn, codes, values) => {
|
|
5555
5557
|
const sys = prn[0];
|
|
5556
5558
|
const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
|
|
5557
5559
|
if (!bandMap) return;
|
|
@@ -5562,7 +5564,12 @@ var IonoAccumulator = class {
|
|
|
5562
5564
|
const lVal = values[L];
|
|
5563
5565
|
const freq = getFreq(this.gloChannels, prn, band);
|
|
5564
5566
|
if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
|
|
5565
|
-
bandData.set(band, {
|
|
5567
|
+
bandData.set(band, {
|
|
5568
|
+
C: cVal,
|
|
5569
|
+
L: lVal,
|
|
5570
|
+
f: freq,
|
|
5571
|
+
code: codes[C] ?? `C${band}`
|
|
5572
|
+
});
|
|
5566
5573
|
}
|
|
5567
5574
|
}
|
|
5568
5575
|
if (bandData.size < 2) return;
|
|
@@ -5581,7 +5588,9 @@ var IonoAccumulator = class {
|
|
|
5581
5588
|
const bLabel = BAND_LABELS[sys]?.[bi] ?? bi;
|
|
5582
5589
|
const rLabel = BAND_LABELS[sys]?.[bj] ?? bj;
|
|
5583
5590
|
this.pairLabel.set(`${sys}:${pairKey}`, `${bLabel}-${rLabel}`);
|
|
5591
|
+
this.pairCodes.set(`${sys}:${pairKey}`, [di.code, dj.code]);
|
|
5584
5592
|
}
|
|
5593
|
+
this.pairMeta.set(`${prn}:${pairKey}`, { fi: di.f, gamma });
|
|
5585
5594
|
this.push(prn, pairKey, time, l4, p4, di.f, gamma);
|
|
5586
5595
|
break;
|
|
5587
5596
|
}
|
|
@@ -5631,8 +5640,8 @@ var IonoAccumulator = class {
|
|
|
5631
5640
|
}
|
|
5632
5641
|
closeArc(prn, pairKey, ps) {
|
|
5633
5642
|
const arc = ps.arc;
|
|
5634
|
-
if (arc.times.length >=
|
|
5635
|
-
const level =
|
|
5643
|
+
if (arc.times.length >= MIN_ARC_LENGTH) {
|
|
5644
|
+
const level = median(arc.l4.map((v, k) => v - arc.p4[k]));
|
|
5636
5645
|
const toTecu = arc.fi * arc.fi / TEC_FACTOR / (arc.gamma - 1);
|
|
5637
5646
|
let satArcs = this.closed.get(prn);
|
|
5638
5647
|
if (!satArcs) {
|
|
@@ -5663,7 +5672,7 @@ var IonoAccumulator = class {
|
|
|
5663
5672
|
const series = [];
|
|
5664
5673
|
let sum = 0;
|
|
5665
5674
|
let count = 0;
|
|
5666
|
-
let maxStec =
|
|
5675
|
+
let maxStec = -Infinity;
|
|
5667
5676
|
for (const [prn, satArcs] of this.closed) {
|
|
5668
5677
|
let bestKey = null;
|
|
5669
5678
|
let bestLen = 0;
|
|
@@ -5682,18 +5691,153 @@ var IonoAccumulator = class {
|
|
|
5682
5691
|
count++;
|
|
5683
5692
|
if (p.stec > maxStec) maxStec = p.stec;
|
|
5684
5693
|
}
|
|
5694
|
+
const meta = this.pairMeta.get(`${prn}:${bestKey}`);
|
|
5695
|
+
const tecuPerNs = meta ? C_LIGHT * 1e-9 / (meta.gamma - 1) * (meta.fi * meta.fi / TEC_FACTOR) : 0;
|
|
5685
5696
|
series.push({
|
|
5686
5697
|
prn,
|
|
5687
5698
|
system: sys,
|
|
5688
5699
|
label: this.pairLabel.get(`${sys}:${bestKey}`) ?? bestKey,
|
|
5700
|
+
codes: this.pairCodes.get(`${sys}:${bestKey}`) ?? ["", ""],
|
|
5701
|
+
tecuPerNs,
|
|
5689
5702
|
points
|
|
5690
5703
|
});
|
|
5691
5704
|
}
|
|
5692
5705
|
series.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
5693
|
-
return {
|
|
5706
|
+
return {
|
|
5707
|
+
series,
|
|
5708
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
5709
|
+
meanStec: count > 0 ? sum / count : 0
|
|
5710
|
+
};
|
|
5694
5711
|
}
|
|
5695
5712
|
};
|
|
5696
5713
|
|
|
5714
|
+
// src/analysis/dcb.ts
|
|
5715
|
+
var PRN_RE = /^[A-Z]\d{2}$/;
|
|
5716
|
+
var SVN_RE = /^[A-Z]\d{3}$/;
|
|
5717
|
+
var OBS_RE = /^[CL]\d[A-Z]$/;
|
|
5718
|
+
function sinexEpochMs(s, openValue) {
|
|
5719
|
+
const [y, d, sec] = s.split(":").map(Number);
|
|
5720
|
+
if (!y) return openValue;
|
|
5721
|
+
return Date.UTC(y, 0, 1) + ((d ?? 1) - 1) * 864e5 + (sec ?? 0) * 1e3;
|
|
5722
|
+
}
|
|
5723
|
+
function parseSinexBiasDcb(text, epochMs) {
|
|
5724
|
+
const rows = /* @__PURE__ */ new Map();
|
|
5725
|
+
for (const line of text.split("\n")) {
|
|
5726
|
+
if (!line.startsWith(" DSB") && !line.startsWith("DSB")) continue;
|
|
5727
|
+
const t = line.trim().split(/\s+/);
|
|
5728
|
+
if (t.length < 9 || !SVN_RE.test(t[1]) || !PRN_RE.test(t[2]) || !OBS_RE.test(t[3]) || !OBS_RE.test(t[4])) {
|
|
5729
|
+
continue;
|
|
5730
|
+
}
|
|
5731
|
+
const unitIdx = t.indexOf("ns");
|
|
5732
|
+
if (unitIdx < 0 || unitIdx + 1 >= t.length) continue;
|
|
5733
|
+
const value = parseFloat(t[unitIdx + 1]);
|
|
5734
|
+
if (!isFinite(value)) continue;
|
|
5735
|
+
const start = sinexEpochMs(t[5] ?? "", -Infinity);
|
|
5736
|
+
const end = sinexEpochMs(t[6] ?? "", Infinity);
|
|
5737
|
+
const covers = epochMs !== void 0 && epochMs >= start && epochMs < end;
|
|
5738
|
+
const prn = t[2];
|
|
5739
|
+
const pair = `${t[3]}-${t[4]}`;
|
|
5740
|
+
let sat = rows.get(prn);
|
|
5741
|
+
if (!sat) {
|
|
5742
|
+
sat = /* @__PURE__ */ new Map();
|
|
5743
|
+
rows.set(prn, sat);
|
|
5744
|
+
}
|
|
5745
|
+
const prev = sat.get(pair);
|
|
5746
|
+
if (!prev || covers && !prev.covers || covers === prev.covers && end > prev.end) {
|
|
5747
|
+
sat.set(pair, { value, end, covers });
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
const out = /* @__PURE__ */ new Map();
|
|
5751
|
+
for (const [prn, sat] of rows) {
|
|
5752
|
+
const m = /* @__PURE__ */ new Map();
|
|
5753
|
+
for (const [pair, row] of sat) m.set(pair, row.value);
|
|
5754
|
+
out.set(prn, m);
|
|
5755
|
+
}
|
|
5756
|
+
return out;
|
|
5757
|
+
}
|
|
5758
|
+
function applyIonoDcb(iono, satDcb) {
|
|
5759
|
+
const missing = [];
|
|
5760
|
+
let corrected = 0;
|
|
5761
|
+
const dcbFor = (s) => {
|
|
5762
|
+
const sat = satDcb.get(s.prn);
|
|
5763
|
+
if (!sat) return null;
|
|
5764
|
+
const fwd = sat.get(`${s.codes[0]}-${s.codes[1]}`);
|
|
5765
|
+
if (fwd !== void 0) return fwd;
|
|
5766
|
+
const rev = sat.get(`${s.codes[1]}-${s.codes[0]}`);
|
|
5767
|
+
if (rev !== void 0) return -rev;
|
|
5768
|
+
return null;
|
|
5769
|
+
};
|
|
5770
|
+
const series = iono.series.map((s) => {
|
|
5771
|
+
const dcbNs = dcbFor(s);
|
|
5772
|
+
if (dcbNs === null) {
|
|
5773
|
+
missing.push(s.prn);
|
|
5774
|
+
return s;
|
|
5775
|
+
}
|
|
5776
|
+
corrected++;
|
|
5777
|
+
const shift = dcbNs * s.tecuPerNs;
|
|
5778
|
+
return {
|
|
5779
|
+
...s,
|
|
5780
|
+
points: s.points.map((p) => ({ time: p.time, stec: p.stec + shift }))
|
|
5781
|
+
};
|
|
5782
|
+
});
|
|
5783
|
+
const missingSet = new Set(missing);
|
|
5784
|
+
const receiverDcbTecu = {};
|
|
5785
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5786
|
+
for (const s of series) {
|
|
5787
|
+
const key = `${s.system} ${s.label}`;
|
|
5788
|
+
let g = groups.get(key);
|
|
5789
|
+
if (!g) {
|
|
5790
|
+
g = [];
|
|
5791
|
+
groups.set(key, g);
|
|
5792
|
+
}
|
|
5793
|
+
g.push(s);
|
|
5794
|
+
}
|
|
5795
|
+
const out = [];
|
|
5796
|
+
for (const [key, group] of groups) {
|
|
5797
|
+
const samples = [];
|
|
5798
|
+
for (const s of group) {
|
|
5799
|
+
if (missingSet.has(s.prn)) continue;
|
|
5800
|
+
for (const p of s.points) samples.push(p.stec);
|
|
5801
|
+
}
|
|
5802
|
+
if (samples.length >= 100) {
|
|
5803
|
+
const rx = percentile(samples, 0.01);
|
|
5804
|
+
receiverDcbTecu[key] = rx;
|
|
5805
|
+
for (const s of group) {
|
|
5806
|
+
out.push({
|
|
5807
|
+
...s,
|
|
5808
|
+
points: s.points.map((p) => ({
|
|
5809
|
+
time: p.time,
|
|
5810
|
+
stec: p.stec - rx
|
|
5811
|
+
}))
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5814
|
+
} else {
|
|
5815
|
+
out.push(...group);
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
out.sort((a, b) => a.prn.localeCompare(b.prn));
|
|
5819
|
+
let sum = 0;
|
|
5820
|
+
let count = 0;
|
|
5821
|
+
let maxStec = -Infinity;
|
|
5822
|
+
for (const s of out) {
|
|
5823
|
+
for (const p of s.points) {
|
|
5824
|
+
sum += p.stec;
|
|
5825
|
+
count++;
|
|
5826
|
+
if (p.stec > maxStec) maxStec = p.stec;
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
return {
|
|
5830
|
+
result: {
|
|
5831
|
+
series: out,
|
|
5832
|
+
maxStec: count > 0 ? maxStec : 0,
|
|
5833
|
+
meanStec: count > 0 ? sum / count : 0
|
|
5834
|
+
},
|
|
5835
|
+
satellitesCorrected: corrected,
|
|
5836
|
+
satellitesMissing: [...new Set(missing)].sort(),
|
|
5837
|
+
receiverDcbTecu
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5697
5841
|
// src/analysis/quality-analysis.ts
|
|
5698
5842
|
async function analyzeQuality(file, header, onProgress, signal) {
|
|
5699
5843
|
const mpAccum = new MultipathAccumulator(header);
|
|
@@ -7000,6 +7144,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
7000
7144
|
WarningAccumulator,
|
|
7001
7145
|
analyzeQuality,
|
|
7002
7146
|
applyHelmert,
|
|
7147
|
+
applyIonoDcb,
|
|
7003
7148
|
buildGloChannelMap,
|
|
7004
7149
|
buildObsIndices,
|
|
7005
7150
|
clampUnit,
|
|
@@ -7102,6 +7247,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
|
|
|
7102
7247
|
parseNavFile,
|
|
7103
7248
|
parseNmeaFile,
|
|
7104
7249
|
parseRinexStream,
|
|
7250
|
+
parseSinexBiasDcb,
|
|
7105
7251
|
parseSourcetable,
|
|
7106
7252
|
phiAltBOC,
|
|
7107
7253
|
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';
|