gnss-js 1.31.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.
@@ -2001,6 +2001,122 @@ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
2001
2001
  };
2002
2002
  }
2003
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
+
2004
2120
  // src/positioning/index.ts
2005
2121
  var GM_GPS = 3986005e8;
2006
2122
  var F_REL = -4442807633e-19;
@@ -2250,6 +2366,8 @@ export {
2250
2366
  wlWavelength,
2251
2367
  nlWavelength,
2252
2368
  resolvePppAmbiguities,
2369
+ extractWidelaneArcs,
2370
+ estimateWidelaneFcb,
2253
2371
  satClockCorrection,
2254
2372
  ionoFree,
2255
2373
  solveSpp
package/dist/index.cjs CHANGED
@@ -149,7 +149,9 @@ __export(src_exports, {
149
149
  ecefToAzEl: () => ecefToAzEl,
150
150
  ecefToGeodetic: () => ecefToGeodetic,
151
151
  ephInfoToEphemeris: () => ephInfoToEphemeris,
152
+ estimateWidelaneFcb: () => estimateWidelaneFcb,
152
153
  euclidean3D: () => euclidean3D,
154
+ extractWidelaneArcs: () => extractWidelaneArcs,
153
155
  fetchSourcetable: () => fetchSourcetable,
154
156
  findSatBias: () => findSatBias,
155
157
  fmtD: () => fmtD,
@@ -13395,6 +13397,122 @@ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
13395
13397
  };
13396
13398
  }
13397
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
+
13398
13516
  // src/positioning/index.ts
13399
13517
  var GM_GPS3 = 3986005e8;
13400
13518
  var F_REL = -4442807633e-19;
@@ -16012,7 +16130,9 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16012
16130
  ecefToAzEl,
16013
16131
  ecefToGeodetic,
16014
16132
  ephInfoToEphemeris,
16133
+ estimateWidelaneFcb,
16015
16134
  euclidean3D,
16135
+ extractWidelaneArcs,
16016
16136
  fetchSourcetable,
16017
16137
  findSatBias,
16018
16138
  fmtD,
package/dist/index.d.cts CHANGED
@@ -17,7 +17,7 @@ export { TrimbleEpoch, TrimbleFrame, TrimbleMeasurement, TrimbleNavResult, Trimb
17
17
  export { BinexEpoch, BinexMeasurement, BinexParseResult, BinexRecord, binexCrc16, binexCrc32, binexCsum8, binexRecords, decodeBinexEph, getBnxi, parseBinex } from './binex.cjs';
18
18
  export { AllPositionsData, AlmanacPosition, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.cjs';
19
19
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.cjs';
20
- export { AntennaOffset, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, PppAntennaModel, PppCorrectionConfig, PppCorrections, PppEpoch, PppEpochContext, PppEpochResult, PppOptions, PppSatObs, PppSolution, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SatGeom, SppOptions, SppSolution, TropoMapping, buildPppAntenna, createPppCorrections, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength } from './positioning.cjs';
20
+ export { AntennaOffset, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, PppAntennaModel, PppCorrectionConfig, PppCorrections, PppEpoch, PppEpochContext, PppEpochResult, PppOptions, PppSatObs, PppSolution, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SatGeom, SppOptions, SppSolution, TropoMapping, WlArc, WlFcbOptions, WlFcbResult, WlObs, buildPppAntenna, createPppCorrections, estimateWidelaneFcb, extractWidelaneArcs, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength } from './positioning.cjs';
21
21
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.cjs';
22
22
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.cjs';
23
23
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.cjs';
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { TrimbleEpoch, TrimbleFrame, TrimbleMeasurement, TrimbleNavResult, Trimb
17
17
  export { BinexEpoch, BinexMeasurement, BinexParseResult, BinexRecord, binexCrc16, binexCrc32, binexCsum8, binexRecords, decodeBinexEph, getBnxi, parseBinex } from './binex.js';
18
18
  export { AllPositionsData, AlmanacPosition, DopValues, EpochSkyData, SatAzEl, SatPoint, SatPosition, VisibilityPass, VisibilityResult, almanacEpochMs, almanacSatPosition, computeAllPositions, computeDop, computeLiveSkyPositions, computeSatPosition, computeVisibility, computeVisibilityFromPositions, dopplerHz, ecefToAzEl, ephInfoToEphemeris, glonassAlmanacEpochMs, glonassAlmanacPosition, glonassPosition, keplerPosition, maskRadForAzimuth, navTimesFromEph, rangeRate, selectEphemeris } from './orbit.js';
19
19
  export { Helmert14, REFERENCE_FRAMES, ReferenceFrame, applyHelmert, dateToEpoch, transformFrame } from './frames.js';
20
- export { AntennaOffset, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, PppAntennaModel, PppCorrectionConfig, PppCorrections, PppEpoch, PppEpochContext, PppEpochResult, PppOptions, PppSatObs, PppSolution, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SatGeom, SppOptions, SppSolution, TropoMapping, buildPppAntenna, createPppCorrections, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength } from './positioning.js';
20
+ export { AntennaOffset, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, PppAntennaModel, PppCorrectionConfig, PppCorrections, PppEpoch, PppEpochContext, PppEpochResult, PppOptions, PppSatObs, PppSolution, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SatGeom, SppOptions, SppSolution, TropoMapping, WlArc, WlFcbOptions, WlFcbResult, WlObs, buildPppAntenna, createPppCorrections, estimateWidelaneFcb, extractWidelaneArcs, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength } from './positioning.js';
21
21
  export { NtripCaster, NtripConnectionInfo, NtripNetwork, NtripStream, NtripStreamConnection, NtripVersion, Sourcetable, SourcetableEntry, connectToMountpoint, fetchSourcetable, parseSourcetable } from './ntrip.js';
22
22
  export { CompleteSatSignal, CompleteSignalStats, CompletenessAccumulator, CompletenessResult, CycleSlipAccumulator, CycleSlipEvent, CycleSlipResult, CycleSlipSignalStats, IonoAccumulator, IonoDcbResult, IonoPoint, IonoRatePoint, IonoRateSeries, IonoResult, IonoSeries, MultipathAccumulator, MultipathPoint, MultipathResult, MultipathSeries, MultipathSignalStat, QualityResult, SatDcbMap, analyzeQuality, applyIonoDcb, computeIonoRate, detrendIonoArcs, parseSinexBiasDcb } from './analysis.js';
23
23
  export { AntennaEntry, AntexFile, FrequencyData, frequencyLabel, parseAntex } from './antex.js';
package/dist/index.js CHANGED
@@ -146,6 +146,8 @@ import {
146
146
  RtkFloatEngine,
147
147
  buildPppAntenna,
148
148
  createPppCorrections,
149
+ estimateWidelaneFcb,
150
+ extractWidelaneArcs,
149
151
  gimSlantIonoDelayL1,
150
152
  gimVerticalTec,
151
153
  gmst,
@@ -165,7 +167,7 @@ import {
165
167
  sunEcef,
166
168
  toRtkEpoch,
167
169
  wlWavelength
168
- } from "./chunk-EVFD4UNZ.js";
170
+ } from "./chunk-HOH27N42.js";
169
171
  import {
170
172
  almanacEpochMs,
171
173
  almanacSatPosition,
@@ -472,7 +474,9 @@ export {
472
474
  ecefToAzEl,
473
475
  ecefToGeodetic,
474
476
  ephInfoToEphemeris,
477
+ estimateWidelaneFcb,
475
478
  euclidean3D,
479
+ extractWidelaneArcs,
476
480
  fetchSourcetable,
477
481
  findSatBias,
478
482
  fmtD,
@@ -24,6 +24,8 @@ __export(positioning_exports, {
24
24
  RtkFloatEngine: () => RtkFloatEngine,
25
25
  buildPppAntenna: () => buildPppAntenna,
26
26
  createPppCorrections: () => createPppCorrections,
27
+ estimateWidelaneFcb: () => estimateWidelaneFcb,
28
+ extractWidelaneArcs: () => extractWidelaneArcs,
27
29
  gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
28
30
  gimVerticalTec: () => gimVerticalTec,
29
31
  gmst: () => gmst,
@@ -2523,6 +2525,122 @@ function resolvePppAmbiguities(sats, Qaif, opts = {}) {
2523
2525
  };
2524
2526
  }
2525
2527
 
2528
+ // src/positioning/ppp-fcb.ts
2529
+ function extractWidelaneArcs(station, epochs, opts = {}) {
2530
+ const slipCyc = opts.slipCyc ?? 4;
2531
+ const gapMs = opts.gapMs ?? 12e4;
2532
+ const minObs = opts.minObs ?? 20;
2533
+ const state = /* @__PURE__ */ new Map();
2534
+ const out = [];
2535
+ const flush = (prn) => {
2536
+ const s = state.get(prn);
2537
+ if (s && s.n >= minObs)
2538
+ out.push({ station, prn, wlFloat: s.sum / s.n, nObs: s.n });
2539
+ };
2540
+ for (const ep of epochs) {
2541
+ for (const o of ep.obs) {
2542
+ if (!o.c1 || !o.c2 || !o.l1 || !o.l2) continue;
2543
+ const lamW = C_LIGHT / (o.f1 - o.f2);
2544
+ const codeNl = (o.f1 * o.c1 + o.f2 * o.c2) / (o.f1 + o.f2);
2545
+ const mw = o.l1 - o.l2 - codeNl / lamW;
2546
+ const s = state.get(o.prn);
2547
+ if (!s || ep.timeMs - s.lastT > gapMs || Math.abs(mw - s.mean) > slipCyc) {
2548
+ if (s) flush(o.prn);
2549
+ state.set(o.prn, { sum: mw, n: 1, mean: mw, lastT: ep.timeMs });
2550
+ } else {
2551
+ s.sum += mw;
2552
+ s.n += 1;
2553
+ s.mean = s.sum / s.n;
2554
+ s.lastT = ep.timeMs;
2555
+ }
2556
+ }
2557
+ }
2558
+ for (const prn of state.keys()) flush(prn);
2559
+ return out;
2560
+ }
2561
+ function wrapHalf(x) {
2562
+ return x - Math.round(x);
2563
+ }
2564
+ function circMean(vals, weights) {
2565
+ let s = 0;
2566
+ let c = 0;
2567
+ for (let i = 0; i < vals.length; i++) {
2568
+ const a = 2 * Math.PI * vals[i];
2569
+ s += weights[i] * Math.sin(a);
2570
+ c += weights[i] * Math.cos(a);
2571
+ }
2572
+ if (s === 0 && c === 0) return 0;
2573
+ return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
2574
+ }
2575
+ function estimateWidelaneFcb(arcs, opts = {}) {
2576
+ const maxIter = opts.maxIter ?? 50;
2577
+ const tol = opts.tol ?? 1e-4;
2578
+ const fixWindow = opts.fixWindow ?? 0.25;
2579
+ const sats = [...new Set(arcs.map((a) => a.prn))].sort();
2580
+ const stations = [...new Set(arcs.map((a) => a.station))].sort();
2581
+ const refStation = stations[0] ?? "";
2582
+ const satFcb = new Map(sats.map((s) => [s, 0]));
2583
+ const rcvBias = new Map(stations.map((s) => [s, 0]));
2584
+ const nInt = new Array(arcs.length).fill(0);
2585
+ let iterations = 0;
2586
+ for (let it = 0; it < maxIter; it++) {
2587
+ iterations = it + 1;
2588
+ for (let i = 0; i < arcs.length; i++) {
2589
+ const a = arcs[i];
2590
+ nInt[i] = Math.round(
2591
+ a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
2592
+ );
2593
+ }
2594
+ let maxChange = 0;
2595
+ for (const s of sats) {
2596
+ const vals = [];
2597
+ const w = [];
2598
+ for (let i = 0; i < arcs.length; i++) {
2599
+ const a = arcs[i];
2600
+ if (a.prn !== s) continue;
2601
+ vals.push(a.wlFloat - nInt[i] - rcvBias.get(a.station));
2602
+ w.push(a.nObs);
2603
+ }
2604
+ if (!vals.length) continue;
2605
+ const nv = circMean(vals, w);
2606
+ maxChange = Math.max(maxChange, Math.abs(wrapHalf(nv - satFcb.get(s))));
2607
+ satFcb.set(s, nv);
2608
+ }
2609
+ for (const r of stations) {
2610
+ const vals = [];
2611
+ const w = [];
2612
+ for (let i = 0; i < arcs.length; i++) {
2613
+ const a = arcs[i];
2614
+ if (a.station !== r) continue;
2615
+ vals.push(a.wlFloat - nInt[i] - satFcb.get(a.prn));
2616
+ w.push(a.nObs);
2617
+ }
2618
+ if (!vals.length) continue;
2619
+ rcvBias.set(r, circMean(vals, w));
2620
+ }
2621
+ const shift = rcvBias.get(refStation);
2622
+ if (shift !== 0) {
2623
+ for (const r of stations)
2624
+ rcvBias.set(r, wrapHalf(rcvBias.get(r) - shift));
2625
+ for (const s of sats) satFcb.set(s, wrapHalf(satFcb.get(s) + shift));
2626
+ }
2627
+ if (maxChange < tol) break;
2628
+ }
2629
+ let sumSq = 0;
2630
+ let fixed = 0;
2631
+ for (let i = 0; i < arcs.length; i++) {
2632
+ const a = arcs[i];
2633
+ const resid = wrapHalf(
2634
+ a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
2635
+ );
2636
+ sumSq += resid * resid;
2637
+ if (Math.abs(resid) <= fixWindow) fixed++;
2638
+ }
2639
+ const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
2640
+ const fixRate = arcs.length ? fixed / arcs.length : 0;
2641
+ return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
2642
+ }
2643
+
2526
2644
  // src/positioning/index.ts
2527
2645
  var GM_GPS2 = 3986005e8;
2528
2646
  var F_REL = -4442807633e-19;
@@ -2756,6 +2874,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
2756
2874
  RtkFloatEngine,
2757
2875
  buildPppAntenna,
2758
2876
  createPppCorrections,
2877
+ estimateWidelaneFcb,
2878
+ extractWidelaneArcs,
2759
2879
  gimSlantIonoDelayL1,
2760
2880
  gimVerticalTec,
2761
2881
  gmst,
@@ -793,6 +793,93 @@ interface ArResult {
793
793
  */
794
794
  declare function resolvePppAmbiguities(sats: ArSat[], Qaif: Float64Array, opts?: ArOptions): ArResult;
795
795
 
796
+ /**
797
+ * Wide-lane fractional-cycle bias (FCB) estimation for PPP-AR — the
798
+ * self-contained route to integer ambiguities that needs no external
799
+ * phase-bias product, only a small network of stations.
800
+ *
801
+ * A single receiver cannot resolve ambiguities because its Melbourne–Wübbena
802
+ * wide-lane float is `Ñ_wl = N (integer) + b_rcv + b_sat`, and the integer
803
+ * and the satellite fractional bias `b_sat` are entangled. Across a *network*
804
+ * they separate: every station sees the same `b_sat` for a given satellite
805
+ * but its own integers and receiver bias, so the fractional part common to
806
+ * all stations *is* the satellite FCB. This is the classic Ge/Gendt method.
807
+ *
808
+ * The estimator alternates:
809
+ * 1. round each arc to its integer N = round(Ñ_wl − b_rcv − b_sat);
810
+ * 2. update each satellite FCB = circular-mean over stations of
811
+ * (Ñ_wl − N − b_rcv);
812
+ * 3. update each receiver bias = circular-mean over satellites of
813
+ * (Ñ_wl − N − b_sat);
814
+ * 4. pin the datum (b_rcv of a reference station ≡ 0) — receiver and
815
+ * satellite biases otherwise trade off by an arbitrary constant.
816
+ *
817
+ * The output `satFcb` feeds `resolvePppAmbiguities`' `wlBiasCyc`.
818
+ */
819
+ /** Minimal per-satellite observation for wide-lane extraction. */
820
+ interface WlObs {
821
+ prn: string;
822
+ f1: number;
823
+ f2: number;
824
+ /** Code on f1/f2 (m) and carrier phase on f1/f2 (cycles). */
825
+ c1: number;
826
+ c2: number;
827
+ l1: number;
828
+ l2: number;
829
+ }
830
+ /**
831
+ * Extract arc-averaged Melbourne–Wübbena wide-lanes for one station from its
832
+ * epoch stream. The MW combination
833
+ * MW = (L1 − L2) − [(f1·C1 + f2·C2)/(f1+f2)] / (c/(f1−f2)) [cycles]
834
+ * is geometry-, clock- and (first-order) ionosphere-free, so it holds the
835
+ * wide-lane ambiguity plus biases and is near-constant within an arc. Arcs
836
+ * are split on a running-mean jump (cycle slip) or a time gap.
837
+ */
838
+ declare function extractWidelaneArcs(station: string, epochs: {
839
+ timeMs: number;
840
+ obs: WlObs[];
841
+ }[], opts?: {
842
+ slipCyc?: number;
843
+ gapMs?: number;
844
+ minObs?: number;
845
+ }): WlArc[];
846
+ /** One continuous wide-lane arc at a station. */
847
+ interface WlArc {
848
+ station: string;
849
+ prn: string;
850
+ /** Arc-averaged Melbourne–Wübbena wide-lane float (cycles). */
851
+ wlFloat: number;
852
+ /** Number of epochs averaged (used as the weight). */
853
+ nObs: number;
854
+ }
855
+ interface WlFcbOptions {
856
+ /** Max iterations. Default 50. */
857
+ maxIter?: number;
858
+ /** Convergence threshold on the largest bias change (cycles). Default 1e-4. */
859
+ tol?: number;
860
+ /** Fix-rate acceptance window |resid| (cycles). Default 0.25. */
861
+ fixWindow?: number;
862
+ }
863
+ interface WlFcbResult {
864
+ /** Per-satellite wide-lane FCB (cycles, wrapped to [−0.5, 0.5)). */
865
+ satFcb: Map<string, number>;
866
+ /** Per-station wide-lane bias (cycles); the reference station is 0. */
867
+ rcvBias: Map<string, number>;
868
+ /** RMS of the post-fit arc residuals Ñ_wl − N − b_rcv − b_sat (cycles). */
869
+ residRms: number;
870
+ /** Fraction of arcs whose residual lies within `fixWindow` of an integer. */
871
+ fixRate: number;
872
+ /** Reference station pinning the datum. */
873
+ refStation: string;
874
+ iterations: number;
875
+ }
876
+ /**
877
+ * Estimate per-satellite wide-lane FCBs from a network of stations' arc
878
+ * wide-lane floats. Needs ≥2 stations sharing satellites to break the
879
+ * integer/bias entanglement.
880
+ */
881
+ declare function estimateWidelaneFcb(arcs: WlArc[], opts?: WlFcbOptions): WlFcbResult;
882
+
796
883
  /**
797
884
  * Single-point positioning (SPP) from pseudoranges and broadcast
798
885
  * ephemerides.
@@ -882,4 +969,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
882
969
  */
883
970
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
884
971
 
885
- export { type AntennaOffset, type ArFixedSat, type ArOptions, type ArResult, type ArSat, type CorrectionResult, type DgnssOptions, type DgnssSolution, type EphemerisSource, IONO_L1_M_PER_TECU, type KlobucharCoeffs, type LambdaResult, type PppAntennaModel, type PppCorrectionConfig, type PppCorrections, type PppEpoch, type PppEpochContext, type PppEpochResult, type PppOptions, type PppSatObs, type PppSolution, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SatGeom, type SppOptions, type SppSolution, type TropoMapping, buildPppAntenna, createPppCorrections, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength };
972
+ export { type AntennaOffset, type ArFixedSat, type ArOptions, type ArResult, type ArSat, type CorrectionResult, type DgnssOptions, type DgnssSolution, type EphemerisSource, IONO_L1_M_PER_TECU, type KlobucharCoeffs, type LambdaResult, type PppAntennaModel, type PppCorrectionConfig, type PppCorrections, type PppEpoch, type PppEpochContext, type PppEpochResult, type PppOptions, type PppSatObs, type PppSolution, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SatGeom, type SppOptions, type SppSolution, type TropoMapping, type WlArc, type WlFcbOptions, type WlFcbResult, type WlObs, buildPppAntenna, createPppCorrections, estimateWidelaneFcb, extractWidelaneArcs, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength };
@@ -793,6 +793,93 @@ interface ArResult {
793
793
  */
794
794
  declare function resolvePppAmbiguities(sats: ArSat[], Qaif: Float64Array, opts?: ArOptions): ArResult;
795
795
 
796
+ /**
797
+ * Wide-lane fractional-cycle bias (FCB) estimation for PPP-AR — the
798
+ * self-contained route to integer ambiguities that needs no external
799
+ * phase-bias product, only a small network of stations.
800
+ *
801
+ * A single receiver cannot resolve ambiguities because its Melbourne–Wübbena
802
+ * wide-lane float is `Ñ_wl = N (integer) + b_rcv + b_sat`, and the integer
803
+ * and the satellite fractional bias `b_sat` are entangled. Across a *network*
804
+ * they separate: every station sees the same `b_sat` for a given satellite
805
+ * but its own integers and receiver bias, so the fractional part common to
806
+ * all stations *is* the satellite FCB. This is the classic Ge/Gendt method.
807
+ *
808
+ * The estimator alternates:
809
+ * 1. round each arc to its integer N = round(Ñ_wl − b_rcv − b_sat);
810
+ * 2. update each satellite FCB = circular-mean over stations of
811
+ * (Ñ_wl − N − b_rcv);
812
+ * 3. update each receiver bias = circular-mean over satellites of
813
+ * (Ñ_wl − N − b_sat);
814
+ * 4. pin the datum (b_rcv of a reference station ≡ 0) — receiver and
815
+ * satellite biases otherwise trade off by an arbitrary constant.
816
+ *
817
+ * The output `satFcb` feeds `resolvePppAmbiguities`' `wlBiasCyc`.
818
+ */
819
+ /** Minimal per-satellite observation for wide-lane extraction. */
820
+ interface WlObs {
821
+ prn: string;
822
+ f1: number;
823
+ f2: number;
824
+ /** Code on f1/f2 (m) and carrier phase on f1/f2 (cycles). */
825
+ c1: number;
826
+ c2: number;
827
+ l1: number;
828
+ l2: number;
829
+ }
830
+ /**
831
+ * Extract arc-averaged Melbourne–Wübbena wide-lanes for one station from its
832
+ * epoch stream. The MW combination
833
+ * MW = (L1 − L2) − [(f1·C1 + f2·C2)/(f1+f2)] / (c/(f1−f2)) [cycles]
834
+ * is geometry-, clock- and (first-order) ionosphere-free, so it holds the
835
+ * wide-lane ambiguity plus biases and is near-constant within an arc. Arcs
836
+ * are split on a running-mean jump (cycle slip) or a time gap.
837
+ */
838
+ declare function extractWidelaneArcs(station: string, epochs: {
839
+ timeMs: number;
840
+ obs: WlObs[];
841
+ }[], opts?: {
842
+ slipCyc?: number;
843
+ gapMs?: number;
844
+ minObs?: number;
845
+ }): WlArc[];
846
+ /** One continuous wide-lane arc at a station. */
847
+ interface WlArc {
848
+ station: string;
849
+ prn: string;
850
+ /** Arc-averaged Melbourne–Wübbena wide-lane float (cycles). */
851
+ wlFloat: number;
852
+ /** Number of epochs averaged (used as the weight). */
853
+ nObs: number;
854
+ }
855
+ interface WlFcbOptions {
856
+ /** Max iterations. Default 50. */
857
+ maxIter?: number;
858
+ /** Convergence threshold on the largest bias change (cycles). Default 1e-4. */
859
+ tol?: number;
860
+ /** Fix-rate acceptance window |resid| (cycles). Default 0.25. */
861
+ fixWindow?: number;
862
+ }
863
+ interface WlFcbResult {
864
+ /** Per-satellite wide-lane FCB (cycles, wrapped to [−0.5, 0.5)). */
865
+ satFcb: Map<string, number>;
866
+ /** Per-station wide-lane bias (cycles); the reference station is 0. */
867
+ rcvBias: Map<string, number>;
868
+ /** RMS of the post-fit arc residuals Ñ_wl − N − b_rcv − b_sat (cycles). */
869
+ residRms: number;
870
+ /** Fraction of arcs whose residual lies within `fixWindow` of an integer. */
871
+ fixRate: number;
872
+ /** Reference station pinning the datum. */
873
+ refStation: string;
874
+ iterations: number;
875
+ }
876
+ /**
877
+ * Estimate per-satellite wide-lane FCBs from a network of stations' arc
878
+ * wide-lane floats. Needs ≥2 stations sharing satellites to break the
879
+ * integer/bias entanglement.
880
+ */
881
+ declare function estimateWidelaneFcb(arcs: WlArc[], opts?: WlFcbOptions): WlFcbResult;
882
+
796
883
  /**
797
884
  * Single-point positioning (SPP) from pseudoranges and broadcast
798
885
  * ephemerides.
@@ -882,4 +969,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
882
969
  */
883
970
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
884
971
 
885
- export { type AntennaOffset, type ArFixedSat, type ArOptions, type ArResult, type ArSat, type CorrectionResult, type DgnssOptions, type DgnssSolution, type EphemerisSource, IONO_L1_M_PER_TECU, type KlobucharCoeffs, type LambdaResult, type PppAntennaModel, type PppCorrectionConfig, type PppCorrections, type PppEpoch, type PppEpochContext, type PppEpochResult, type PppOptions, type PppSatObs, type PppSolution, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SatGeom, type SppOptions, type SppSolution, type TropoMapping, buildPppAntenna, createPppCorrections, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength };
972
+ export { type AntennaOffset, type ArFixedSat, type ArOptions, type ArResult, type ArSat, type CorrectionResult, type DgnssOptions, type DgnssSolution, type EphemerisSource, IONO_L1_M_PER_TECU, type KlobucharCoeffs, type LambdaResult, type PppAntennaModel, type PppCorrectionConfig, type PppCorrections, type PppEpoch, type PppEpochContext, type PppEpochResult, type PppOptions, type PppSatObs, type PppSolution, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SatGeom, type SppOptions, type SppSolution, type TropoMapping, type WlArc, type WlFcbOptions, type WlFcbResult, type WlObs, buildPppAntenna, createPppCorrections, estimateWidelaneFcb, extractWidelaneArcs, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength };
@@ -3,6 +3,8 @@ import {
3
3
  RtkFloatEngine,
4
4
  buildPppAntenna,
5
5
  createPppCorrections,
6
+ estimateWidelaneFcb,
7
+ extractWidelaneArcs,
6
8
  gimSlantIonoDelayL1,
7
9
  gimVerticalTec,
8
10
  gmst,
@@ -22,7 +24,7 @@ import {
22
24
  sunEcef,
23
25
  toRtkEpoch,
24
26
  wlWavelength
25
- } from "./chunk-EVFD4UNZ.js";
27
+ } from "./chunk-HOH27N42.js";
26
28
  import "./chunk-MHGNKSSH.js";
27
29
  import "./chunk-37QNKGTC.js";
28
30
  import "./chunk-6FAL6P4G.js";
@@ -35,6 +37,8 @@ export {
35
37
  RtkFloatEngine,
36
38
  buildPppAntenna,
37
39
  createPppCorrections,
40
+ estimateWidelaneFcb,
41
+ extractWidelaneArcs,
38
42
  gimSlantIonoDelayL1,
39
43
  gimVerticalTec,
40
44
  gmst,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Comprehensive GNSS library for JavaScript — time scales, coordinates, RINEX parsing, RTCM3 decoding, orbit computation, and signal analysis",
5
5
  "type": "module",
6
6
  "sideEffects": false,