gnss-js 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,7 @@ Everything is exposed through subpath exports (`gnss-js/rinex`, `gnss-js/positio
17
17
  - **Formats in** — RINEX 2/3/4 obs + nav (plain, Hatanaka `.crx`, `.gz`), SP3, RINEX CLK, IONEX, SINEX bias; RTCM3 (MSM + legacy obs, ephemeris, SSR/IGS-SSR, station); RTCM 2.x; and the receiver binaries SBF, UBX, NovAtel OEM4, Trimble, BINEX.
18
18
  - **Formats out** — RINEX 2/3/4 observation + navigation writers and Hatanaka (CRINEX) compression.
19
19
  - **Navigation** — Keplerian/GLONASS ephemeris handling plus raw-frame decoding of GPS LNAV/CNAV, Galileo I/NAV + F/NAV, BeiDou D1/D2 and B-CNAV1/2/3 (B1C/B2a/B2b), GLONASS strings, and Galileo HAS (E6 C/NAV) corrections.
20
+ - **Authentication** — Galileo OSNMA: the full chain of trust from the Merkle root through ECDSA-signed TESLA keys to per-satellite MAC tags, flagging navigation data Galileo did not sign.
20
21
  - **Orbits** — satellite position/velocity, Doppler, DOP, azimuth/elevation, pass prediction and almanac propagation.
21
22
  - **Positioning** — SPP, static and kinematic float PPP, PPP-AR (LAMBDA + wide-/narrow-lane FCB estimation), post-processed RTK, DGNSS, and SBAS/DFMC augmentation.
22
23
  - **Quality** — multipath, cycle slips, completeness and slant-TEC ionosphere analysis, with SINEX-DCB calibration.
@@ -99,6 +100,20 @@ const nav = parseSbfNav(bytes); // GPS/GAL/BDS/GLO ephemeris from raw blocks
99
100
  const has = parseSbfHas(bytes); // Galileo HAS (E6 C/NAV) SSR corrections
100
101
  ```
101
102
 
103
+ ### Galileo OSNMA (navigation message authentication)
104
+
105
+ `OsnmaReceiver` takes Galileo E1-B I/NAV pages and verifies them the way the OSNMA SIS ICD v1.1 prescribes: public key against the Merkle root (built in, or broadcast in DSM-PKR), the DSM-KROOT's ECDSA signature, every TESLA chain key back to the root key, and the MAC tags over each satellite's ephemeris/clock (ADKD 0/12) and timing parameters (ADKD 4). A tag that fails means the data differ from what Galileo signed. Validated against the official ICD test vectors and live receivers.
106
+
107
+ ```ts
108
+ import { OsnmaReceiver, verifySbfOsnma } from 'gnss-js/osnma';
109
+
110
+ const s = verifySbfOsnma(sbfBytes); // whole capture
111
+ s.chains; // verified TESLA chains; s.tags → { ok, failed, ... }
112
+ s.satellites; // per PRN: ced.authenticatedIodNav, timing.lastAuthenticated, failures
113
+
114
+ const rx = new OsnmaReceiver(); // live: rx.pushPage(svid, wn, tow, page); rx.status()
115
+ ```
116
+
102
117
  ### RTCM (RTCM3, RTCM2, SSR)
103
118
 
104
119
  ```ts
package/dist/analysis.cjs CHANGED
@@ -1113,20 +1113,14 @@ function applyIonoDcb(iono, satDcb) {
1113
1113
  // src/rinex/crx.ts
1114
1114
  function crxRepair(old, diff) {
1115
1115
  const chars = old.split("");
1116
- for (let i = 0; i < diff.length; i++) {
1116
+ const n = Math.min(old.length, diff.length);
1117
+ for (let i = 0; i < n; i++) {
1117
1118
  const dc = diff[i];
1118
- if (dc === " ") {
1119
- } else if (dc === "&") {
1120
- chars[i] = " ";
1121
- } else {
1122
- chars[i] = dc;
1123
- }
1124
- }
1125
- if (diff.length > chars.length) {
1126
- for (let i = chars.length; i < diff.length; i++) {
1127
- chars[i] = diff[i] === "&" ? " " : diff[i];
1128
- }
1119
+ if (dc === " ") continue;
1120
+ chars[i] = dc === "&" ? " " : dc;
1129
1121
  }
1122
+ for (let i = old.length; i < diff.length; i++)
1123
+ chars[i] = diff[i] === "&" ? " " : diff[i];
1130
1124
  return chars.join("");
1131
1125
  }
1132
1126
  function parseCrxDataLine(line, ntype) {
@@ -1371,6 +1365,15 @@ function readObsValue(line, index) {
1371
1365
  const v = parseFloat(raw);
1372
1366
  return isNaN(v) ? null : v;
1373
1367
  }
1368
+ function normalizePrn(id) {
1369
+ let sys = id[0];
1370
+ let num = id.substring(1);
1371
+ if (sys === " " || /\d/.test(sys)) {
1372
+ num = id;
1373
+ sys = "G";
1374
+ }
1375
+ return sys + num.replace(/\s/g, "").padStart(2, "0");
1376
+ }
1374
1377
  function parseEpochLine3(line) {
1375
1378
  if (line[0] !== ">") return null;
1376
1379
  const yr = parseInt(line.substring(2, 6));
@@ -1520,7 +1523,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
1520
1523
  function processSatLineV3(line) {
1521
1524
  if (!header || line.length < 3) return;
1522
1525
  const sys = line[0];
1523
- const prn = line.substring(0, 3);
1526
+ const prn = normalizePrn(line.substring(0, 3));
1524
1527
  trackSat(sys, prn);
1525
1528
  const obsLine = line.substring(3);
1526
1529
  const codes = header.obsTypes[sys] ?? [];
@@ -1545,10 +1548,8 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
1545
1548
  let v2ObsAccum = [];
1546
1549
  function processSatLineV2(line) {
1547
1550
  if (!header || v2SatIndex >= v2SatIds.length) return;
1548
- const satId = v2SatIds[v2SatIndex];
1549
- let sys = satId[0];
1550
- if (sys === " " || /\d/.test(sys)) sys = "G";
1551
- const prn = sys + satId.substring(1).padStart(2, "0");
1551
+ const prn = normalizePrn(v2SatIds[v2SatIndex]);
1552
+ const sys = prn[0];
1552
1553
  if (v2CurrentSatLine === 0) {
1553
1554
  trackSat(sys, prn);
1554
1555
  if (onSatObs) v2ObsAccum = [];
@@ -1639,6 +1640,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
1639
1640
  } else {
1640
1641
  epochLine = crxRepair(crxPrevEpochLine, line);
1641
1642
  }
1643
+ epochLine = epochLine.replace(/ +$/, "");
1642
1644
  if (isV3) {
1643
1645
  if (epochLine[0] !== ">") return;
1644
1646
  const info = parseEpochLine3(epochLine);
@@ -1656,10 +1658,11 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
1656
1658
  return;
1657
1659
  }
1658
1660
  resetEpochAccum(info);
1659
- const satPart = epochLine.substring(41).replace(/\s+$/, "");
1660
1661
  crxSatList = [];
1661
- for (let i = 0; i + 3 <= satPart.length; i += 3) {
1662
- crxSatList.push(satPart.substring(i, i + 3));
1662
+ for (let k = 0; k < info.numSats; k++) {
1663
+ crxSatList.push(
1664
+ normalizePrn(epochLine.substring(41 + 3 * k, 44 + 3 * k))
1665
+ );
1663
1666
  }
1664
1667
  crxSatIndex = 0;
1665
1668
  crxPrevEpochLine = epochLine;
@@ -1680,11 +1683,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
1680
1683
  return;
1681
1684
  }
1682
1685
  resetEpochAccum(info);
1683
- crxSatList = (info.satIds ?? []).map((id) => {
1684
- let sys = id[0];
1685
- if (sys === " " || /\d/.test(sys)) sys = "G";
1686
- return sys + id.substring(1).padStart(2, "0");
1687
- });
1686
+ crxSatList = (info.satIds ?? []).slice(0, info.numSats).map(normalizePrn);
1688
1687
  crxSatIndex = 0;
1689
1688
  crxPrevEpochLine = epochLine;
1690
1689
  crxPhase = "clock";
package/dist/analysis.js CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  computeIonoRate,
9
9
  detrendIonoArcs,
10
10
  parseSinexBiasDcb
11
- } from "./chunk-KUSUN7DP.js";
12
- import "./chunk-3U5AX7PY.js";
11
+ } from "./chunk-MFCPYUEI.js";
12
+ import "./chunk-FEABDV5P.js";
13
13
  import "./chunk-FIEWO4J4.js";
14
14
  export {
15
15
  CompletenessAccumulator,
@@ -93,4 +93,69 @@ declare function parseClk(text: string): ClkFile;
93
93
  */
94
94
  declare function clkBias(clk: ClkFile, prn: string, tMs: number): number | null;
95
95
 
96
- export { type ClkFile as C, type IonexGrid as I, type Sp3File as S, type Sp3Sample as a, parseIonex as b, clkBias as c, parseSp3 as d, parseClk as p, sp3Position as s };
96
+ /**
97
+ * Bias-SINEX (`.BIA`) parser — GNSS code/phase biases published by the IGS
98
+ * analysis centres. Two record kinds matter here:
99
+ *
100
+ * - **DSB** differential signal bias (OBS1−OBS2), the classic inter-code
101
+ * bias used for ionosphere/DCB corrections in SPP.
102
+ * - **OSB** observable-specific (absolute) bias for a single OBS, the
103
+ * ingredient PPP-AR needs: the *phase* OSBs are the satellite
104
+ * fractional-cycle biases that make the ambiguities integer-recoverable.
105
+ *
106
+ * Values keep their file unit (`ns` for code/most phase biases, `cyc` for
107
+ * cycle-based phase biases); `biasMetres` converts ns → m via c.
108
+ */
109
+ interface BiasRecord {
110
+ /** 'DSB' | 'OSB' | 'ISB' | … */
111
+ type: string;
112
+ /** Satellite PRN (e.g. 'G01', 'E11'); '' for a pure station bias. */
113
+ prn: string;
114
+ /** Station (9-char), '' for satellite biases. */
115
+ station: string;
116
+ /** First observable code (e.g. 'C1W', 'L1C'). */
117
+ obs1: string;
118
+ /** Second observable (DSB only; '' for OSB). */
119
+ obs2: string;
120
+ /** Validity start / end (GPS-scale ms); ±Infinity when open. */
121
+ startMs: number;
122
+ endMs: number;
123
+ /** Estimated value in the file unit. */
124
+ value: number;
125
+ /** File unit ('ns' | 'cyc' | …). */
126
+ unit: string;
127
+ }
128
+ interface BiasSinex {
129
+ records: BiasRecord[];
130
+ /** All satellite records grouped by PRN (station === ''). */
131
+ bySat: Map<string, BiasRecord[]>;
132
+ }
133
+ /** Parse a Bias-SINEX file. */
134
+ declare function parseBiasSinex(text: string): BiasSinex;
135
+ /** A satellite bias record's value in metres (ns → m via c; cyc left as-is). */
136
+ declare function biasMetres(r: BiasRecord): number;
137
+ /**
138
+ * Look up a satellite bias: a DSB by its (obs1,obs2) pair, or an OSB by obs1
139
+ * alone (pass obs2 = undefined), valid at `tMs` (default: any). Returns the
140
+ * record or null.
141
+ */
142
+ declare function findSatBias(bias: BiasSinex, prn: string, obs1: string, obs2?: string, tMs?: number): BiasRecord | null;
143
+ /**
144
+ * Fast time-aware lookup of satellite OSBs (metres) — the form PPP needs.
145
+ * Products with integer-recoverable clocks (CNES/GFZ "ABS", CODE) publish
146
+ * phase OSBs that change every 30 s–15 min, so one PRN/observable can carry
147
+ * thousands of records; they're sorted once and found by binary search.
148
+ * Values are as published (observed = true + bias), so the corrected
149
+ * observable is `observed − osb(...)`.
150
+ */
151
+ declare class SatOsbTable {
152
+ private readonly byKey;
153
+ constructor(bias: BiasSinex | BiasRecord[]);
154
+ /** Whether any phase (L…) OSB is present — i.e. the product supports AR. */
155
+ get hasPhase(): boolean;
156
+ /** OSB (m) of `prn`'s observable `obs` (e.g. 'C1W', 'L2W') at `tMs`;
157
+ * null when the product has none valid then. */
158
+ get(prn: string, obs: string, tMs: number): number | null;
159
+ }
160
+
161
+ export { type BiasRecord as B, type ClkFile as C, type IonexGrid as I, SatOsbTable as S, type BiasSinex as a, type Sp3File as b, type Sp3Sample as c, biasMetres as d, clkBias as e, findSatBias as f, parseClk as g, parseIonex as h, parseSp3 as i, parseBiasSinex as p, sp3Position as s };
@@ -93,4 +93,69 @@ declare function parseClk(text: string): ClkFile;
93
93
  */
94
94
  declare function clkBias(clk: ClkFile, prn: string, tMs: number): number | null;
95
95
 
96
- export { type ClkFile as C, type IonexGrid as I, type Sp3File as S, type Sp3Sample as a, parseIonex as b, clkBias as c, parseSp3 as d, parseClk as p, sp3Position as s };
96
+ /**
97
+ * Bias-SINEX (`.BIA`) parser — GNSS code/phase biases published by the IGS
98
+ * analysis centres. Two record kinds matter here:
99
+ *
100
+ * - **DSB** differential signal bias (OBS1−OBS2), the classic inter-code
101
+ * bias used for ionosphere/DCB corrections in SPP.
102
+ * - **OSB** observable-specific (absolute) bias for a single OBS, the
103
+ * ingredient PPP-AR needs: the *phase* OSBs are the satellite
104
+ * fractional-cycle biases that make the ambiguities integer-recoverable.
105
+ *
106
+ * Values keep their file unit (`ns` for code/most phase biases, `cyc` for
107
+ * cycle-based phase biases); `biasMetres` converts ns → m via c.
108
+ */
109
+ interface BiasRecord {
110
+ /** 'DSB' | 'OSB' | 'ISB' | … */
111
+ type: string;
112
+ /** Satellite PRN (e.g. 'G01', 'E11'); '' for a pure station bias. */
113
+ prn: string;
114
+ /** Station (9-char), '' for satellite biases. */
115
+ station: string;
116
+ /** First observable code (e.g. 'C1W', 'L1C'). */
117
+ obs1: string;
118
+ /** Second observable (DSB only; '' for OSB). */
119
+ obs2: string;
120
+ /** Validity start / end (GPS-scale ms); ±Infinity when open. */
121
+ startMs: number;
122
+ endMs: number;
123
+ /** Estimated value in the file unit. */
124
+ value: number;
125
+ /** File unit ('ns' | 'cyc' | …). */
126
+ unit: string;
127
+ }
128
+ interface BiasSinex {
129
+ records: BiasRecord[];
130
+ /** All satellite records grouped by PRN (station === ''). */
131
+ bySat: Map<string, BiasRecord[]>;
132
+ }
133
+ /** Parse a Bias-SINEX file. */
134
+ declare function parseBiasSinex(text: string): BiasSinex;
135
+ /** A satellite bias record's value in metres (ns → m via c; cyc left as-is). */
136
+ declare function biasMetres(r: BiasRecord): number;
137
+ /**
138
+ * Look up a satellite bias: a DSB by its (obs1,obs2) pair, or an OSB by obs1
139
+ * alone (pass obs2 = undefined), valid at `tMs` (default: any). Returns the
140
+ * record or null.
141
+ */
142
+ declare function findSatBias(bias: BiasSinex, prn: string, obs1: string, obs2?: string, tMs?: number): BiasRecord | null;
143
+ /**
144
+ * Fast time-aware lookup of satellite OSBs (metres) — the form PPP needs.
145
+ * Products with integer-recoverable clocks (CNES/GFZ "ABS", CODE) publish
146
+ * phase OSBs that change every 30 s–15 min, so one PRN/observable can carry
147
+ * thousands of records; they're sorted once and found by binary search.
148
+ * Values are as published (observed = true + bias), so the corrected
149
+ * observable is `observed − osb(...)`.
150
+ */
151
+ declare class SatOsbTable {
152
+ private readonly byKey;
153
+ constructor(bias: BiasSinex | BiasRecord[]);
154
+ /** Whether any phase (L…) OSB is present — i.e. the product supports AR. */
155
+ get hasPhase(): boolean;
156
+ /** OSB (m) of `prn`'s observable `obs` (e.g. 'C1W', 'L2W') at `tMs`;
157
+ * null when the product has none valid then. */
158
+ get(prn: string, obs: string, tMs: number): number | null;
159
+ }
160
+
161
+ export { type BiasRecord as B, type ClkFile as C, type IonexGrid as I, SatOsbTable as S, type BiasSinex as a, type Sp3File as b, type Sp3Sample as c, biasMetres as d, clkBias as e, findSatBias as f, parseClk as g, parseIonex as h, parseSp3 as i, parseBiasSinex as p, sp3Position as s };
@@ -1617,6 +1617,50 @@ function findSatBias(bias, prn, obs1, obs2, tMs) {
1617
1617
  }
1618
1618
  return null;
1619
1619
  }
1620
+ var SatOsbTable = class {
1621
+ byKey = /* @__PURE__ */ new Map();
1622
+ constructor(bias) {
1623
+ const recs = Array.isArray(bias) ? bias : bias.records;
1624
+ const groups = /* @__PURE__ */ new Map();
1625
+ for (const r of recs) {
1626
+ if (r.type !== "OSB" || !r.prn || r.station) continue;
1627
+ if (r.unit !== "ns") continue;
1628
+ const k = `${r.prn} ${r.obs1}`;
1629
+ let g = groups.get(k);
1630
+ if (!g) groups.set(k, g = []);
1631
+ g.push(r);
1632
+ }
1633
+ for (const [k, g] of groups) {
1634
+ g.sort((a, b) => a.startMs - b.startMs);
1635
+ this.byKey.set(k, {
1636
+ start: Float64Array.from(g, (r) => r.startMs),
1637
+ end: Float64Array.from(g, (r) => r.endMs),
1638
+ m: Float64Array.from(g, biasMetres)
1639
+ });
1640
+ }
1641
+ }
1642
+ /** Whether any phase (L…) OSB is present — i.e. the product supports AR. */
1643
+ get hasPhase() {
1644
+ for (const k of this.byKey.keys())
1645
+ if (k.split(" ")[1][0] === "L") return true;
1646
+ return false;
1647
+ }
1648
+ /** OSB (m) of `prn`'s observable `obs` (e.g. 'C1W', 'L2W') at `tMs`;
1649
+ * null when the product has none valid then. */
1650
+ get(prn, obs, tMs) {
1651
+ const e = this.byKey.get(`${prn} ${obs}`) ?? this.byKey.get(`${prn} ${obs.slice(0, 2)}X`);
1652
+ if (!e) return null;
1653
+ let lo = 0;
1654
+ let hi = e.start.length - 1;
1655
+ if (tMs < e.start[0]) return null;
1656
+ while (lo < hi) {
1657
+ const mid = lo + hi + 1 >> 1;
1658
+ if (e.start[mid] <= tMs) lo = mid;
1659
+ else hi = mid - 1;
1660
+ }
1661
+ return tMs <= e.end[lo] ? e.m[lo] : null;
1662
+ }
1663
+ };
1620
1664
 
1621
1665
  // src/rinex/obs-writer-common.ts
1622
1666
  function createGzipLineSink() {
@@ -2021,6 +2065,7 @@ export {
2021
2065
  parseBiasSinex,
2022
2066
  biasMetres,
2023
2067
  findSatBias,
2068
+ SatOsbTable,
2024
2069
  createGzipLineSink,
2025
2070
  stationHeaderLines,
2026
2071
  writeRinexObsBlob,
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  BdsAssembler,
3
- GalInavAssembler,
4
3
  GloStringAssembler,
5
4
  bdsSubframeParityOk,
6
- galInavPageCrcOk,
7
5
  testGloString
8
- } from "./chunk-SSWFX3P3.js";
6
+ } from "./chunk-NIJ6WFQJ.js";
7
+ import {
8
+ GalInavAssembler,
9
+ galInavPageCrcOk
10
+ } from "./chunk-TWGZTRBL.js";
9
11
  import {
10
12
  decodeSbasGeoNav,
11
13
  sbasCrcOk
@@ -0,0 +1,65 @@
1
+ // src/sbf/frame.ts
2
+ var CRC_TABLE = (() => {
3
+ const t = new Uint16Array(256);
4
+ for (let n = 0; n < 256; n++) {
5
+ let c = n << 8;
6
+ for (let k = 0; k < 8; k++)
7
+ c = c & 32768 ? (c << 1 ^ 4129) & 65535 : c << 1 & 65535;
8
+ t[n] = c;
9
+ }
10
+ return t;
11
+ })();
12
+ function crc16(data, start, end) {
13
+ let crc = 0;
14
+ for (let i = start; i < end; i++)
15
+ crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
16
+ return crc;
17
+ }
18
+ function scanSbfFrames(data, view, onBlock) {
19
+ let badCrc = 0;
20
+ let i = 0;
21
+ while (i + 8 <= data.length) {
22
+ if (data[i] !== 36 || data[i + 1] !== 64) {
23
+ i++;
24
+ continue;
25
+ }
26
+ const len = view.getUint16(i + 6, true);
27
+ if (len < 8 || len % 4 !== 0) {
28
+ i++;
29
+ continue;
30
+ }
31
+ if (i + len > data.length) break;
32
+ if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
33
+ badCrc++;
34
+ i++;
35
+ continue;
36
+ }
37
+ onBlock(view.getUint16(i + 4, true) & 8191, i, len);
38
+ i += len;
39
+ }
40
+ return badCrc;
41
+ }
42
+ var two = (n) => String(n).padStart(2, "0");
43
+ function svidToPrn(svid) {
44
+ if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
45
+ if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
46
+ if (svid <= 62) return null;
47
+ if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
48
+ if (svid <= 70) return null;
49
+ if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
50
+ if (svid <= 119) return null;
51
+ if (svid <= 140) return `S${two(svid - 100)}`;
52
+ if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
53
+ if (svid <= 190) return `J${two(svid - 180)}`;
54
+ if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
55
+ if (svid <= 215) return `S${two(svid - 157)}`;
56
+ if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
57
+ if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
58
+ return null;
59
+ }
60
+
61
+ export {
62
+ crc16,
63
+ scanSbfFrames,
64
+ svidToPrn
65
+ };
@@ -5,20 +5,14 @@ import {
5
5
  // src/rinex/crx.ts
6
6
  function crxRepair(old, diff) {
7
7
  const chars = old.split("");
8
- for (let i = 0; i < diff.length; i++) {
8
+ const n = Math.min(old.length, diff.length);
9
+ for (let i = 0; i < n; i++) {
9
10
  const dc = diff[i];
10
- if (dc === " ") {
11
- } else if (dc === "&") {
12
- chars[i] = " ";
13
- } else {
14
- chars[i] = dc;
15
- }
16
- }
17
- if (diff.length > chars.length) {
18
- for (let i = chars.length; i < diff.length; i++) {
19
- chars[i] = diff[i] === "&" ? " " : diff[i];
20
- }
11
+ if (dc === " ") continue;
12
+ chars[i] = dc === "&" ? " " : dc;
21
13
  }
14
+ for (let i = old.length; i < diff.length; i++)
15
+ chars[i] = diff[i] === "&" ? " " : diff[i];
22
16
  return chars.join("");
23
17
  }
24
18
  function parseCrxDataLine(line, ntype) {
@@ -266,6 +260,15 @@ function readObsValue(line, index) {
266
260
  const v = parseFloat(raw);
267
261
  return isNaN(v) ? null : v;
268
262
  }
263
+ function normalizePrn(id) {
264
+ let sys = id[0];
265
+ let num = id.substring(1);
266
+ if (sys === " " || /\d/.test(sys)) {
267
+ num = id;
268
+ sys = "G";
269
+ }
270
+ return sys + num.replace(/\s/g, "").padStart(2, "0");
271
+ }
269
272
  function parseEpochLine3(line) {
270
273
  if (line[0] !== ">") return null;
271
274
  const yr = parseInt(line.substring(2, 6));
@@ -415,7 +418,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
415
418
  function processSatLineV3(line) {
416
419
  if (!header || line.length < 3) return;
417
420
  const sys = line[0];
418
- const prn = line.substring(0, 3);
421
+ const prn = normalizePrn(line.substring(0, 3));
419
422
  trackSat(sys, prn);
420
423
  const obsLine = line.substring(3);
421
424
  const codes = header.obsTypes[sys] ?? [];
@@ -440,10 +443,8 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
440
443
  let v2ObsAccum = [];
441
444
  function processSatLineV2(line) {
442
445
  if (!header || v2SatIndex >= v2SatIds.length) return;
443
- const satId = v2SatIds[v2SatIndex];
444
- let sys = satId[0];
445
- if (sys === " " || /\d/.test(sys)) sys = "G";
446
- const prn = sys + satId.substring(1).padStart(2, "0");
446
+ const prn = normalizePrn(v2SatIds[v2SatIndex]);
447
+ const sys = prn[0];
447
448
  if (v2CurrentSatLine === 0) {
448
449
  trackSat(sys, prn);
449
450
  if (onSatObs) v2ObsAccum = [];
@@ -534,6 +535,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
534
535
  } else {
535
536
  epochLine = crxRepair(crxPrevEpochLine, line);
536
537
  }
538
+ epochLine = epochLine.replace(/ +$/, "");
537
539
  if (isV3) {
538
540
  if (epochLine[0] !== ">") return;
539
541
  const info = parseEpochLine3(epochLine);
@@ -551,10 +553,11 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
551
553
  return;
552
554
  }
553
555
  resetEpochAccum(info);
554
- const satPart = epochLine.substring(41).replace(/\s+$/, "");
555
556
  crxSatList = [];
556
- for (let i = 0; i + 3 <= satPart.length; i += 3) {
557
- crxSatList.push(satPart.substring(i, i + 3));
557
+ for (let k = 0; k < info.numSats; k++) {
558
+ crxSatList.push(
559
+ normalizePrn(epochLine.substring(41 + 3 * k, 44 + 3 * k))
560
+ );
558
561
  }
559
562
  crxSatIndex = 0;
560
563
  crxPrevEpochLine = epochLine;
@@ -575,11 +578,7 @@ async function parseRinexStream(file, onProgress, signal, onSatObs, workerMode)
575
578
  return;
576
579
  }
577
580
  resetEpochAccum(info);
578
- crxSatList = (info.satIds ?? []).map((id) => {
579
- let sys = id[0];
580
- if (sys === " " || /\d/.test(sys)) sys = "G";
581
- return sys + id.substring(1).padStart(2, "0");
582
- });
581
+ crxSatList = (info.satIds ?? []).slice(0, info.numSats).map(normalizePrn);
583
582
  crxSatIndex = 0;
584
583
  crxPrevEpochLine = epochLine;
585
584
  crxPhase = "clock";
@@ -1,13 +1,19 @@
1
+ import {
2
+ scanSbfFrames,
3
+ svidToPrn
4
+ } from "./chunk-E3Y6YKQM.js";
1
5
  import {
2
6
  BdsAssembler,
3
- GalFnavAssembler,
4
- GalInavAssembler,
5
7
  GloStringAssembler,
6
8
  bdsSubframeParityOk,
7
- galFnavPageCrcOk,
8
- galInavPageCrcOk,
9
9
  testGloString
10
- } from "./chunk-SSWFX3P3.js";
10
+ } from "./chunk-NIJ6WFQJ.js";
11
+ import {
12
+ GalFnavAssembler,
13
+ GalInavAssembler,
14
+ galFnavPageCrcOk,
15
+ galInavPageCrcOk
16
+ } from "./chunk-TWGZTRBL.js";
11
17
  import {
12
18
  decodeSbasGeoNav,
13
19
  sbasCrcOk
@@ -40,66 +46,6 @@ import {
40
46
  setBitU
41
47
  } from "./chunk-V2D5KUKH.js";
42
48
 
43
- // src/sbf/frame.ts
44
- var CRC_TABLE = (() => {
45
- const t = new Uint16Array(256);
46
- for (let n = 0; n < 256; n++) {
47
- let c = n << 8;
48
- for (let k = 0; k < 8; k++)
49
- c = c & 32768 ? (c << 1 ^ 4129) & 65535 : c << 1 & 65535;
50
- t[n] = c;
51
- }
52
- return t;
53
- })();
54
- function crc16(data, start, end) {
55
- let crc = 0;
56
- for (let i = start; i < end; i++)
57
- crc = (crc << 8 ^ CRC_TABLE[(crc >> 8 ^ data[i]) & 255]) & 65535;
58
- return crc;
59
- }
60
- function scanSbfFrames(data, view, onBlock) {
61
- let badCrc = 0;
62
- let i = 0;
63
- while (i + 8 <= data.length) {
64
- if (data[i] !== 36 || data[i + 1] !== 64) {
65
- i++;
66
- continue;
67
- }
68
- const len = view.getUint16(i + 6, true);
69
- if (len < 8 || len % 4 !== 0) {
70
- i++;
71
- continue;
72
- }
73
- if (i + len > data.length) break;
74
- if (crc16(data, i + 4, i + len) !== view.getUint16(i + 2, true)) {
75
- badCrc++;
76
- i++;
77
- continue;
78
- }
79
- onBlock(view.getUint16(i + 4, true) & 8191, i, len);
80
- i += len;
81
- }
82
- return badCrc;
83
- }
84
- var two = (n) => String(n).padStart(2, "0");
85
- function svidToPrn(svid) {
86
- if (svid >= 1 && svid <= 37) return svid <= 32 ? `G${two(svid)}` : null;
87
- if (svid <= 61) return svid - 37 <= 27 ? `R${two(svid - 37)}` : null;
88
- if (svid <= 62) return null;
89
- if (svid <= 68) return svid - 38 <= 27 ? `R${two(svid - 38)}` : null;
90
- if (svid <= 70) return null;
91
- if (svid <= 106) return svid - 70 <= 36 ? `E${two(svid - 70)}` : null;
92
- if (svid <= 119) return null;
93
- if (svid <= 140) return `S${two(svid - 100)}`;
94
- if (svid <= 180) return svid - 140 <= 50 ? `C${two(svid - 140)}` : null;
95
- if (svid <= 190) return `J${two(svid - 180)}`;
96
- if (svid <= 197) return svid - 190 <= 14 ? `I${two(svid - 190)}` : null;
97
- if (svid <= 215) return `S${two(svid - 157)}`;
98
- if (svid <= 222) return svid - 208 <= 14 ? `I${two(svid - 208)}` : null;
99
- if (svid <= 245) return svid - 182 <= 50 ? `C${two(svid - 182)}` : null;
100
- return null;
101
- }
102
-
103
49
  // src/sbf/nav.ts
104
50
  var PI = Math.PI;
105
51
  var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
@@ -1981,23 +1927,23 @@ var MEAS2_SIG = [
1981
1927
  ["J", "1E"],
1982
1928
  ["J", "5P"]
1983
1929
  ];
1984
- var two2 = (n) => String(n).padStart(2, "0");
1930
+ var two = (n) => String(n).padStart(2, "0");
1985
1931
  function m3Prn(navsys, svid) {
1986
1932
  switch (navsys) {
1987
1933
  case 0:
1988
- return svid < 32 ? `G${two2(svid + 1)}` : null;
1934
+ return svid < 32 ? `G${two(svid + 1)}` : null;
1989
1935
  case 1:
1990
- return svid < 27 ? `R${two2(svid + 1)}` : null;
1936
+ return svid < 27 ? `R${two(svid + 1)}` : null;
1991
1937
  case 2:
1992
- return svid < 36 ? `E${two2(svid + 1)}` : null;
1938
+ return svid < 36 ? `E${two(svid + 1)}` : null;
1993
1939
  case 3:
1994
- return svid < 50 ? `C${two2(svid + 1)}` : null;
1940
+ return svid < 50 ? `C${two(svid + 1)}` : null;
1995
1941
  case 4:
1996
- return svid <= 38 ? `S${two2(svid + 20)}` : null;
1942
+ return svid <= 38 ? `S${two(svid + 20)}` : null;
1997
1943
  case 5:
1998
- return svid < 10 ? `J${two2(svid + 1)}` : null;
1944
+ return svid < 10 ? `J${two(svid + 1)}` : null;
1999
1945
  case 6:
2000
- return svid < 14 ? `I${two2(svid + 1)}` : null;
1946
+ return svid < 14 ? `I${two(svid + 1)}` : null;
2001
1947
  default:
2002
1948
  return null;
2003
1949
  }
@@ -2548,8 +2494,6 @@ function parseSbfMeas(data) {
2548
2494
  }
2549
2495
 
2550
2496
  export {
2551
- crc16,
2552
- scanSbfFrames,
2553
2497
  parseSbfNav,
2554
2498
  parseSbfAlmanac,
2555
2499
  parseSbfIonoUtc,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseRinexStream
3
- } from "./chunk-3U5AX7PY.js";
3
+ } from "./chunk-FEABDV5P.js";
4
4
  import {
5
5
  ARC_GAP_FACTOR,
6
6
  BAND_LABELS,