gnss-js 2.11.0 → 2.13.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 +19 -1
- package/dist/analysis.cjs +24 -25
- package/dist/analysis.js +2 -2
- package/dist/android.cjs +338 -0
- package/dist/android.d.cts +68 -0
- package/dist/android.d.ts +68 -0
- package/dist/android.js +311 -0
- package/dist/{clk-DGVBGRzA.d.cts → bias-sinex-OGMw9xEZ.d.cts} +66 -1
- package/dist/{clk-DGVBGRzA.d.ts → bias-sinex-OGMw9xEZ.d.ts} +66 -1
- package/dist/{chunk-MDO77CCC.js → chunk-2DJ4XENO.js} +45 -0
- package/dist/{chunk-IQHQHUOB.js → chunk-2KQMCJ5Z.js} +5 -3
- package/dist/chunk-E3Y6YKQM.js +65 -0
- package/dist/{chunk-3U5AX7PY.js → chunk-FEABDV5P.js} +24 -25
- package/dist/{chunk-RFPR4LKM.js → chunk-HWHD5E6V.js} +19 -75
- package/dist/{chunk-KUSUN7DP.js → chunk-MFCPYUEI.js} +1 -1
- package/dist/{chunk-SSWFX3P3.js → chunk-NIJ6WFQJ.js} +6 -321
- package/dist/{chunk-UMINQTMK.js → chunk-RWFJEVLJ.js} +589 -295
- package/dist/chunk-TWGZTRBL.js +323 -0
- package/dist/index.cjs +655 -316
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -9
- package/dist/osnma.cjs +1336 -0
- package/dist/osnma.d.cts +273 -0
- package/dist/osnma.d.ts +273 -0
- package/dist/osnma.js +1216 -0
- package/dist/positioning.cjs +585 -291
- package/dist/positioning.d.cts +114 -4
- package/dist/positioning.d.ts +114 -4
- package/dist/positioning.js +1 -1
- package/dist/rinex.cjs +70 -25
- package/dist/rinex.d.cts +2 -50
- package/dist/rinex.d.ts +2 -50
- package/dist/rinex.js +4 -2
- package/dist/sbf.js +7 -4
- package/dist/ubx.js +3 -2
- package/package.json +11 -1
package/dist/android.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import {
|
|
2
|
+
C_LIGHT
|
|
3
|
+
} from "./chunk-FIEWO4J4.js";
|
|
4
|
+
|
|
5
|
+
// src/android/index.ts
|
|
6
|
+
var NS_PER_WEEK = 604800000000000n;
|
|
7
|
+
var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
|
|
8
|
+
var BDT_MINUS_GPST_S = -14;
|
|
9
|
+
var GLO_RANGE = [159e7, 161e7];
|
|
10
|
+
var GLO_G1 = 1602e6;
|
|
11
|
+
var GLO_G1_STEP = 562500;
|
|
12
|
+
var STATE_CODE_LOCK = 1;
|
|
13
|
+
var STATE_TOW_DECODED = 8;
|
|
14
|
+
var STATE_MSEC_AMBIGUOUS = 16;
|
|
15
|
+
var STATE_GLO_TOD_DECODED = 128;
|
|
16
|
+
var STATE_GAL_E1BC_CODE_LOCK = 1024;
|
|
17
|
+
var STATE_GAL_E1C_2ND_CODE_LOCK = 2048;
|
|
18
|
+
var STATE_GAL_E1B_PAGE_SYNC = 4096;
|
|
19
|
+
var STATE_TOW_KNOWN = 16384;
|
|
20
|
+
var STATE_GLO_TOD_KNOWN = 32768;
|
|
21
|
+
var ADR_VALID = 1;
|
|
22
|
+
var ADR_RESET = 2;
|
|
23
|
+
var ADR_CYCLE_SLIP = 4;
|
|
24
|
+
var ADR_HALF_CYCLE_RESOLVED = 8;
|
|
25
|
+
var MAX_PR_UNC_NS = 500;
|
|
26
|
+
var MAX_ADR_UNC_M = 0.1;
|
|
27
|
+
var SYSTEM = {
|
|
28
|
+
1: "G",
|
|
29
|
+
2: "S",
|
|
30
|
+
3: "R",
|
|
31
|
+
4: "J",
|
|
32
|
+
5: "C",
|
|
33
|
+
6: "E",
|
|
34
|
+
7: "I"
|
|
35
|
+
};
|
|
36
|
+
var BANDS = {
|
|
37
|
+
G: [
|
|
38
|
+
[1, 157542e4],
|
|
39
|
+
[2, 12276e5],
|
|
40
|
+
[5, 117645e4]
|
|
41
|
+
],
|
|
42
|
+
S: [
|
|
43
|
+
[1, 157542e4],
|
|
44
|
+
[5, 117645e4]
|
|
45
|
+
],
|
|
46
|
+
J: [
|
|
47
|
+
[1, 157542e4],
|
|
48
|
+
[2, 12276e5],
|
|
49
|
+
[5, 117645e4],
|
|
50
|
+
[6, 127875e4]
|
|
51
|
+
],
|
|
52
|
+
E: [
|
|
53
|
+
[1, 157542e4],
|
|
54
|
+
[5, 117645e4],
|
|
55
|
+
[7, 120714e4],
|
|
56
|
+
[6, 127875e4]
|
|
57
|
+
],
|
|
58
|
+
C: [
|
|
59
|
+
[2, 1561098e3],
|
|
60
|
+
[1, 157542e4],
|
|
61
|
+
[5, 117645e4],
|
|
62
|
+
[7, 120714e4],
|
|
63
|
+
[6, 126852e4]
|
|
64
|
+
],
|
|
65
|
+
I: [[5, 117645e4]]
|
|
66
|
+
};
|
|
67
|
+
var DEFAULT_ATTR = {
|
|
68
|
+
G1: "C",
|
|
69
|
+
G2: "L",
|
|
70
|
+
G5: "Q",
|
|
71
|
+
S1: "C",
|
|
72
|
+
S5: "I",
|
|
73
|
+
J1: "C",
|
|
74
|
+
J2: "L",
|
|
75
|
+
J5: "Q",
|
|
76
|
+
E1: "C",
|
|
77
|
+
E5: "Q",
|
|
78
|
+
E7: "Q",
|
|
79
|
+
E6: "C",
|
|
80
|
+
C2: "I",
|
|
81
|
+
C1: "P",
|
|
82
|
+
C5: "P",
|
|
83
|
+
C7: "I",
|
|
84
|
+
C6: "I",
|
|
85
|
+
I5: "A",
|
|
86
|
+
R1: "C",
|
|
87
|
+
R2: "C"
|
|
88
|
+
};
|
|
89
|
+
function prnOf(sys, svid) {
|
|
90
|
+
let n = svid;
|
|
91
|
+
if (sys === "S")
|
|
92
|
+
n = svid - 100;
|
|
93
|
+
else if (sys === "J")
|
|
94
|
+
n = svid - 192;
|
|
95
|
+
else if (sys === "R" && svid > 24) return null;
|
|
96
|
+
if (n < 1 || n > 99) return null;
|
|
97
|
+
return sys + String(n).padStart(2, "0");
|
|
98
|
+
}
|
|
99
|
+
function bandOf(sys, freqHz) {
|
|
100
|
+
if (sys === "R") {
|
|
101
|
+
if (freqHz >= GLO_RANGE[0] && freqHz <= GLO_RANGE[1]) {
|
|
102
|
+
const k = Math.round((freqHz - GLO_G1) / GLO_G1_STEP);
|
|
103
|
+
return { band: 1, freq: GLO_G1 + k * GLO_G1_STEP, gloChannel: k };
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
for (const [band, f] of BANDS[sys] ?? [])
|
|
108
|
+
if (Math.abs(freqHz - f) < 1e6) return { band, freq: f, gloChannel: null };
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
var cells = (line) => line.split(",");
|
|
112
|
+
function parseAndroidGnssLog(text) {
|
|
113
|
+
const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
114
|
+
const messageCounts = {};
|
|
115
|
+
const fixes = [];
|
|
116
|
+
let device = null;
|
|
117
|
+
let rawCols = null;
|
|
118
|
+
let fixCols = null;
|
|
119
|
+
let leapSeconds = null;
|
|
120
|
+
let rejected = 0;
|
|
121
|
+
const groups = [];
|
|
122
|
+
let curKey = "";
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
if (!line) continue;
|
|
125
|
+
if (line.startsWith("#")) {
|
|
126
|
+
const body = line.replace(/^#\s*/, "");
|
|
127
|
+
if (body.startsWith("Raw,"))
|
|
128
|
+
rawCols = new Map(cells(body).map((c, i) => [c.trim(), i]));
|
|
129
|
+
else if (body.startsWith("Fix,"))
|
|
130
|
+
fixCols = new Map(cells(body).map((c, i) => [c.trim(), i]));
|
|
131
|
+
else if (/Manufacturer/.test(body)) {
|
|
132
|
+
const m = (k) => new RegExp(`${k}:\\s*([^:]*?)(?:\\s+\\w+:|$)`).exec(body)?.[1]?.trim() ?? "";
|
|
133
|
+
device = {
|
|
134
|
+
manufacturer: m("Manufacturer"),
|
|
135
|
+
model: m("Model"),
|
|
136
|
+
version: m("Version").replace(/,$/, "")
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const type = line.slice(0, line.indexOf(","));
|
|
142
|
+
if (!type) continue;
|
|
143
|
+
messageCounts[type] = (messageCounts[type] ?? 0) + 1;
|
|
144
|
+
if (type === "Raw" && rawCols) {
|
|
145
|
+
const r = cells(line);
|
|
146
|
+
const key = r[rawCols.get("TimeNanos")] ?? "";
|
|
147
|
+
if (key !== curKey) {
|
|
148
|
+
groups.push([]);
|
|
149
|
+
curKey = key;
|
|
150
|
+
}
|
|
151
|
+
groups[groups.length - 1].push(r);
|
|
152
|
+
} else if (type === "Fix" && fixCols) {
|
|
153
|
+
const r = cells(line);
|
|
154
|
+
const g = (k) => {
|
|
155
|
+
const i = fixCols.get(k);
|
|
156
|
+
const v = i === void 0 ? "" : (r[i] ?? "").trim();
|
|
157
|
+
return v === "" ? null : Number(v);
|
|
158
|
+
};
|
|
159
|
+
const lat = g("LatitudeDegrees");
|
|
160
|
+
const lon = g("LongitudeDegrees");
|
|
161
|
+
const t = g("UnixTimeMillis");
|
|
162
|
+
if (lat != null && lon != null && t != null)
|
|
163
|
+
fixes.push({
|
|
164
|
+
utcMs: t,
|
|
165
|
+
provider: (r[fixCols.get("Provider") ?? 1] ?? "").trim(),
|
|
166
|
+
latDeg: lat,
|
|
167
|
+
lonDeg: lon,
|
|
168
|
+
altM: g("AltitudeMeters"),
|
|
169
|
+
accuracyM: g("AccuracyMeters")
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const epochs = [];
|
|
174
|
+
const obsCodes = {};
|
|
175
|
+
if (!rawCols) {
|
|
176
|
+
return {
|
|
177
|
+
epochs,
|
|
178
|
+
fixes,
|
|
179
|
+
device,
|
|
180
|
+
messageCounts,
|
|
181
|
+
obsCodes,
|
|
182
|
+
rejected,
|
|
183
|
+
leapSeconds
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const col = rawCols;
|
|
187
|
+
const str = (r, k) => {
|
|
188
|
+
const i = col.get(k);
|
|
189
|
+
return i === void 0 ? "" : (r[i] ?? "").trim();
|
|
190
|
+
};
|
|
191
|
+
const num = (r, k) => {
|
|
192
|
+
const v = str(r, k);
|
|
193
|
+
return v === "" ? null : Number(v);
|
|
194
|
+
};
|
|
195
|
+
let segment = null;
|
|
196
|
+
let fullBias = 0n;
|
|
197
|
+
let bias = 0;
|
|
198
|
+
const lockStart = /* @__PURE__ */ new Map();
|
|
199
|
+
for (const rows of groups) {
|
|
200
|
+
const r0 = rows[0];
|
|
201
|
+
const timeNanosStr = str(r0, "TimeNanos");
|
|
202
|
+
const fullBiasStr = str(r0, "FullBiasNanos");
|
|
203
|
+
if (!timeNanosStr || !fullBiasStr) {
|
|
204
|
+
rejected += rows.length;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const disc = str(r0, "HardwareClockDiscontinuityCount");
|
|
208
|
+
if (disc !== segment) {
|
|
209
|
+
segment = disc;
|
|
210
|
+
fullBias = BigInt(fullBiasStr.split(".")[0]);
|
|
211
|
+
bias = num(r0, "BiasNanos") ?? 0;
|
|
212
|
+
}
|
|
213
|
+
const leap = num(r0, "LeapSecond");
|
|
214
|
+
if (leap != null && !Number.isNaN(leap)) leapSeconds = leap;
|
|
215
|
+
const rxNs = BigInt(timeNanosStr.split(".")[0]) - fullBias;
|
|
216
|
+
const week = Number(rxNs / NS_PER_WEEK);
|
|
217
|
+
const rxWeekNs = Number(rxNs % NS_PER_WEEK);
|
|
218
|
+
const epochWeekNs = Math.round((rxWeekNs - bias) / 100) * 100;
|
|
219
|
+
const toffNs = rxWeekNs - epochWeekNs - bias;
|
|
220
|
+
const timeMs = GPS_EPOCH_MS + week * 6048e5 + epochWeekNs / 1e6;
|
|
221
|
+
const leapNow = leapSeconds ?? 18;
|
|
222
|
+
const meas = [];
|
|
223
|
+
for (const r of rows) {
|
|
224
|
+
const sys = SYSTEM[num(r, "ConstellationType") ?? 1];
|
|
225
|
+
const svid = num(r, "Svid");
|
|
226
|
+
if (!sys || svid == null) {
|
|
227
|
+
rejected++;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const prn = prnOf(sys, svid);
|
|
231
|
+
const freqHz = num(r, "CarrierFrequencyHz") ?? 157542e4;
|
|
232
|
+
const b = prn ? bandOf(sys, freqHz) : null;
|
|
233
|
+
const state = num(r, "State") ?? 0;
|
|
234
|
+
const towOk = sys === "R" ? (state & (STATE_GLO_TOD_DECODED | STATE_GLO_TOD_KNOWN)) !== 0 : (state & (STATE_TOW_DECODED | STATE_TOW_KNOWN)) !== 0;
|
|
235
|
+
const codeLock = (state & (STATE_CODE_LOCK | STATE_GAL_E1BC_CODE_LOCK)) !== 0;
|
|
236
|
+
const prUnc = num(r, "ReceivedSvTimeUncertaintyNanos");
|
|
237
|
+
const svTimeNs = num(r, "ReceivedSvTimeNanos");
|
|
238
|
+
if (!prn || !b || !towOk || !codeLock || (state & STATE_MSEC_AMBIGUOUS) !== 0 || svTimeNs == null || prUnc != null && prUnc > MAX_PR_UNC_NS) {
|
|
239
|
+
rejected++;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const subNs = bias + (num(r, "TimeOffsetNanos") ?? 0);
|
|
243
|
+
let tauNs;
|
|
244
|
+
if (sys === "R") {
|
|
245
|
+
const DAY = 864e11;
|
|
246
|
+
const tTxNs = svTimeNs - 108e11 + leapNow * 1e9;
|
|
247
|
+
tauNs = ((rxWeekNs % DAY - tTxNs) % DAY + DAY) % DAY;
|
|
248
|
+
if (tauNs > DAY / 2) tauNs -= DAY;
|
|
249
|
+
} else {
|
|
250
|
+
const tTxNs = svTimeNs - (sys === "C" ? BDT_MINUS_GPST_S * 1e9 : 0);
|
|
251
|
+
tauNs = rxWeekNs - tTxNs;
|
|
252
|
+
if (tauNs > 3024e11) tauNs -= 6048e11;
|
|
253
|
+
else if (tauNs < -3024e11) tauNs += 6048e11;
|
|
254
|
+
}
|
|
255
|
+
tauNs -= subNs;
|
|
256
|
+
if (!(tauNs > 6e7 && tauNs < 16e7)) {
|
|
257
|
+
rejected++;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const pr = (tauNs - toffNs) * 1e-9 * C_LIGHT;
|
|
261
|
+
const attrCell = str(r, "CodeType");
|
|
262
|
+
let attr = attrCell || DEFAULT_ATTR[sys + b.band] || "C";
|
|
263
|
+
if (!attrCell && sys === "E" && b.band === 1 && (state & STATE_GAL_E1C_2ND_CODE_LOCK) === 0 && (state & STATE_GAL_E1B_PAGE_SYNC) !== 0)
|
|
264
|
+
attr = "B";
|
|
265
|
+
const code = `${b.band}${attr}`;
|
|
266
|
+
const lambda = C_LIGHT / b.freq;
|
|
267
|
+
const adrState = num(r, "AccumulatedDeltaRangeState") ?? 0;
|
|
268
|
+
const adr = num(r, "AccumulatedDeltaRangeMeters");
|
|
269
|
+
const adrUnc = num(r, "AccumulatedDeltaRangeUncertaintyMeters");
|
|
270
|
+
const lockKey = prn + code;
|
|
271
|
+
let cp = null;
|
|
272
|
+
if ((adrState & ADR_VALID) !== 0 && adr != null && adr !== 0 && (adrUnc == null || adrUnc < MAX_ADR_UNC_M)) {
|
|
273
|
+
cp = adr / lambda - toffNs * 1e-9 * b.freq;
|
|
274
|
+
if ((adrState & (ADR_RESET | ADR_CYCLE_SLIP)) !== 0 || !lockStart.has(lockKey))
|
|
275
|
+
lockStart.set(lockKey, timeMs);
|
|
276
|
+
} else lockStart.delete(lockKey);
|
|
277
|
+
const prr = num(r, "PseudorangeRateMetersPerSecond");
|
|
278
|
+
meas.push({
|
|
279
|
+
prn,
|
|
280
|
+
code,
|
|
281
|
+
pr,
|
|
282
|
+
cp,
|
|
283
|
+
doppler: prr == null ? null : -prr / lambda,
|
|
284
|
+
cn0: num(r, "Cn0DbHz"),
|
|
285
|
+
halfCycleAmbiguous: cp != null && (adrState & ADR_HALF_CYCLE_RESOLVED) === 0,
|
|
286
|
+
lockTimeMs: cp != null ? timeMs - lockStart.get(lockKey) : 0,
|
|
287
|
+
gloChannel: b.gloChannel
|
|
288
|
+
});
|
|
289
|
+
const list = obsCodes[sys] ??= [];
|
|
290
|
+
if (!list.includes(code)) list.push(code);
|
|
291
|
+
}
|
|
292
|
+
if (meas.length > 0) epochs.push({ timeMs, meas });
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
epochs,
|
|
296
|
+
fixes,
|
|
297
|
+
device,
|
|
298
|
+
messageCounts,
|
|
299
|
+
obsCodes,
|
|
300
|
+
rejected,
|
|
301
|
+
leapSeconds
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function isAndroidGnssLog(text) {
|
|
305
|
+
const head = text.slice(0, 16384);
|
|
306
|
+
return /^#\s*Raw,/m.test(head) || /^Raw,\d+,\d+,/m.test(head);
|
|
307
|
+
}
|
|
308
|
+
export {
|
|
309
|
+
isAndroidGnssLog,
|
|
310
|
+
parseAndroidGnssLog
|
|
311
|
+
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
444
|
-
|
|
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
|
|
557
|
-
crxSatList.push(
|
|
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(
|
|
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";
|