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 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
- const \u03BBi = C_LIGHT / di.f;
260
- const \u03BBj = C_LIGHT / dj.f;
261
- const \u03B1 = di.f * di.f / (dj.f * dj.f);
262
- const coeff = 2 / (\u03B1 - 1);
263
- const mp = di.C - (1 + coeff) * di.L * \u03BBi + coeff * dj.L * \u03BBj;
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, _codes, values) => {
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, { C: cVal, L: lVal, f: freq });
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
  });
@@ -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, _codes: string[], values: (number | null)[]) => void;
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 };
@@ -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, _codes: string[], values: (number | null)[]) => void;
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
- } from "./chunk-3ZAFWOT6.js";
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
- const \u03BBi = C_LIGHT / di.f;
59
- const \u03BBj = C_LIGHT / dj.f;
60
- const \u03B1 = di.f * di.f / (dj.f * dj.f);
61
- const coeff = 2 / (\u03B1 - 1);
62
- const mp = di.C - (1 + coeff) * di.L * \u03BBi + coeff * dj.L * \u03BBj;
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, _codes, values) => {
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, { C: cVal, L: lVal, f: freq });
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
- const \u03BBi = C_LIGHT / di.f;
5088
- const \u03BBj = C_LIGHT / dj.f;
5089
- const \u03B1 = di.f * di.f / (dj.f * dj.f);
5090
- const coeff = 2 / (\u03B1 - 1);
5091
- const mp = di.C - (1 + coeff) * di.L * \u03BBi + coeff * dj.L * \u03BBj;
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, _codes, values) => {
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, { C: cVal, L: lVal, f: freq });
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
- } from "./chunk-3ZAFWOT6.js";
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.4.0",
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,