gnss-js 2.12.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- 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/{chunk-RWFJEVLJ.js → chunk-PQLPPFIK.js} +27 -2
- package/dist/index.cjs +27 -2
- package/dist/index.js +1 -1
- package/dist/positioning.cjs +27 -2
- package/dist/positioning.js +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Everything is exposed through subpath exports (`gnss-js/rinex`, `gnss-js/positio
|
|
|
14
14
|
|
|
15
15
|
## What's inside
|
|
16
16
|
|
|
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.
|
|
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; Android raw GNSS logs (GnssLogger).
|
|
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
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.
|
|
@@ -94,12 +94,15 @@ import { parseUbxRawx, parseUbxRawNav } from 'gnss-js/ubx'; // u-blox
|
|
|
94
94
|
import { parseNovatelRange, parseNovatelNav } from 'gnss-js/novatel'; // NovAtel OEM4
|
|
95
95
|
import { parseTrimble, parseTrimbleNav } from 'gnss-js/trimble'; // Trimble
|
|
96
96
|
import { parseBinex } from 'gnss-js/binex'; // BINEX
|
|
97
|
+
import { parseAndroidGnssLog } from 'gnss-js/android'; // Android GnssLogger text logs
|
|
97
98
|
|
|
98
99
|
const sbf = parseSbfMeas(bytes); // measurement epochs + counts
|
|
99
100
|
const nav = parseSbfNav(bytes); // GPS/GAL/BDS/GLO ephemeris from raw blocks
|
|
100
101
|
const has = parseSbfHas(bytes); // Galileo HAS (E6 C/NAV) SSR corrections
|
|
101
102
|
```
|
|
102
103
|
|
|
104
|
+
Android phones log raw measurements (`android.location.GnssMeasurement`) through Google's GnssLogger app. `parseAndroidGnssLog(text)` rebuilds pseudoranges, carrier phase, Doppler and C/N0 per signal (GPS, GLONASS, Galileo, BeiDou, QZSS, NavIC, SBAS; L1/E1/B1 and L5/E5a/B2a), in the same epoch shape as the receiver decoders, plus the phone's own position fixes. It matches Google's gps-measurement-tools pseudoranges exactly.
|
|
105
|
+
|
|
103
106
|
### Galileo OSNMA (navigation message authentication)
|
|
104
107
|
|
|
105
108
|
`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.
|
package/dist/android.cjs
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/android/index.ts
|
|
21
|
+
var android_exports = {};
|
|
22
|
+
__export(android_exports, {
|
|
23
|
+
isAndroidGnssLog: () => isAndroidGnssLog,
|
|
24
|
+
parseAndroidGnssLog: () => parseAndroidGnssLog
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(android_exports);
|
|
27
|
+
|
|
28
|
+
// src/constants/gnss.ts
|
|
29
|
+
var C_LIGHT = 299792458;
|
|
30
|
+
|
|
31
|
+
// src/android/index.ts
|
|
32
|
+
var NS_PER_WEEK = 604800000000000n;
|
|
33
|
+
var GPS_EPOCH_MS = Date.UTC(1980, 0, 6);
|
|
34
|
+
var BDT_MINUS_GPST_S = -14;
|
|
35
|
+
var GLO_RANGE = [159e7, 161e7];
|
|
36
|
+
var GLO_G1 = 1602e6;
|
|
37
|
+
var GLO_G1_STEP = 562500;
|
|
38
|
+
var STATE_CODE_LOCK = 1;
|
|
39
|
+
var STATE_TOW_DECODED = 8;
|
|
40
|
+
var STATE_MSEC_AMBIGUOUS = 16;
|
|
41
|
+
var STATE_GLO_TOD_DECODED = 128;
|
|
42
|
+
var STATE_GAL_E1BC_CODE_LOCK = 1024;
|
|
43
|
+
var STATE_GAL_E1C_2ND_CODE_LOCK = 2048;
|
|
44
|
+
var STATE_GAL_E1B_PAGE_SYNC = 4096;
|
|
45
|
+
var STATE_TOW_KNOWN = 16384;
|
|
46
|
+
var STATE_GLO_TOD_KNOWN = 32768;
|
|
47
|
+
var ADR_VALID = 1;
|
|
48
|
+
var ADR_RESET = 2;
|
|
49
|
+
var ADR_CYCLE_SLIP = 4;
|
|
50
|
+
var ADR_HALF_CYCLE_RESOLVED = 8;
|
|
51
|
+
var MAX_PR_UNC_NS = 500;
|
|
52
|
+
var MAX_ADR_UNC_M = 0.1;
|
|
53
|
+
var SYSTEM = {
|
|
54
|
+
1: "G",
|
|
55
|
+
2: "S",
|
|
56
|
+
3: "R",
|
|
57
|
+
4: "J",
|
|
58
|
+
5: "C",
|
|
59
|
+
6: "E",
|
|
60
|
+
7: "I"
|
|
61
|
+
};
|
|
62
|
+
var BANDS = {
|
|
63
|
+
G: [
|
|
64
|
+
[1, 157542e4],
|
|
65
|
+
[2, 12276e5],
|
|
66
|
+
[5, 117645e4]
|
|
67
|
+
],
|
|
68
|
+
S: [
|
|
69
|
+
[1, 157542e4],
|
|
70
|
+
[5, 117645e4]
|
|
71
|
+
],
|
|
72
|
+
J: [
|
|
73
|
+
[1, 157542e4],
|
|
74
|
+
[2, 12276e5],
|
|
75
|
+
[5, 117645e4],
|
|
76
|
+
[6, 127875e4]
|
|
77
|
+
],
|
|
78
|
+
E: [
|
|
79
|
+
[1, 157542e4],
|
|
80
|
+
[5, 117645e4],
|
|
81
|
+
[7, 120714e4],
|
|
82
|
+
[6, 127875e4]
|
|
83
|
+
],
|
|
84
|
+
C: [
|
|
85
|
+
[2, 1561098e3],
|
|
86
|
+
[1, 157542e4],
|
|
87
|
+
[5, 117645e4],
|
|
88
|
+
[7, 120714e4],
|
|
89
|
+
[6, 126852e4]
|
|
90
|
+
],
|
|
91
|
+
I: [[5, 117645e4]]
|
|
92
|
+
};
|
|
93
|
+
var DEFAULT_ATTR = {
|
|
94
|
+
G1: "C",
|
|
95
|
+
G2: "L",
|
|
96
|
+
G5: "Q",
|
|
97
|
+
S1: "C",
|
|
98
|
+
S5: "I",
|
|
99
|
+
J1: "C",
|
|
100
|
+
J2: "L",
|
|
101
|
+
J5: "Q",
|
|
102
|
+
E1: "C",
|
|
103
|
+
E5: "Q",
|
|
104
|
+
E7: "Q",
|
|
105
|
+
E6: "C",
|
|
106
|
+
C2: "I",
|
|
107
|
+
C1: "P",
|
|
108
|
+
C5: "P",
|
|
109
|
+
C7: "I",
|
|
110
|
+
C6: "I",
|
|
111
|
+
I5: "A",
|
|
112
|
+
R1: "C",
|
|
113
|
+
R2: "C"
|
|
114
|
+
};
|
|
115
|
+
function prnOf(sys, svid) {
|
|
116
|
+
let n = svid;
|
|
117
|
+
if (sys === "S")
|
|
118
|
+
n = svid - 100;
|
|
119
|
+
else if (sys === "J")
|
|
120
|
+
n = svid - 192;
|
|
121
|
+
else if (sys === "R" && svid > 24) return null;
|
|
122
|
+
if (n < 1 || n > 99) return null;
|
|
123
|
+
return sys + String(n).padStart(2, "0");
|
|
124
|
+
}
|
|
125
|
+
function bandOf(sys, freqHz) {
|
|
126
|
+
if (sys === "R") {
|
|
127
|
+
if (freqHz >= GLO_RANGE[0] && freqHz <= GLO_RANGE[1]) {
|
|
128
|
+
const k = Math.round((freqHz - GLO_G1) / GLO_G1_STEP);
|
|
129
|
+
return { band: 1, freq: GLO_G1 + k * GLO_G1_STEP, gloChannel: k };
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
for (const [band, f] of BANDS[sys] ?? [])
|
|
134
|
+
if (Math.abs(freqHz - f) < 1e6) return { band, freq: f, gloChannel: null };
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
var cells = (line) => line.split(",");
|
|
138
|
+
function parseAndroidGnssLog(text) {
|
|
139
|
+
const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
140
|
+
const messageCounts = {};
|
|
141
|
+
const fixes = [];
|
|
142
|
+
let device = null;
|
|
143
|
+
let rawCols = null;
|
|
144
|
+
let fixCols = null;
|
|
145
|
+
let leapSeconds = null;
|
|
146
|
+
let rejected = 0;
|
|
147
|
+
const groups = [];
|
|
148
|
+
let curKey = "";
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
if (!line) continue;
|
|
151
|
+
if (line.startsWith("#")) {
|
|
152
|
+
const body = line.replace(/^#\s*/, "");
|
|
153
|
+
if (body.startsWith("Raw,"))
|
|
154
|
+
rawCols = new Map(cells(body).map((c, i) => [c.trim(), i]));
|
|
155
|
+
else if (body.startsWith("Fix,"))
|
|
156
|
+
fixCols = new Map(cells(body).map((c, i) => [c.trim(), i]));
|
|
157
|
+
else if (/Manufacturer/.test(body)) {
|
|
158
|
+
const m = (k) => new RegExp(`${k}:\\s*([^:]*?)(?:\\s+\\w+:|$)`).exec(body)?.[1]?.trim() ?? "";
|
|
159
|
+
device = {
|
|
160
|
+
manufacturer: m("Manufacturer"),
|
|
161
|
+
model: m("Model"),
|
|
162
|
+
version: m("Version").replace(/,$/, "")
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const type = line.slice(0, line.indexOf(","));
|
|
168
|
+
if (!type) continue;
|
|
169
|
+
messageCounts[type] = (messageCounts[type] ?? 0) + 1;
|
|
170
|
+
if (type === "Raw" && rawCols) {
|
|
171
|
+
const r = cells(line);
|
|
172
|
+
const key = r[rawCols.get("TimeNanos")] ?? "";
|
|
173
|
+
if (key !== curKey) {
|
|
174
|
+
groups.push([]);
|
|
175
|
+
curKey = key;
|
|
176
|
+
}
|
|
177
|
+
groups[groups.length - 1].push(r);
|
|
178
|
+
} else if (type === "Fix" && fixCols) {
|
|
179
|
+
const r = cells(line);
|
|
180
|
+
const g = (k) => {
|
|
181
|
+
const i = fixCols.get(k);
|
|
182
|
+
const v = i === void 0 ? "" : (r[i] ?? "").trim();
|
|
183
|
+
return v === "" ? null : Number(v);
|
|
184
|
+
};
|
|
185
|
+
const lat = g("LatitudeDegrees");
|
|
186
|
+
const lon = g("LongitudeDegrees");
|
|
187
|
+
const t = g("UnixTimeMillis");
|
|
188
|
+
if (lat != null && lon != null && t != null)
|
|
189
|
+
fixes.push({
|
|
190
|
+
utcMs: t,
|
|
191
|
+
provider: (r[fixCols.get("Provider") ?? 1] ?? "").trim(),
|
|
192
|
+
latDeg: lat,
|
|
193
|
+
lonDeg: lon,
|
|
194
|
+
altM: g("AltitudeMeters"),
|
|
195
|
+
accuracyM: g("AccuracyMeters")
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const epochs = [];
|
|
200
|
+
const obsCodes = {};
|
|
201
|
+
if (!rawCols) {
|
|
202
|
+
return {
|
|
203
|
+
epochs,
|
|
204
|
+
fixes,
|
|
205
|
+
device,
|
|
206
|
+
messageCounts,
|
|
207
|
+
obsCodes,
|
|
208
|
+
rejected,
|
|
209
|
+
leapSeconds
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const col = rawCols;
|
|
213
|
+
const str = (r, k) => {
|
|
214
|
+
const i = col.get(k);
|
|
215
|
+
return i === void 0 ? "" : (r[i] ?? "").trim();
|
|
216
|
+
};
|
|
217
|
+
const num = (r, k) => {
|
|
218
|
+
const v = str(r, k);
|
|
219
|
+
return v === "" ? null : Number(v);
|
|
220
|
+
};
|
|
221
|
+
let segment = null;
|
|
222
|
+
let fullBias = 0n;
|
|
223
|
+
let bias = 0;
|
|
224
|
+
const lockStart = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const rows of groups) {
|
|
226
|
+
const r0 = rows[0];
|
|
227
|
+
const timeNanosStr = str(r0, "TimeNanos");
|
|
228
|
+
const fullBiasStr = str(r0, "FullBiasNanos");
|
|
229
|
+
if (!timeNanosStr || !fullBiasStr) {
|
|
230
|
+
rejected += rows.length;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const disc = str(r0, "HardwareClockDiscontinuityCount");
|
|
234
|
+
if (disc !== segment) {
|
|
235
|
+
segment = disc;
|
|
236
|
+
fullBias = BigInt(fullBiasStr.split(".")[0]);
|
|
237
|
+
bias = num(r0, "BiasNanos") ?? 0;
|
|
238
|
+
}
|
|
239
|
+
const leap = num(r0, "LeapSecond");
|
|
240
|
+
if (leap != null && !Number.isNaN(leap)) leapSeconds = leap;
|
|
241
|
+
const rxNs = BigInt(timeNanosStr.split(".")[0]) - fullBias;
|
|
242
|
+
const week = Number(rxNs / NS_PER_WEEK);
|
|
243
|
+
const rxWeekNs = Number(rxNs % NS_PER_WEEK);
|
|
244
|
+
const epochWeekNs = Math.round((rxWeekNs - bias) / 100) * 100;
|
|
245
|
+
const toffNs = rxWeekNs - epochWeekNs - bias;
|
|
246
|
+
const timeMs = GPS_EPOCH_MS + week * 6048e5 + epochWeekNs / 1e6;
|
|
247
|
+
const leapNow = leapSeconds ?? 18;
|
|
248
|
+
const meas = [];
|
|
249
|
+
for (const r of rows) {
|
|
250
|
+
const sys = SYSTEM[num(r, "ConstellationType") ?? 1];
|
|
251
|
+
const svid = num(r, "Svid");
|
|
252
|
+
if (!sys || svid == null) {
|
|
253
|
+
rejected++;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const prn = prnOf(sys, svid);
|
|
257
|
+
const freqHz = num(r, "CarrierFrequencyHz") ?? 157542e4;
|
|
258
|
+
const b = prn ? bandOf(sys, freqHz) : null;
|
|
259
|
+
const state = num(r, "State") ?? 0;
|
|
260
|
+
const towOk = sys === "R" ? (state & (STATE_GLO_TOD_DECODED | STATE_GLO_TOD_KNOWN)) !== 0 : (state & (STATE_TOW_DECODED | STATE_TOW_KNOWN)) !== 0;
|
|
261
|
+
const codeLock = (state & (STATE_CODE_LOCK | STATE_GAL_E1BC_CODE_LOCK)) !== 0;
|
|
262
|
+
const prUnc = num(r, "ReceivedSvTimeUncertaintyNanos");
|
|
263
|
+
const svTimeNs = num(r, "ReceivedSvTimeNanos");
|
|
264
|
+
if (!prn || !b || !towOk || !codeLock || (state & STATE_MSEC_AMBIGUOUS) !== 0 || svTimeNs == null || prUnc != null && prUnc > MAX_PR_UNC_NS) {
|
|
265
|
+
rejected++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const subNs = bias + (num(r, "TimeOffsetNanos") ?? 0);
|
|
269
|
+
let tauNs;
|
|
270
|
+
if (sys === "R") {
|
|
271
|
+
const DAY = 864e11;
|
|
272
|
+
const tTxNs = svTimeNs - 108e11 + leapNow * 1e9;
|
|
273
|
+
tauNs = ((rxWeekNs % DAY - tTxNs) % DAY + DAY) % DAY;
|
|
274
|
+
if (tauNs > DAY / 2) tauNs -= DAY;
|
|
275
|
+
} else {
|
|
276
|
+
const tTxNs = svTimeNs - (sys === "C" ? BDT_MINUS_GPST_S * 1e9 : 0);
|
|
277
|
+
tauNs = rxWeekNs - tTxNs;
|
|
278
|
+
if (tauNs > 3024e11) tauNs -= 6048e11;
|
|
279
|
+
else if (tauNs < -3024e11) tauNs += 6048e11;
|
|
280
|
+
}
|
|
281
|
+
tauNs -= subNs;
|
|
282
|
+
if (!(tauNs > 6e7 && tauNs < 16e7)) {
|
|
283
|
+
rejected++;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const pr = (tauNs - toffNs) * 1e-9 * C_LIGHT;
|
|
287
|
+
const attrCell = str(r, "CodeType");
|
|
288
|
+
let attr = attrCell || DEFAULT_ATTR[sys + b.band] || "C";
|
|
289
|
+
if (!attrCell && sys === "E" && b.band === 1 && (state & STATE_GAL_E1C_2ND_CODE_LOCK) === 0 && (state & STATE_GAL_E1B_PAGE_SYNC) !== 0)
|
|
290
|
+
attr = "B";
|
|
291
|
+
const code = `${b.band}${attr}`;
|
|
292
|
+
const lambda = C_LIGHT / b.freq;
|
|
293
|
+
const adrState = num(r, "AccumulatedDeltaRangeState") ?? 0;
|
|
294
|
+
const adr = num(r, "AccumulatedDeltaRangeMeters");
|
|
295
|
+
const adrUnc = num(r, "AccumulatedDeltaRangeUncertaintyMeters");
|
|
296
|
+
const lockKey = prn + code;
|
|
297
|
+
let cp = null;
|
|
298
|
+
if ((adrState & ADR_VALID) !== 0 && adr != null && adr !== 0 && (adrUnc == null || adrUnc < MAX_ADR_UNC_M)) {
|
|
299
|
+
cp = adr / lambda - toffNs * 1e-9 * b.freq;
|
|
300
|
+
if ((adrState & (ADR_RESET | ADR_CYCLE_SLIP)) !== 0 || !lockStart.has(lockKey))
|
|
301
|
+
lockStart.set(lockKey, timeMs);
|
|
302
|
+
} else lockStart.delete(lockKey);
|
|
303
|
+
const prr = num(r, "PseudorangeRateMetersPerSecond");
|
|
304
|
+
meas.push({
|
|
305
|
+
prn,
|
|
306
|
+
code,
|
|
307
|
+
pr,
|
|
308
|
+
cp,
|
|
309
|
+
doppler: prr == null ? null : -prr / lambda,
|
|
310
|
+
cn0: num(r, "Cn0DbHz"),
|
|
311
|
+
halfCycleAmbiguous: cp != null && (adrState & ADR_HALF_CYCLE_RESOLVED) === 0,
|
|
312
|
+
lockTimeMs: cp != null ? timeMs - lockStart.get(lockKey) : 0,
|
|
313
|
+
gloChannel: b.gloChannel
|
|
314
|
+
});
|
|
315
|
+
const list = obsCodes[sys] ??= [];
|
|
316
|
+
if (!list.includes(code)) list.push(code);
|
|
317
|
+
}
|
|
318
|
+
if (meas.length > 0) epochs.push({ timeMs, meas });
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
epochs,
|
|
322
|
+
fixes,
|
|
323
|
+
device,
|
|
324
|
+
messageCounts,
|
|
325
|
+
obsCodes,
|
|
326
|
+
rejected,
|
|
327
|
+
leapSeconds
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function isAndroidGnssLog(text) {
|
|
331
|
+
const head = text.slice(0, 16384);
|
|
332
|
+
return /^#\s*Raw,/m.test(head) || /^Raw,\d+,\d+,/m.test(head);
|
|
333
|
+
}
|
|
334
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
335
|
+
0 && (module.exports = {
|
|
336
|
+
isAndroidGnssLog,
|
|
337
|
+
parseAndroidGnssLog
|
|
338
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** One satellite signal at an epoch, in the shape of the other receiver
|
|
2
|
+
* decoders (`ubx`, `sbf`…). */
|
|
3
|
+
interface AndroidMeasurement {
|
|
4
|
+
/** RINEX PRN, e.g. "G04", "R11", "J02". */
|
|
5
|
+
prn: string;
|
|
6
|
+
/** RINEX band + attribute, e.g. "1C", "5Q", "2I". */
|
|
7
|
+
code: string;
|
|
8
|
+
/** Pseudorange (m). */
|
|
9
|
+
pr: number;
|
|
10
|
+
/** Carrier phase (cycles), null when not tracked/valid. */
|
|
11
|
+
cp: number | null;
|
|
12
|
+
/** Doppler (Hz). */
|
|
13
|
+
doppler: number | null;
|
|
14
|
+
/** C/N0 (dB-Hz). */
|
|
15
|
+
cn0: number | null;
|
|
16
|
+
/** Carrier phase may be off by half a cycle (not half-cycle resolved). */
|
|
17
|
+
halfCycleAmbiguous: boolean;
|
|
18
|
+
/** Continuous carrier-lock time (ms): 0 at a slip/reset, then growing —
|
|
19
|
+
* the same loss-of-lock signal the other decoders give. */
|
|
20
|
+
lockTimeMs: number;
|
|
21
|
+
/** GLONASS frequency channel (−7…+6), from the carrier frequency. */
|
|
22
|
+
gloChannel: number | null;
|
|
23
|
+
}
|
|
24
|
+
interface AndroidEpoch {
|
|
25
|
+
/** Receiver epoch (GPS-scale ms — the RINEX-parser convention). */
|
|
26
|
+
timeMs: number;
|
|
27
|
+
meas: AndroidMeasurement[];
|
|
28
|
+
}
|
|
29
|
+
/** The phone's own position fix (`Fix,` rows). */
|
|
30
|
+
interface AndroidFix {
|
|
31
|
+
/** UTC ms. */
|
|
32
|
+
utcMs: number;
|
|
33
|
+
/** 'GPS' (GNSS chip), 'FLP' (fused), 'NLP' (network)… */
|
|
34
|
+
provider: string;
|
|
35
|
+
latDeg: number;
|
|
36
|
+
lonDeg: number;
|
|
37
|
+
/** Altitude (m) as reported (WGS84 ellipsoidal on most devices). */
|
|
38
|
+
altM: number | null;
|
|
39
|
+
/** Horizontal accuracy (m, 68%). */
|
|
40
|
+
accuracyM: number | null;
|
|
41
|
+
}
|
|
42
|
+
interface AndroidLogResult {
|
|
43
|
+
epochs: AndroidEpoch[];
|
|
44
|
+
fixes: AndroidFix[];
|
|
45
|
+
/** Device line (`# Version: … Manufacturer: … Model: …`), if present. */
|
|
46
|
+
device: {
|
|
47
|
+
manufacturer: string;
|
|
48
|
+
model: string;
|
|
49
|
+
version: string;
|
|
50
|
+
} | null;
|
|
51
|
+
/** Rows per record type ('Raw', 'Fix', 'Status', 'Nav'…). */
|
|
52
|
+
messageCounts: Record<string, number>;
|
|
53
|
+
/** Observation codes per system letter, first-seen order. */
|
|
54
|
+
obsCodes: Record<string, string[]>;
|
|
55
|
+
/** Raw rows dropped (no code lock / ambiguous time / bad values). */
|
|
56
|
+
rejected: number;
|
|
57
|
+
/** GPS − UTC leap seconds from the log, if reported. */
|
|
58
|
+
leapSeconds: number | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse a GnssLogger text log. Missing `CarrierFrequencyHz` (very old logs)
|
|
62
|
+
* means GPS/Galileo/QZSS L1.
|
|
63
|
+
*/
|
|
64
|
+
declare function parseAndroidGnssLog(text: string): AndroidLogResult;
|
|
65
|
+
/** Whether `text` looks like a GnssLogger log (first few KB). */
|
|
66
|
+
declare function isAndroidGnssLog(text: string): boolean;
|
|
67
|
+
|
|
68
|
+
export { type AndroidEpoch, type AndroidFix, type AndroidLogResult, type AndroidMeasurement, isAndroidGnssLog, parseAndroidGnssLog };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** One satellite signal at an epoch, in the shape of the other receiver
|
|
2
|
+
* decoders (`ubx`, `sbf`…). */
|
|
3
|
+
interface AndroidMeasurement {
|
|
4
|
+
/** RINEX PRN, e.g. "G04", "R11", "J02". */
|
|
5
|
+
prn: string;
|
|
6
|
+
/** RINEX band + attribute, e.g. "1C", "5Q", "2I". */
|
|
7
|
+
code: string;
|
|
8
|
+
/** Pseudorange (m). */
|
|
9
|
+
pr: number;
|
|
10
|
+
/** Carrier phase (cycles), null when not tracked/valid. */
|
|
11
|
+
cp: number | null;
|
|
12
|
+
/** Doppler (Hz). */
|
|
13
|
+
doppler: number | null;
|
|
14
|
+
/** C/N0 (dB-Hz). */
|
|
15
|
+
cn0: number | null;
|
|
16
|
+
/** Carrier phase may be off by half a cycle (not half-cycle resolved). */
|
|
17
|
+
halfCycleAmbiguous: boolean;
|
|
18
|
+
/** Continuous carrier-lock time (ms): 0 at a slip/reset, then growing —
|
|
19
|
+
* the same loss-of-lock signal the other decoders give. */
|
|
20
|
+
lockTimeMs: number;
|
|
21
|
+
/** GLONASS frequency channel (−7…+6), from the carrier frequency. */
|
|
22
|
+
gloChannel: number | null;
|
|
23
|
+
}
|
|
24
|
+
interface AndroidEpoch {
|
|
25
|
+
/** Receiver epoch (GPS-scale ms — the RINEX-parser convention). */
|
|
26
|
+
timeMs: number;
|
|
27
|
+
meas: AndroidMeasurement[];
|
|
28
|
+
}
|
|
29
|
+
/** The phone's own position fix (`Fix,` rows). */
|
|
30
|
+
interface AndroidFix {
|
|
31
|
+
/** UTC ms. */
|
|
32
|
+
utcMs: number;
|
|
33
|
+
/** 'GPS' (GNSS chip), 'FLP' (fused), 'NLP' (network)… */
|
|
34
|
+
provider: string;
|
|
35
|
+
latDeg: number;
|
|
36
|
+
lonDeg: number;
|
|
37
|
+
/** Altitude (m) as reported (WGS84 ellipsoidal on most devices). */
|
|
38
|
+
altM: number | null;
|
|
39
|
+
/** Horizontal accuracy (m, 68%). */
|
|
40
|
+
accuracyM: number | null;
|
|
41
|
+
}
|
|
42
|
+
interface AndroidLogResult {
|
|
43
|
+
epochs: AndroidEpoch[];
|
|
44
|
+
fixes: AndroidFix[];
|
|
45
|
+
/** Device line (`# Version: … Manufacturer: … Model: …`), if present. */
|
|
46
|
+
device: {
|
|
47
|
+
manufacturer: string;
|
|
48
|
+
model: string;
|
|
49
|
+
version: string;
|
|
50
|
+
} | null;
|
|
51
|
+
/** Rows per record type ('Raw', 'Fix', 'Status', 'Nav'…). */
|
|
52
|
+
messageCounts: Record<string, number>;
|
|
53
|
+
/** Observation codes per system letter, first-seen order. */
|
|
54
|
+
obsCodes: Record<string, string[]>;
|
|
55
|
+
/** Raw rows dropped (no code lock / ambiguous time / bad values). */
|
|
56
|
+
rejected: number;
|
|
57
|
+
/** GPS − UTC leap seconds from the log, if reported. */
|
|
58
|
+
leapSeconds: number | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse a GnssLogger text log. Missing `CarrierFrequencyHz` (very old logs)
|
|
62
|
+
* means GPS/Galileo/QZSS L1.
|
|
63
|
+
*/
|
|
64
|
+
declare function parseAndroidGnssLog(text: string): AndroidLogResult;
|
|
65
|
+
/** Whether `text` looks like a GnssLogger log (first few KB). */
|
|
66
|
+
declare function isAndroidGnssLog(text: string): boolean;
|
|
67
|
+
|
|
68
|
+
export { type AndroidEpoch, type AndroidFix, type AndroidLogResult, type AndroidMeasurement, isAndroidGnssLog, parseAndroidGnssLog };
|
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
|
+
};
|
|
@@ -3144,7 +3144,32 @@ function sagnacRotate(x, y, z, travelS) {
|
|
|
3144
3144
|
const s = Math.sin(theta);
|
|
3145
3145
|
return [x * c + y * s, -x * s + y * c, z];
|
|
3146
3146
|
}
|
|
3147
|
-
function satStateAtEmission(source, prn, recvTimeMs, rcv) {
|
|
3147
|
+
function satStateAtEmission(source, prn, recvTimeMs, rcv, pseudorangeM) {
|
|
3148
|
+
if (pseudorangeM !== void 0 && pseudorangeM > 1e6) {
|
|
3149
|
+
let tEmit = recvTimeMs - pseudorangeM / C_LIGHT * 1e3;
|
|
3150
|
+
const s0 = source.satState(prn, tEmit);
|
|
3151
|
+
if (!s0) return null;
|
|
3152
|
+
tEmit -= s0.clkS * 1e3;
|
|
3153
|
+
const s = source.satState(prn, tEmit);
|
|
3154
|
+
if (!s) return null;
|
|
3155
|
+
let travelS2 = 0.075;
|
|
3156
|
+
let pos = [s.x, s.y, s.z];
|
|
3157
|
+
for (let iter = 0; iter < 3; iter++) {
|
|
3158
|
+
pos = sagnacRotate(s.x, s.y, s.z, travelS2);
|
|
3159
|
+
travelS2 = Math.hypot(pos[0] - rcv[0], pos[1] - rcv[1], pos[2] - rcv[2]) / C_LIGHT;
|
|
3160
|
+
}
|
|
3161
|
+
const velDotPos2 = s.x * s.vx + s.y * s.vy + s.z * s.vz;
|
|
3162
|
+
const relS2 = -2 * velDotPos2 / (C_LIGHT * C_LIGHT);
|
|
3163
|
+
return {
|
|
3164
|
+
state: {
|
|
3165
|
+
x: pos[0],
|
|
3166
|
+
y: pos[1],
|
|
3167
|
+
z: pos[2],
|
|
3168
|
+
clkM: C_LIGHT * (s.clkS + relS2)
|
|
3169
|
+
},
|
|
3170
|
+
travelS: travelS2
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3148
3173
|
let travelS = 0.075;
|
|
3149
3174
|
let sx = 0;
|
|
3150
3175
|
let sy = 0;
|
|
@@ -3377,7 +3402,7 @@ var PppEngine = class {
|
|
|
3377
3402
|
const o = applySatBiases(source, raw, epoch.timeMs);
|
|
3378
3403
|
if (!o) continue;
|
|
3379
3404
|
const slip = detectSlip(o, ei);
|
|
3380
|
-
const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
|
|
3405
|
+
const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv, o.c1);
|
|
3381
3406
|
if (!sat) continue;
|
|
3382
3407
|
const [elRad, azRad] = getAer(
|
|
3383
3408
|
sat.state.x,
|
package/dist/index.cjs
CHANGED
|
@@ -17013,7 +17013,32 @@ function sagnacRotate(x, y, z, travelS) {
|
|
|
17013
17013
|
const s = Math.sin(theta);
|
|
17014
17014
|
return [x * c + y * s, -x * s + y * c, z];
|
|
17015
17015
|
}
|
|
17016
|
-
function satStateAtEmission(source, prn, recvTimeMs, rcv) {
|
|
17016
|
+
function satStateAtEmission(source, prn, recvTimeMs, rcv, pseudorangeM) {
|
|
17017
|
+
if (pseudorangeM !== void 0 && pseudorangeM > 1e6) {
|
|
17018
|
+
let tEmit = recvTimeMs - pseudorangeM / C_LIGHT * 1e3;
|
|
17019
|
+
const s0 = source.satState(prn, tEmit);
|
|
17020
|
+
if (!s0) return null;
|
|
17021
|
+
tEmit -= s0.clkS * 1e3;
|
|
17022
|
+
const s = source.satState(prn, tEmit);
|
|
17023
|
+
if (!s) return null;
|
|
17024
|
+
let travelS2 = 0.075;
|
|
17025
|
+
let pos = [s.x, s.y, s.z];
|
|
17026
|
+
for (let iter = 0; iter < 3; iter++) {
|
|
17027
|
+
pos = sagnacRotate(s.x, s.y, s.z, travelS2);
|
|
17028
|
+
travelS2 = Math.hypot(pos[0] - rcv[0], pos[1] - rcv[1], pos[2] - rcv[2]) / C_LIGHT;
|
|
17029
|
+
}
|
|
17030
|
+
const velDotPos2 = s.x * s.vx + s.y * s.vy + s.z * s.vz;
|
|
17031
|
+
const relS2 = -2 * velDotPos2 / (C_LIGHT * C_LIGHT);
|
|
17032
|
+
return {
|
|
17033
|
+
state: {
|
|
17034
|
+
x: pos[0],
|
|
17035
|
+
y: pos[1],
|
|
17036
|
+
z: pos[2],
|
|
17037
|
+
clkM: C_LIGHT * (s.clkS + relS2)
|
|
17038
|
+
},
|
|
17039
|
+
travelS: travelS2
|
|
17040
|
+
};
|
|
17041
|
+
}
|
|
17017
17042
|
let travelS = 0.075;
|
|
17018
17043
|
let sx = 0;
|
|
17019
17044
|
let sy = 0;
|
|
@@ -17246,7 +17271,7 @@ var PppEngine = class {
|
|
|
17246
17271
|
const o = applySatBiases(source, raw, epoch.timeMs);
|
|
17247
17272
|
if (!o) continue;
|
|
17248
17273
|
const slip = detectSlip(o, ei);
|
|
17249
|
-
const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
|
|
17274
|
+
const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv, o.c1);
|
|
17250
17275
|
if (!sat) continue;
|
|
17251
17276
|
const [elRad, azRad] = getAer(
|
|
17252
17277
|
sat.state.x,
|
package/dist/index.js
CHANGED
package/dist/positioning.cjs
CHANGED
|
@@ -3753,7 +3753,32 @@ function sagnacRotate(x, y, z, travelS) {
|
|
|
3753
3753
|
const s = Math.sin(theta);
|
|
3754
3754
|
return [x * c + y * s, -x * s + y * c, z];
|
|
3755
3755
|
}
|
|
3756
|
-
function satStateAtEmission(source, prn, recvTimeMs, rcv) {
|
|
3756
|
+
function satStateAtEmission(source, prn, recvTimeMs, rcv, pseudorangeM) {
|
|
3757
|
+
if (pseudorangeM !== void 0 && pseudorangeM > 1e6) {
|
|
3758
|
+
let tEmit = recvTimeMs - pseudorangeM / C_LIGHT * 1e3;
|
|
3759
|
+
const s0 = source.satState(prn, tEmit);
|
|
3760
|
+
if (!s0) return null;
|
|
3761
|
+
tEmit -= s0.clkS * 1e3;
|
|
3762
|
+
const s = source.satState(prn, tEmit);
|
|
3763
|
+
if (!s) return null;
|
|
3764
|
+
let travelS2 = 0.075;
|
|
3765
|
+
let pos = [s.x, s.y, s.z];
|
|
3766
|
+
for (let iter = 0; iter < 3; iter++) {
|
|
3767
|
+
pos = sagnacRotate(s.x, s.y, s.z, travelS2);
|
|
3768
|
+
travelS2 = Math.hypot(pos[0] - rcv[0], pos[1] - rcv[1], pos[2] - rcv[2]) / C_LIGHT;
|
|
3769
|
+
}
|
|
3770
|
+
const velDotPos2 = s.x * s.vx + s.y * s.vy + s.z * s.vz;
|
|
3771
|
+
const relS2 = -2 * velDotPos2 / (C_LIGHT * C_LIGHT);
|
|
3772
|
+
return {
|
|
3773
|
+
state: {
|
|
3774
|
+
x: pos[0],
|
|
3775
|
+
y: pos[1],
|
|
3776
|
+
z: pos[2],
|
|
3777
|
+
clkM: C_LIGHT * (s.clkS + relS2)
|
|
3778
|
+
},
|
|
3779
|
+
travelS: travelS2
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3757
3782
|
let travelS = 0.075;
|
|
3758
3783
|
let sx = 0;
|
|
3759
3784
|
let sy = 0;
|
|
@@ -3986,7 +4011,7 @@ var PppEngine = class {
|
|
|
3986
4011
|
const o = applySatBiases(source, raw, epoch.timeMs);
|
|
3987
4012
|
if (!o) continue;
|
|
3988
4013
|
const slip = detectSlip(o, ei);
|
|
3989
|
-
const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv);
|
|
4014
|
+
const sat = satStateAtEmission(source, o.prn, epoch.timeMs, rcv, o.c1);
|
|
3990
4015
|
if (!sat) continue;
|
|
3991
4016
|
const [elRad, azRad] = getAer(
|
|
3992
4017
|
sat.state.x,
|
package/dist/positioning.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnss-js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -109,6 +109,11 @@
|
|
|
109
109
|
"types": "./dist/osnma.d.ts",
|
|
110
110
|
"import": "./dist/osnma.js",
|
|
111
111
|
"require": "./dist/osnma.cjs"
|
|
112
|
+
},
|
|
113
|
+
"./android": {
|
|
114
|
+
"types": "./dist/android.d.ts",
|
|
115
|
+
"import": "./dist/android.js",
|
|
116
|
+
"require": "./dist/android.cjs"
|
|
112
117
|
}
|
|
113
118
|
},
|
|
114
119
|
"main": "./dist/index.cjs",
|