gnss-js 1.30.0 → 1.32.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.
@@ -5,6 +5,9 @@ import {
5
5
  import {
6
6
  GPS_PI
7
7
  } from "./chunk-IQ5OIMQD.js";
8
+ import {
9
+ C_LIGHT
10
+ } from "./chunk-FIEWO4J4.js";
8
11
 
9
12
  // src/rinex/crx-writer.ts
10
13
  var ARC_ORDER = 3;
@@ -1417,6 +1420,81 @@ function parseIonex(text) {
1417
1420
  return { epochs, lats, lons, maps };
1418
1421
  }
1419
1422
 
1423
+ // src/rinex/bias-sinex.ts
1424
+ function parseEpoch(s, openTo) {
1425
+ const m = /^(\d{4}):(\d{3}):(\d{5})$/.exec(s.trim());
1426
+ if (!m) return openTo;
1427
+ const y = +m[1];
1428
+ if (y === 0) return openTo;
1429
+ const doy = +m[2];
1430
+ const sod = +m[3];
1431
+ return Date.UTC(y, 0, 1) + (doy - 1) * 864e5 + sod * 1e3;
1432
+ }
1433
+ function parseBiasSinex(text) {
1434
+ const records = [];
1435
+ let inBlock = false;
1436
+ for (const line of text.split("\n")) {
1437
+ if (line.startsWith("+BIAS/SOLUTION")) {
1438
+ inBlock = true;
1439
+ continue;
1440
+ }
1441
+ if (line.startsWith("-BIAS/SOLUTION")) {
1442
+ inBlock = false;
1443
+ continue;
1444
+ }
1445
+ if (!inBlock || line.length < 65 || line.startsWith("*")) continue;
1446
+ const type = line.slice(1, 4).trim();
1447
+ if (type !== "DSB" && type !== "OSB" && type !== "ISB") continue;
1448
+ const prn = line.slice(11, 14).trim();
1449
+ const station = line.slice(15, 24).trim();
1450
+ const obs1 = line.slice(25, 29).trim();
1451
+ const obs2 = line.slice(30, 34).trim();
1452
+ const startMs = parseEpoch(line.slice(35, 49), -Infinity);
1453
+ const endMs = parseEpoch(line.slice(50, 64), Infinity);
1454
+ const tail = line.slice(65).trim().split(/\s+/);
1455
+ if (tail.length < 2) continue;
1456
+ const unit = tail[0];
1457
+ const value = Number(tail[1]);
1458
+ if (!Number.isFinite(value)) continue;
1459
+ records.push({
1460
+ type,
1461
+ prn,
1462
+ station,
1463
+ obs1,
1464
+ obs2,
1465
+ startMs,
1466
+ endMs,
1467
+ value,
1468
+ unit
1469
+ });
1470
+ }
1471
+ const bySat = /* @__PURE__ */ new Map();
1472
+ for (const r of records) {
1473
+ if (!r.prn || r.station) continue;
1474
+ let a = bySat.get(r.prn);
1475
+ if (!a) {
1476
+ a = [];
1477
+ bySat.set(r.prn, a);
1478
+ }
1479
+ a.push(r);
1480
+ }
1481
+ return { records, bySat };
1482
+ }
1483
+ function biasMetres(r) {
1484
+ return r.unit === "ns" ? r.value * 1e-9 * C_LIGHT : r.value;
1485
+ }
1486
+ function findSatBias(bias, prn, obs1, obs2, tMs) {
1487
+ const recs = bias.bySat.get(prn);
1488
+ if (!recs) return null;
1489
+ for (const r of recs) {
1490
+ if (r.obs1 !== obs1) continue;
1491
+ if (obs2 === void 0 ? r.obs2 !== "" : r.obs2 !== obs2) continue;
1492
+ if (tMs != null && (tMs < r.startMs || tMs > r.endMs)) continue;
1493
+ return r;
1494
+ }
1495
+ return null;
1496
+ }
1497
+
1420
1498
  // src/rinex/obs-writer-common.ts
1421
1499
  function createGzipLineSink() {
1422
1500
  const encoder = new TextEncoder();
@@ -1804,6 +1882,9 @@ export {
1804
1882
  writeRinexNav,
1805
1883
  writeRinexNav4,
1806
1884
  parseIonex,
1885
+ parseBiasSinex,
1886
+ biasMetres,
1887
+ findSatBias,
1807
1888
  createGzipLineSink,
1808
1889
  stationHeaderLines,
1809
1890
  writeRinexObsBlob,
@@ -1911,6 +1911,212 @@ function solvePpp(epochs, sp3, opts) {
1911
1911
  };
1912
1912
  }
1913
1913
 
1914
+ // src/positioning/ppp-ar.ts
1915
+ function wlWavelength(f1, f2) {
1916
+ return C_LIGHT / (f1 - f2);
1917
+ }
1918
+ function nlWavelength(f1, f2) {
1919
+ return C_LIGHT / (f1 + f2);
1920
+ }
1921
+ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
1922
+ const wlThreshold = opts.wlThreshold ?? 0.25;
1923
+ const ratioThreshold = opts.ratioThreshold ?? 3;
1924
+ const kept = [];
1925
+ const nWl = [];
1926
+ const rejectedWl = [];
1927
+ for (let i = 0; i < sats.length; i++) {
1928
+ const s = sats[i];
1929
+ const wlFloat = s.mwCyc - (s.wlBiasCyc ?? 0);
1930
+ const r = Math.round(wlFloat);
1931
+ if (Math.abs(wlFloat - r) <= wlThreshold) {
1932
+ kept.push(i);
1933
+ nWl.push(r);
1934
+ } else {
1935
+ rejectedWl.push(s.prn);
1936
+ }
1937
+ }
1938
+ if (kept.length < 2) {
1939
+ return {
1940
+ fixed: false,
1941
+ ratio: 0,
1942
+ refPrn: null,
1943
+ sats: [],
1944
+ rejectedWl
1945
+ };
1946
+ }
1947
+ const n1Float = [];
1948
+ const lamNl = [];
1949
+ for (let k = 0; k < kept.length; k++) {
1950
+ const s = sats[kept[k]];
1951
+ const lNl = nlWavelength(s.f1, s.f2);
1952
+ lamNl.push(lNl);
1953
+ const n1 = s.aIF / lNl - s.f2 / (s.f1 - s.f2) * nWl[k] - (s.nlBiasCyc ?? 0);
1954
+ n1Float.push(n1);
1955
+ }
1956
+ let ref = 0;
1957
+ for (let k = 1; k < kept.length; k++) {
1958
+ const e = sats[kept[k]].elevDeg ?? -1;
1959
+ if (e > (sats[kept[ref]].elevDeg ?? -1)) ref = k;
1960
+ }
1961
+ const m = kept.length - 1;
1962
+ const sd = new Float64Array(m);
1963
+ const Qsd = new Float64Array(m * m);
1964
+ const others = [];
1965
+ for (let k = 0; k < kept.length; k++) if (k !== ref) others.push(k);
1966
+ const n = sats.length;
1967
+ const qN1 = (ka, kb) => Qaif[kept[ka] * n + kept[kb]] / (lamNl[ka] * lamNl[kb]);
1968
+ for (let a = 0; a < m; a++) {
1969
+ const ka = others[a];
1970
+ sd[a] = n1Float[ka] - n1Float[ref];
1971
+ for (let b = 0; b < m; b++) {
1972
+ const kb = others[b];
1973
+ Qsd[a * m + b] = qN1(ka, kb) - qN1(ka, ref) - qN1(ref, kb) + qN1(ref, ref);
1974
+ }
1975
+ }
1976
+ const res = lambdaSearch(sd, Qsd, m, 2);
1977
+ const ratio = res ? Math.min(res.ratio, 999.9) : 0;
1978
+ const fixed = !!res && ratio >= ratioThreshold;
1979
+ const refN1 = Math.round(n1Float[ref]);
1980
+ const sdFix = res?.candidates[0];
1981
+ const outSats = [];
1982
+ for (let k = 0; k < kept.length; k++) {
1983
+ const s = sats[kept[k]];
1984
+ let n1;
1985
+ if (k === ref) {
1986
+ n1 = refN1;
1987
+ } else {
1988
+ const a = others.indexOf(k);
1989
+ n1 = fixed && sdFix ? refN1 + Math.round(sdFix[a]) : Math.round(n1Float[k]);
1990
+ }
1991
+ const lNl = lamNl[k];
1992
+ const aIF = lNl * n1 + lNl * (s.f2 / (s.f1 - s.f2)) * nWl[k];
1993
+ outSats.push({ prn: s.prn, nWl: nWl[k], n1, aIF });
1994
+ }
1995
+ return {
1996
+ fixed,
1997
+ ratio,
1998
+ refPrn: sats[kept[ref]].prn,
1999
+ sats: outSats,
2000
+ rejectedWl
2001
+ };
2002
+ }
2003
+
2004
+ // src/positioning/ppp-fcb.ts
2005
+ function extractWidelaneArcs(station, epochs, opts = {}) {
2006
+ const slipCyc = opts.slipCyc ?? 4;
2007
+ const gapMs = opts.gapMs ?? 12e4;
2008
+ const minObs = opts.minObs ?? 20;
2009
+ const state = /* @__PURE__ */ new Map();
2010
+ const out = [];
2011
+ const flush = (prn) => {
2012
+ const s = state.get(prn);
2013
+ if (s && s.n >= minObs)
2014
+ out.push({ station, prn, wlFloat: s.sum / s.n, nObs: s.n });
2015
+ };
2016
+ for (const ep of epochs) {
2017
+ for (const o of ep.obs) {
2018
+ if (!o.c1 || !o.c2 || !o.l1 || !o.l2) continue;
2019
+ const lamW = C_LIGHT / (o.f1 - o.f2);
2020
+ const codeNl = (o.f1 * o.c1 + o.f2 * o.c2) / (o.f1 + o.f2);
2021
+ const mw = o.l1 - o.l2 - codeNl / lamW;
2022
+ const s = state.get(o.prn);
2023
+ if (!s || ep.timeMs - s.lastT > gapMs || Math.abs(mw - s.mean) > slipCyc) {
2024
+ if (s) flush(o.prn);
2025
+ state.set(o.prn, { sum: mw, n: 1, mean: mw, lastT: ep.timeMs });
2026
+ } else {
2027
+ s.sum += mw;
2028
+ s.n += 1;
2029
+ s.mean = s.sum / s.n;
2030
+ s.lastT = ep.timeMs;
2031
+ }
2032
+ }
2033
+ }
2034
+ for (const prn of state.keys()) flush(prn);
2035
+ return out;
2036
+ }
2037
+ function wrapHalf(x) {
2038
+ return x - Math.round(x);
2039
+ }
2040
+ function circMean(vals, weights) {
2041
+ let s = 0;
2042
+ let c = 0;
2043
+ for (let i = 0; i < vals.length; i++) {
2044
+ const a = 2 * Math.PI * vals[i];
2045
+ s += weights[i] * Math.sin(a);
2046
+ c += weights[i] * Math.cos(a);
2047
+ }
2048
+ if (s === 0 && c === 0) return 0;
2049
+ return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
2050
+ }
2051
+ function estimateWidelaneFcb(arcs, opts = {}) {
2052
+ const maxIter = opts.maxIter ?? 50;
2053
+ const tol = opts.tol ?? 1e-4;
2054
+ const fixWindow = opts.fixWindow ?? 0.25;
2055
+ const sats = [...new Set(arcs.map((a) => a.prn))].sort();
2056
+ const stations = [...new Set(arcs.map((a) => a.station))].sort();
2057
+ const refStation = stations[0] ?? "";
2058
+ const satFcb = new Map(sats.map((s) => [s, 0]));
2059
+ const rcvBias = new Map(stations.map((s) => [s, 0]));
2060
+ const nInt = new Array(arcs.length).fill(0);
2061
+ let iterations = 0;
2062
+ for (let it = 0; it < maxIter; it++) {
2063
+ iterations = it + 1;
2064
+ for (let i = 0; i < arcs.length; i++) {
2065
+ const a = arcs[i];
2066
+ nInt[i] = Math.round(
2067
+ a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
2068
+ );
2069
+ }
2070
+ let maxChange = 0;
2071
+ for (const s of sats) {
2072
+ const vals = [];
2073
+ const w = [];
2074
+ for (let i = 0; i < arcs.length; i++) {
2075
+ const a = arcs[i];
2076
+ if (a.prn !== s) continue;
2077
+ vals.push(a.wlFloat - nInt[i] - rcvBias.get(a.station));
2078
+ w.push(a.nObs);
2079
+ }
2080
+ if (!vals.length) continue;
2081
+ const nv = circMean(vals, w);
2082
+ maxChange = Math.max(maxChange, Math.abs(wrapHalf(nv - satFcb.get(s))));
2083
+ satFcb.set(s, nv);
2084
+ }
2085
+ for (const r of stations) {
2086
+ const vals = [];
2087
+ const w = [];
2088
+ for (let i = 0; i < arcs.length; i++) {
2089
+ const a = arcs[i];
2090
+ if (a.station !== r) continue;
2091
+ vals.push(a.wlFloat - nInt[i] - satFcb.get(a.prn));
2092
+ w.push(a.nObs);
2093
+ }
2094
+ if (!vals.length) continue;
2095
+ rcvBias.set(r, circMean(vals, w));
2096
+ }
2097
+ const shift = rcvBias.get(refStation);
2098
+ if (shift !== 0) {
2099
+ for (const r of stations)
2100
+ rcvBias.set(r, wrapHalf(rcvBias.get(r) - shift));
2101
+ for (const s of sats) satFcb.set(s, wrapHalf(satFcb.get(s) + shift));
2102
+ }
2103
+ if (maxChange < tol) break;
2104
+ }
2105
+ let sumSq = 0;
2106
+ let fixed = 0;
2107
+ for (let i = 0; i < arcs.length; i++) {
2108
+ const a = arcs[i];
2109
+ const resid = wrapHalf(
2110
+ a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
2111
+ );
2112
+ sumSq += resid * resid;
2113
+ if (Math.abs(resid) <= fixWindow) fixed++;
2114
+ }
2115
+ const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
2116
+ const fixRate = arcs.length ? fixed / arcs.length : 0;
2117
+ return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
2118
+ }
2119
+
1914
2120
  // src/positioning/index.ts
1915
2121
  var GM_GPS = 3986005e8;
1916
2122
  var F_REL = -4442807633e-19;
@@ -2157,6 +2363,11 @@ export {
2157
2363
  buildPppAntenna,
2158
2364
  createPppCorrections,
2159
2365
  solvePpp,
2366
+ wlWavelength,
2367
+ nlWavelength,
2368
+ resolvePppAmbiguities,
2369
+ extractWidelaneArcs,
2370
+ estimateWidelaneFcb,
2160
2371
  satClockCorrection,
2161
2372
  ionoFree,
2162
2373
  solveSpp
package/dist/index.cjs CHANGED
@@ -111,6 +111,7 @@ __export(src_exports, {
111
111
  analyzeQuality: () => analyzeQuality,
112
112
  applyHelmert: () => applyHelmert,
113
113
  applyIonoDcb: () => applyIonoDcb,
114
+ biasMetres: () => biasMetres,
114
115
  binexCrc16: () => binexCrc16,
115
116
  binexCrc32: () => binexCrc32,
116
117
  binexCsum8: () => binexCsum8,
@@ -148,8 +149,11 @@ __export(src_exports, {
148
149
  ecefToAzEl: () => ecefToAzEl,
149
150
  ecefToGeodetic: () => ecefToGeodetic,
150
151
  ephInfoToEphemeris: () => ephInfoToEphemeris,
152
+ estimateWidelaneFcb: () => estimateWidelaneFcb,
151
153
  euclidean3D: () => euclidean3D,
154
+ extractWidelaneArcs: () => extractWidelaneArcs,
152
155
  fetchSourcetable: () => fetchSourcetable,
156
+ findSatBias: () => findSatBias,
153
157
  fmtD: () => fmtD,
154
158
  fmtF: () => fmtF,
155
159
  formatUTCTime: () => formatUTCTime,
@@ -232,11 +236,13 @@ __export(src_exports, {
232
236
  msmEpochToDate: () => msmEpochToDate,
233
237
  navTimesFromEph: () => navTimesFromEph,
234
238
  niellMapping: () => niellMapping,
239
+ nlWavelength: () => nlWavelength,
235
240
  nmeaCoordToDecimal: () => nmeaCoordToDecimal,
236
241
  oem4Frames: () => oem4Frames,
237
242
  padL: () => padL,
238
243
  padR: () => padR,
239
244
  parseAntex: () => parseAntex,
245
+ parseBiasSinex: () => parseBiasSinex,
240
246
  parseBinex: () => parseBinex,
241
247
  parseClk: () => parseClk,
242
248
  parseCrxDataLine: () => parseCrxDataLine,
@@ -274,6 +280,7 @@ __export(src_exports, {
274
280
  readString: () => readString,
275
281
  reportDecodeError: () => reportDecodeError,
276
282
  resetGloFreqCache: () => resetGloFreqCache,
283
+ resolvePppAmbiguities: () => resolvePppAmbiguities,
277
284
  rhumbLine: () => rhumbLine,
278
285
  rtcm3Constellation: () => rtcm3Constellation,
279
286
  satClockCorrection: () => satClockCorrection,
@@ -299,6 +306,7 @@ __export(src_exports, {
299
306
  updateStreamStats: () => updateStreamStats,
300
307
  verifyChecksum: () => verifyChecksum,
301
308
  vincenty: () => vincenty,
309
+ wlWavelength: () => wlWavelength,
302
310
  writeCrx: () => writeCrx,
303
311
  writeModernObsBlob: () => writeModernObsBlob,
304
312
  writeRinex2ObsBlob: () => writeRinex2ObsBlob,
@@ -3958,6 +3966,81 @@ function clkBias(clk, prn, tMs) {
3958
3966
  return c[lo] + f * (c[lo + 1] - c[lo]);
3959
3967
  }
3960
3968
 
3969
+ // src/rinex/bias-sinex.ts
3970
+ function parseEpoch(s, openTo) {
3971
+ const m = /^(\d{4}):(\d{3}):(\d{5})$/.exec(s.trim());
3972
+ if (!m) return openTo;
3973
+ const y = +m[1];
3974
+ if (y === 0) return openTo;
3975
+ const doy = +m[2];
3976
+ const sod = +m[3];
3977
+ return Date.UTC(y, 0, 1) + (doy - 1) * 864e5 + sod * 1e3;
3978
+ }
3979
+ function parseBiasSinex(text) {
3980
+ const records = [];
3981
+ let inBlock = false;
3982
+ for (const line of text.split("\n")) {
3983
+ if (line.startsWith("+BIAS/SOLUTION")) {
3984
+ inBlock = true;
3985
+ continue;
3986
+ }
3987
+ if (line.startsWith("-BIAS/SOLUTION")) {
3988
+ inBlock = false;
3989
+ continue;
3990
+ }
3991
+ if (!inBlock || line.length < 65 || line.startsWith("*")) continue;
3992
+ const type = line.slice(1, 4).trim();
3993
+ if (type !== "DSB" && type !== "OSB" && type !== "ISB") continue;
3994
+ const prn = line.slice(11, 14).trim();
3995
+ const station = line.slice(15, 24).trim();
3996
+ const obs1 = line.slice(25, 29).trim();
3997
+ const obs2 = line.slice(30, 34).trim();
3998
+ const startMs = parseEpoch(line.slice(35, 49), -Infinity);
3999
+ const endMs = parseEpoch(line.slice(50, 64), Infinity);
4000
+ const tail = line.slice(65).trim().split(/\s+/);
4001
+ if (tail.length < 2) continue;
4002
+ const unit = tail[0];
4003
+ const value = Number(tail[1]);
4004
+ if (!Number.isFinite(value)) continue;
4005
+ records.push({
4006
+ type,
4007
+ prn,
4008
+ station,
4009
+ obs1,
4010
+ obs2,
4011
+ startMs,
4012
+ endMs,
4013
+ value,
4014
+ unit
4015
+ });
4016
+ }
4017
+ const bySat = /* @__PURE__ */ new Map();
4018
+ for (const r of records) {
4019
+ if (!r.prn || r.station) continue;
4020
+ let a = bySat.get(r.prn);
4021
+ if (!a) {
4022
+ a = [];
4023
+ bySat.set(r.prn, a);
4024
+ }
4025
+ a.push(r);
4026
+ }
4027
+ return { records, bySat };
4028
+ }
4029
+ function biasMetres(r) {
4030
+ return r.unit === "ns" ? r.value * 1e-9 * C_LIGHT : r.value;
4031
+ }
4032
+ function findSatBias(bias, prn, obs1, obs2, tMs) {
4033
+ const recs = bias.bySat.get(prn);
4034
+ if (!recs) return null;
4035
+ for (const r of recs) {
4036
+ if (r.obs1 !== obs1) continue;
4037
+ if (obs2 === void 0 ? r.obs2 !== "" : r.obs2 !== obs2) continue;
4038
+ if (tMs != null && (tMs < r.startMs || tMs > r.endMs)) continue;
4039
+ return r;
4040
+ }
4041
+ return null;
4042
+ }
4043
+
3961
4044
  // src/rinex/obs-writer-common.ts
3962
4045
  function createGzipLineSink() {
3963
4046
  const encoder = new TextEncoder();
@@ -13224,6 +13307,212 @@ function solvePpp(epochs, sp3, opts) {
13224
13307
  };
13225
13308
  }
13226
13309
 
13310
+ // src/positioning/ppp-ar.ts
13311
+ function wlWavelength(f1, f2) {
13312
+ return C_LIGHT / (f1 - f2);
13313
+ }
13314
+ function nlWavelength(f1, f2) {
13315
+ return C_LIGHT / (f1 + f2);
13316
+ }
13317
+ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
13318
+ const wlThreshold = opts.wlThreshold ?? 0.25;
13319
+ const ratioThreshold = opts.ratioThreshold ?? 3;
13320
+ const kept = [];
13321
+ const nWl = [];
13322
+ const rejectedWl = [];
13323
+ for (let i = 0; i < sats.length; i++) {
13324
+ const s = sats[i];
13325
+ const wlFloat = s.mwCyc - (s.wlBiasCyc ?? 0);
13326
+ const r = Math.round(wlFloat);
13327
+ if (Math.abs(wlFloat - r) <= wlThreshold) {
13328
+ kept.push(i);
13329
+ nWl.push(r);
13330
+ } else {
13331
+ rejectedWl.push(s.prn);
13332
+ }
13333
+ }
13334
+ if (kept.length < 2) {
13335
+ return {
13336
+ fixed: false,
13337
+ ratio: 0,
13338
+ refPrn: null,
13339
+ sats: [],
13340
+ rejectedWl
13341
+ };
13342
+ }
13343
+ const n1Float = [];
13344
+ const lamNl = [];
13345
+ for (let k = 0; k < kept.length; k++) {
13346
+ const s = sats[kept[k]];
13347
+ const lNl = nlWavelength(s.f1, s.f2);
13348
+ lamNl.push(lNl);
13349
+ const n1 = s.aIF / lNl - s.f2 / (s.f1 - s.f2) * nWl[k] - (s.nlBiasCyc ?? 0);
13350
+ n1Float.push(n1);
13351
+ }
13352
+ let ref = 0;
13353
+ for (let k = 1; k < kept.length; k++) {
13354
+ const e = sats[kept[k]].elevDeg ?? -1;
13355
+ if (e > (sats[kept[ref]].elevDeg ?? -1)) ref = k;
13356
+ }
13357
+ const m = kept.length - 1;
13358
+ const sd2 = new Float64Array(m);
13359
+ const Qsd = new Float64Array(m * m);
13360
+ const others = [];
13361
+ for (let k = 0; k < kept.length; k++) if (k !== ref) others.push(k);
13362
+ const n = sats.length;
13363
+ const qN1 = (ka, kb) => Qaif[kept[ka] * n + kept[kb]] / (lamNl[ka] * lamNl[kb]);
13364
+ for (let a = 0; a < m; a++) {
13365
+ const ka = others[a];
13366
+ sd2[a] = n1Float[ka] - n1Float[ref];
13367
+ for (let b = 0; b < m; b++) {
13368
+ const kb = others[b];
13369
+ Qsd[a * m + b] = qN1(ka, kb) - qN1(ka, ref) - qN1(ref, kb) + qN1(ref, ref);
13370
+ }
13371
+ }
13372
+ const res = lambdaSearch(sd2, Qsd, m, 2);
13373
+ const ratio = res ? Math.min(res.ratio, 999.9) : 0;
13374
+ const fixed = !!res && ratio >= ratioThreshold;
13375
+ const refN1 = Math.round(n1Float[ref]);
13376
+ const sdFix = res?.candidates[0];
13377
+ const outSats = [];
13378
+ for (let k = 0; k < kept.length; k++) {
13379
+ const s = sats[kept[k]];
13380
+ let n1;
13381
+ if (k === ref) {
13382
+ n1 = refN1;
13383
+ } else {
13384
+ const a = others.indexOf(k);
13385
+ n1 = fixed && sdFix ? refN1 + Math.round(sdFix[a]) : Math.round(n1Float[k]);
13386
+ }
13387
+ const lNl = lamNl[k];
13388
+ const aIF = lNl * n1 + lNl * (s.f2 / (s.f1 - s.f2)) * nWl[k];
13389
+ outSats.push({ prn: s.prn, nWl: nWl[k], n1, aIF });
13390
+ }
13391
+ return {
13392
+ fixed,
13393
+ ratio,
13394
+ refPrn: sats[kept[ref]].prn,
13395
+ sats: outSats,
13396
+ rejectedWl
13397
+ };
13398
+ }
13399
+
13400
+ // src/positioning/ppp-fcb.ts
13401
+ function extractWidelaneArcs(station, epochs, opts = {}) {
13402
+ const slipCyc = opts.slipCyc ?? 4;
13403
+ const gapMs = opts.gapMs ?? 12e4;
13404
+ const minObs = opts.minObs ?? 20;
13405
+ const state = /* @__PURE__ */ new Map();
13406
+ const out = [];
13407
+ const flush = (prn) => {
13408
+ const s = state.get(prn);
13409
+ if (s && s.n >= minObs)
13410
+ out.push({ station, prn, wlFloat: s.sum / s.n, nObs: s.n });
13411
+ };
13412
+ for (const ep of epochs) {
13413
+ for (const o of ep.obs) {
13414
+ if (!o.c1 || !o.c2 || !o.l1 || !o.l2) continue;
13415
+ const lamW = C_LIGHT / (o.f1 - o.f2);
13416
+ const codeNl = (o.f1 * o.c1 + o.f2 * o.c2) / (o.f1 + o.f2);
13417
+ const mw = o.l1 - o.l2 - codeNl / lamW;
13418
+ const s = state.get(o.prn);
13419
+ if (!s || ep.timeMs - s.lastT > gapMs || Math.abs(mw - s.mean) > slipCyc) {
13420
+ if (s) flush(o.prn);
13421
+ state.set(o.prn, { sum: mw, n: 1, mean: mw, lastT: ep.timeMs });
13422
+ } else {
13423
+ s.sum += mw;
13424
+ s.n += 1;
13425
+ s.mean = s.sum / s.n;
13426
+ s.lastT = ep.timeMs;
13427
+ }
13428
+ }
13429
+ }
13430
+ for (const prn of state.keys()) flush(prn);
13431
+ return out;
13432
+ }
13433
+ function wrapHalf(x) {
13434
+ return x - Math.round(x);
13435
+ }
13436
+ function circMean(vals, weights) {
13437
+ let s = 0;
13438
+ let c = 0;
13439
+ for (let i = 0; i < vals.length; i++) {
13440
+ const a = 2 * Math.PI * vals[i];
13441
+ s += weights[i] * Math.sin(a);
13442
+ c += weights[i] * Math.cos(a);
13443
+ }
13444
+ if (s === 0 && c === 0) return 0;
13445
+ return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
13446
+ }
13447
+ function estimateWidelaneFcb(arcs, opts = {}) {
13448
+ const maxIter = opts.maxIter ?? 50;
13449
+ const tol = opts.tol ?? 1e-4;
13450
+ const fixWindow = opts.fixWindow ?? 0.25;
13451
+ const sats = [...new Set(arcs.map((a) => a.prn))].sort();
13452
+ const stations = [...new Set(arcs.map((a) => a.station))].sort();
13453
+ const refStation = stations[0] ?? "";
13454
+ const satFcb = new Map(sats.map((s) => [s, 0]));
13455
+ const rcvBias = new Map(stations.map((s) => [s, 0]));
13456
+ const nInt = new Array(arcs.length).fill(0);
13457
+ let iterations = 0;
13458
+ for (let it = 0; it < maxIter; it++) {
13459
+ iterations = it + 1;
13460
+ for (let i = 0; i < arcs.length; i++) {
13461
+ const a = arcs[i];
13462
+ nInt[i] = Math.round(
13463
+ a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
13464
+ );
13465
+ }
13466
+ let maxChange = 0;
13467
+ for (const s of sats) {
13468
+ const vals = [];
13469
+ const w = [];
13470
+ for (let i = 0; i < arcs.length; i++) {
13471
+ const a = arcs[i];
13472
+ if (a.prn !== s) continue;
13473
+ vals.push(a.wlFloat - nInt[i] - rcvBias.get(a.station));
13474
+ w.push(a.nObs);
13475
+ }
13476
+ if (!vals.length) continue;
13477
+ const nv = circMean(vals, w);
13478
+ maxChange = Math.max(maxChange, Math.abs(wrapHalf(nv - satFcb.get(s))));
13479
+ satFcb.set(s, nv);
13480
+ }
13481
+ for (const r of stations) {
13482
+ const vals = [];
13483
+ const w = [];
13484
+ for (let i = 0; i < arcs.length; i++) {
13485
+ const a = arcs[i];
13486
+ if (a.station !== r) continue;
13487
+ vals.push(a.wlFloat - nInt[i] - satFcb.get(a.prn));
13488
+ w.push(a.nObs);
13489
+ }
13490
+ if (!vals.length) continue;
13491
+ rcvBias.set(r, circMean(vals, w));
13492
+ }
13493
+ const shift = rcvBias.get(refStation);
13494
+ if (shift !== 0) {
13495
+ for (const r of stations)
13496
+ rcvBias.set(r, wrapHalf(rcvBias.get(r) - shift));
13497
+ for (const s of sats) satFcb.set(s, wrapHalf(satFcb.get(s) + shift));
13498
+ }
13499
+ if (maxChange < tol) break;
13500
+ }
13501
+ let sumSq = 0;
13502
+ let fixed = 0;
13503
+ for (let i = 0; i < arcs.length; i++) {
13504
+ const a = arcs[i];
13505
+ const resid = wrapHalf(
13506
+ a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
13507
+ );
13508
+ sumSq += resid * resid;
13509
+ if (Math.abs(resid) <= fixWindow) fixed++;
13510
+ }
13511
+ const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
13512
+ const fixRate = arcs.length ? fixed / arcs.length : 0;
13513
+ return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
13514
+ }
13515
+
13227
13516
  // src/positioning/index.ts
13228
13517
  var GM_GPS3 = 3986005e8;
13229
13518
  var F_REL = -4442807633e-19;
@@ -15803,6 +16092,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
15803
16092
  analyzeQuality,
15804
16093
  applyHelmert,
15805
16094
  applyIonoDcb,
16095
+ biasMetres,
15806
16096
  binexCrc16,
15807
16097
  binexCrc32,
15808
16098
  binexCsum8,
@@ -15840,8 +16130,11 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
15840
16130
  ecefToAzEl,
15841
16131
  ecefToGeodetic,
15842
16132
  ephInfoToEphemeris,
16133
+ estimateWidelaneFcb,
15843
16134
  euclidean3D,
16135
+ extractWidelaneArcs,
15844
16136
  fetchSourcetable,
16137
+ findSatBias,
15845
16138
  fmtD,
15846
16139
  fmtF,
15847
16140
  formatUTCTime,
@@ -15924,11 +16217,13 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
15924
16217
  msmEpochToDate,
15925
16218
  navTimesFromEph,
15926
16219
  niellMapping,
16220
+ nlWavelength,
15927
16221
  nmeaCoordToDecimal,
15928
16222
  oem4Frames,
15929
16223
  padL,
15930
16224
  padR,
15931
16225
  parseAntex,
16226
+ parseBiasSinex,
15932
16227
  parseBinex,
15933
16228
  parseClk,
15934
16229
  parseCrxDataLine,
@@ -15966,6 +16261,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
15966
16261
  readString,
15967
16262
  reportDecodeError,
15968
16263
  resetGloFreqCache,
16264
+ resolvePppAmbiguities,
15969
16265
  rhumbLine,
15970
16266
  rtcm3Constellation,
15971
16267
  satClockCorrection,
@@ -15991,6 +16287,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
15991
16287
  updateStreamStats,
15992
16288
  verifyChecksum,
15993
16289
  vincenty,
16290
+ wlWavelength,
15994
16291
  writeCrx,
15995
16292
  writeModernObsBlob,
15996
16293
  writeRinex2ObsBlob,