gnss-js 1.33.0 → 1.35.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.
@@ -461,15 +461,15 @@ function matMul(A, B) {
461
461
  const n = A.length;
462
462
  const k = B.length;
463
463
  const m = B[0]?.length ?? 0;
464
- const C = zeros(n, m);
464
+ const C2 = zeros(n, m);
465
465
  for (let i = 0; i < n; i++) {
466
466
  for (let l = 0; l < k; l++) {
467
467
  const a = A[i][l];
468
468
  if (a === 0) continue;
469
- for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
469
+ for (let j = 0; j < m; j++) C2[i][j] += a * B[l][j];
470
470
  }
471
471
  }
472
- return C;
472
+ return C2;
473
473
  }
474
474
  function transpose(A) {
475
475
  const n = A.length;
@@ -1649,6 +1649,24 @@ function solvePpp(epochs, sp3, opts) {
1649
1649
  const clockSeeded = /* @__PURE__ */ new Set();
1650
1650
  const ambIdx = /* @__PURE__ */ new Map();
1651
1651
  const lastSeen = /* @__PURE__ */ new Map();
1652
+ const arcs = [];
1653
+ const arcSnap = /* @__PURE__ */ new Map();
1654
+ const flushArc = (prn, minEpochs = 10) => {
1655
+ const s = arcSnap.get(prn);
1656
+ arcSnap.delete(prn);
1657
+ if (!s || s.nEpochs < minEpochs) return;
1658
+ arcs.push({
1659
+ prn,
1660
+ aIF: s.aIF,
1661
+ mwCyc: s.mwCyc,
1662
+ f1: s.f1,
1663
+ f2: s.f2,
1664
+ meanElevDeg: s.elevSum / s.nEpochs,
1665
+ nEpochs: s.nEpochs,
1666
+ startMs: s.startMs,
1667
+ endMs: s.endMs
1668
+ });
1669
+ };
1652
1670
  const mwState = /* @__PURE__ */ new Map();
1653
1671
  const detectSlip = (o, ei) => {
1654
1672
  const lamW = C_LIGHT / (o.f1 - o.f2);
@@ -1766,7 +1784,9 @@ function solvePpp(epochs, sp3, opts) {
1766
1784
  tropoFixed: zhd * mh + corr.rangeM,
1767
1785
  mw,
1768
1786
  windupM: corr.phaseWindupM,
1769
- slip
1787
+ slip,
1788
+ f1: o.f1,
1789
+ f2: o.f2
1770
1790
  });
1771
1791
  }
1772
1792
  const zwd0 = x[IDX_ZWD];
@@ -1791,6 +1811,7 @@ function solvePpp(epochs, sp3, opts) {
1791
1811
  for (const v of vis) {
1792
1812
  const ci = clockOf(v.prn[0]);
1793
1813
  const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
1814
+ if (v.slip) flushArc(v.prn);
1794
1815
  let ai = ambIdx.get(v.prn);
1795
1816
  if (ai === void 0 || v.slip) {
1796
1817
  const bInit = v.lIf - (common + v.windupM);
@@ -1824,6 +1845,7 @@ function solvePpp(epochs, sp3, opts) {
1824
1845
  const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
1825
1846
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
1826
1847
  if (Math.abs(innov) > 0.15) {
1848
+ flushArc(v.prn);
1827
1849
  x[ai] = v.lIf - (commonPh + v.windupM);
1828
1850
  for (let j = 0; j < P.length; j++) {
1829
1851
  P[ai][j] = 0;
@@ -1841,6 +1863,29 @@ function solvePpp(epochs, sp3, opts) {
1841
1863
  ekfUpdateScalar(x, P, h, innov, rPhase, 4);
1842
1864
  }
1843
1865
  }
1866
+ {
1867
+ const elevDeg = v.elRad * 180 / Math.PI;
1868
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
1869
+ const s = arcSnap.get(v.prn);
1870
+ if (s) {
1871
+ s.aIF = x[ai];
1872
+ s.mwCyc = mwMean;
1873
+ s.elevSum += elevDeg;
1874
+ s.nEpochs++;
1875
+ s.endMs = epoch.timeMs;
1876
+ } else {
1877
+ arcSnap.set(v.prn, {
1878
+ aIF: x[ai],
1879
+ mwCyc: mwMean,
1880
+ f1: v.f1,
1881
+ f2: v.f2,
1882
+ elevSum: elevDeg,
1883
+ nEpochs: 1,
1884
+ startMs: epoch.timeMs,
1885
+ endMs: epoch.timeMs
1886
+ });
1887
+ }
1888
+ }
1844
1889
  lastSeen.set(v.prn, ei);
1845
1890
  nSats++;
1846
1891
  }
@@ -1874,6 +1919,7 @@ function solvePpp(epochs, sp3, opts) {
1874
1919
  const idx = ambIdx.get(prn);
1875
1920
  if (idx !== void 0) dropState(idx);
1876
1921
  lastSeen.delete(prn);
1922
+ flushArc(prn);
1877
1923
  }
1878
1924
  }
1879
1925
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
@@ -1913,6 +1959,7 @@ function solvePpp(epochs, sp3, opts) {
1913
1959
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
1914
1960
  });
1915
1961
  }
1962
+ for (const prn of [...arcSnap.keys()]) flushArc(prn);
1916
1963
  const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
1917
1964
  const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
1918
1965
  const finalError3d = truth ? Math.hypot(
@@ -1927,7 +1974,8 @@ function solvePpp(epochs, sp3, opts) {
1927
1974
  ztdWet: x[IDX_ZWD],
1928
1975
  epochsUsed: epochs.length,
1929
1976
  convergenceSec,
1930
- finalError3d
1977
+ finalError3d,
1978
+ arcs
1931
1979
  };
1932
1980
  }
1933
1981
 
@@ -2068,34 +2116,34 @@ function circMean(vals, weights) {
2068
2116
  if (s === 0 && c === 0) return 0;
2069
2117
  return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
2070
2118
  }
2071
- function estimateWidelaneFcb(arcs, opts = {}) {
2119
+ function decomposeFcb(entries, opts = {}) {
2072
2120
  const maxIter = opts.maxIter ?? 50;
2073
2121
  const tol = opts.tol ?? 1e-4;
2074
2122
  const fixWindow = opts.fixWindow ?? 0.25;
2075
- const sats = [...new Set(arcs.map((a) => a.prn))].sort();
2076
- const stations = [...new Set(arcs.map((a) => a.station))].sort();
2123
+ const sats = [...new Set(entries.map((e) => e.sat))].sort();
2124
+ const stations = [...new Set(entries.map((e) => e.station))].sort();
2077
2125
  const refStation = stations[0] ?? "";
2078
2126
  const satFcb = new Map(sats.map((s) => [s, 0]));
2079
2127
  const rcvBias = new Map(stations.map((s) => [s, 0]));
2080
- const nInt = new Array(arcs.length).fill(0);
2128
+ const nInt = new Array(entries.length).fill(0);
2081
2129
  let iterations = 0;
2082
2130
  for (let it = 0; it < maxIter; it++) {
2083
2131
  iterations = it + 1;
2084
- for (let i = 0; i < arcs.length; i++) {
2085
- const a = arcs[i];
2132
+ for (let i = 0; i < entries.length; i++) {
2133
+ const e = entries[i];
2086
2134
  nInt[i] = Math.round(
2087
- a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
2135
+ e.value - rcvBias.get(e.station) - satFcb.get(e.sat)
2088
2136
  );
2089
2137
  }
2090
2138
  let maxChange = 0;
2091
2139
  for (const s of sats) {
2092
2140
  const vals = [];
2093
2141
  const w = [];
2094
- for (let i = 0; i < arcs.length; i++) {
2095
- const a = arcs[i];
2096
- if (a.prn !== s) continue;
2097
- vals.push(a.wlFloat - nInt[i] - rcvBias.get(a.station));
2098
- w.push(a.nObs);
2142
+ for (let i = 0; i < entries.length; i++) {
2143
+ const e = entries[i];
2144
+ if (e.sat !== s) continue;
2145
+ vals.push(e.value - nInt[i] - rcvBias.get(e.station));
2146
+ w.push(e.weight);
2099
2147
  }
2100
2148
  if (!vals.length) continue;
2101
2149
  const nv = circMean(vals, w);
@@ -2105,11 +2153,11 @@ function estimateWidelaneFcb(arcs, opts = {}) {
2105
2153
  for (const r of stations) {
2106
2154
  const vals = [];
2107
2155
  const w = [];
2108
- for (let i = 0; i < arcs.length; i++) {
2109
- const a = arcs[i];
2110
- if (a.station !== r) continue;
2111
- vals.push(a.wlFloat - nInt[i] - satFcb.get(a.prn));
2112
- w.push(a.nObs);
2156
+ for (let i = 0; i < entries.length; i++) {
2157
+ const e = entries[i];
2158
+ if (e.station !== r) continue;
2159
+ vals.push(e.value - nInt[i] - satFcb.get(e.sat));
2160
+ w.push(e.weight);
2113
2161
  }
2114
2162
  if (!vals.length) continue;
2115
2163
  rcvBias.set(r, circMean(vals, w));
@@ -2124,18 +2172,88 @@ function estimateWidelaneFcb(arcs, opts = {}) {
2124
2172
  }
2125
2173
  let sumSq = 0;
2126
2174
  let fixed = 0;
2127
- for (let i = 0; i < arcs.length; i++) {
2128
- const a = arcs[i];
2175
+ for (let i = 0; i < entries.length; i++) {
2176
+ const e = entries[i];
2129
2177
  const resid = wrapHalf(
2130
- a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
2178
+ e.value - nInt[i] - rcvBias.get(e.station) - satFcb.get(e.sat)
2131
2179
  );
2132
2180
  sumSq += resid * resid;
2133
2181
  if (Math.abs(resid) <= fixWindow) fixed++;
2134
2182
  }
2135
- const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
2136
- const fixRate = arcs.length ? fixed / arcs.length : 0;
2183
+ const residRms = entries.length ? Math.sqrt(sumSq / entries.length) : 0;
2184
+ const fixRate = entries.length ? fixed / entries.length : 0;
2137
2185
  return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
2138
2186
  }
2187
+ function estimateWidelaneFcb(arcs, opts = {}) {
2188
+ return decomposeFcb(
2189
+ arcs.map((a) => ({
2190
+ station: a.station,
2191
+ sat: a.prn,
2192
+ value: a.wlFloat,
2193
+ weight: a.nObs
2194
+ })),
2195
+ opts
2196
+ );
2197
+ }
2198
+ var C = 299792458;
2199
+ function estimateNarrowlaneFcb(arcs, wl, opts = {}) {
2200
+ const entries = [];
2201
+ let wlRejected = 0;
2202
+ for (const a of arcs) {
2203
+ const satWl = wl.satFcb.get(a.prn);
2204
+ const rcvWl = wl.rcvBias.get(a.station);
2205
+ if (satWl === void 0 || rcvWl === void 0) continue;
2206
+ const wlFloat = a.mwCyc - satWl - rcvWl;
2207
+ const nWl = Math.round(wlFloat);
2208
+ if (Math.abs(wlFloat - nWl) > (opts.fixWindow ?? 0.25)) {
2209
+ wlRejected++;
2210
+ continue;
2211
+ }
2212
+ const lamNl = C / (a.f1 + a.f2);
2213
+ const n1Float = a.aIF / lamNl - a.f2 / (a.f1 - a.f2) * nWl;
2214
+ entries.push({
2215
+ station: a.station,
2216
+ sat: a.prn,
2217
+ value: n1Float,
2218
+ weight: a.nEpochs
2219
+ });
2220
+ }
2221
+ const res = decomposeFcb(entries, { fixWindow: 0.15, ...opts });
2222
+ return { ...res, usedArcs: entries.length, wlRejected };
2223
+ }
2224
+ function estimateNetworkFcbs(arcs, opts = {}) {
2225
+ const minArcEpochs = opts.minArcEpochs ?? 120;
2226
+ const minWlObs = opts.minWlObs ?? 40;
2227
+ const fixWindow = opts.fixWindow ?? 0.15;
2228
+ const satWlFcb = /* @__PURE__ */ new Map();
2229
+ const satNlFcb = /* @__PURE__ */ new Map();
2230
+ const perSystem = {};
2231
+ const systems = [...new Set(arcs.map((a) => a.prn[0]))].sort();
2232
+ for (const sys of systems) {
2233
+ const sysArcs = arcs.filter((a) => a.prn[0] === sys);
2234
+ const wlArcs = sysArcs.filter((a) => a.nEpochs >= minWlObs).map((a) => ({
2235
+ station: a.station,
2236
+ prn: a.prn,
2237
+ wlFloat: a.mwCyc,
2238
+ nObs: a.nEpochs
2239
+ }));
2240
+ if (wlArcs.length < 3) continue;
2241
+ const wl = estimateWidelaneFcb(wlArcs, { fixWindow });
2242
+ const nlInput = sysArcs.filter((a) => a.nEpochs >= minArcEpochs);
2243
+ const nl = estimateNarrowlaneFcb(nlInput, wl, { fixWindow });
2244
+ for (const [prn, v] of wl.satFcb) satWlFcb.set(prn, v);
2245
+ for (const [prn, v] of nl.satFcb) satNlFcb.set(prn, v);
2246
+ perSystem[sys] = {
2247
+ wlFixRate: wl.fixRate,
2248
+ wlResidRms: wl.residRms,
2249
+ nlUsedArcs: nl.usedArcs,
2250
+ nlFixRate: nl.fixRate,
2251
+ nlResidRms: nl.residRms,
2252
+ wlRejected: nl.wlRejected
2253
+ };
2254
+ }
2255
+ return { satWlFcb, satNlFcb, perSystem };
2256
+ }
2139
2257
 
2140
2258
  // src/positioning/index.ts
2141
2259
  var GM_GPS = 3986005e8;
@@ -2388,6 +2506,8 @@ export {
2388
2506
  resolvePppAmbiguities,
2389
2507
  extractWidelaneArcs,
2390
2508
  estimateWidelaneFcb,
2509
+ estimateNarrowlaneFcb,
2510
+ estimateNetworkFcbs,
2391
2511
  satClockCorrection,
2392
2512
  ionoFree,
2393
2513
  solveSpp
package/dist/index.cjs CHANGED
@@ -149,6 +149,8 @@ __export(src_exports, {
149
149
  ecefToAzEl: () => ecefToAzEl,
150
150
  ecefToGeodetic: () => ecefToGeodetic,
151
151
  ephInfoToEphemeris: () => ephInfoToEphemeris,
152
+ estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
153
+ estimateNetworkFcbs: () => estimateNetworkFcbs,
152
154
  estimateWidelaneFcb: () => estimateWidelaneFcb,
153
155
  euclidean3D: () => euclidean3D,
154
156
  extractWidelaneArcs: () => extractWidelaneArcs,
@@ -1067,7 +1069,7 @@ function vincenty(lat1, lon1, lat2, lon2) {
1067
1069
  let lambdaPrev;
1068
1070
  let sinSigma, cosSigma, sigma;
1069
1071
  let sinAlpha, cos2Alpha, cos2SigmaM;
1070
- let C;
1072
+ let C2;
1071
1073
  let iter = 0;
1072
1074
  do {
1073
1075
  const sinLambda2 = Math.sin(lambda);
@@ -1083,9 +1085,9 @@ function vincenty(lat1, lon1, lat2, lon2) {
1083
1085
  sinAlpha = cosU1 * cosU2 * sinLambda2 / sinSigma;
1084
1086
  cos2Alpha = 1 - sinAlpha ** 2;
1085
1087
  cos2SigmaM = cos2Alpha !== 0 ? cosSigma - 2 * sinU1 * sinU2 / cos2Alpha : 0;
1086
- C = WGS84_FLATTENING / 16 * cos2Alpha * (4 + WGS84_FLATTENING * (4 - 3 * cos2Alpha));
1088
+ C2 = WGS84_FLATTENING / 16 * cos2Alpha * (4 + WGS84_FLATTENING * (4 - 3 * cos2Alpha));
1087
1089
  lambdaPrev = lambda;
1088
- lambda = lon2 - lon1 + (1 - C) * WGS84_FLATTENING * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)));
1090
+ lambda = lon2 - lon1 + (1 - C2) * WGS84_FLATTENING * sinAlpha * (sigma + C2 * sinSigma * (cos2SigmaM + C2 * cosSigma * (-1 + 2 * cos2SigmaM ** 2)));
1089
1091
  } while (Math.abs(lambda - lambdaPrev) > 1e-12 && ++iter < 200);
1090
1092
  if (iter >= 200) {
1091
1093
  const cosLat1 = Math.cos(lat1), sinLat1 = Math.sin(lat1);
@@ -1189,11 +1191,11 @@ function geodeticToUtm(lat, lon) {
1189
1191
  const ep2 = e2 / (1 - e2);
1190
1192
  const N = WGS84_SEMI_MAJOR_AXIS / Math.sqrt(1 - e2 * Math.sin(lat) ** 2);
1191
1193
  const T = Math.tan(lat) ** 2;
1192
- const C = ep2 * Math.cos(lat) ** 2;
1194
+ const C2 = ep2 * Math.cos(lat) ** 2;
1193
1195
  const A = Math.cos(lat) * (lon - lon0);
1194
1196
  const M = WGS84_SEMI_MAJOR_AXIS * ((1 - e2 / 4 - 3 * e2 ** 2 / 64 - 5 * e2 ** 3 / 256) * lat - (3 * e2 / 8 + 3 * e2 ** 2 / 32 + 45 * e2 ** 3 / 1024) * Math.sin(2 * lat) + (15 * e2 ** 2 / 256 + 45 * e2 ** 3 / 1024) * Math.sin(4 * lat) - 35 * e2 ** 3 / 3072 * Math.sin(6 * lat));
1195
- const easting = k0 * N * (A + (1 - T + C) * A ** 3 / 6 + (5 - 18 * T + T ** 2 + 72 * C - 58 * ep2) * A ** 5 / 120) + 5e5;
1196
- let northing = k0 * (M + N * Math.tan(lat) * (A ** 2 / 2 + (5 - T + 9 * C + 4 * C ** 2) * A ** 4 / 24 + (61 - 58 * T + T ** 2 + 600 * C - 330 * ep2) * A ** 6 / 720));
1197
+ const easting = k0 * N * (A + (1 - T + C2) * A ** 3 / 6 + (5 - 18 * T + T ** 2 + 72 * C2 - 58 * ep2) * A ** 5 / 120) + 5e5;
1198
+ let northing = k0 * (M + N * Math.tan(lat) * (A ** 2 / 2 + (5 - T + 9 * C2 + 4 * C2 ** 2) * A ** 4 / 24 + (61 - 58 * T + T ** 2 + 600 * C2 - 330 * ep2) * A ** 6 / 720));
1197
1199
  const hemisphere = latDeg >= 0 ? "N" : "S";
1198
1200
  if (hemisphere === "S") {
1199
1201
  northing += 1e7;
@@ -11857,15 +11859,15 @@ function matMul(A, B) {
11857
11859
  const n = A.length;
11858
11860
  const k = B.length;
11859
11861
  const m = B[0]?.length ?? 0;
11860
- const C = zeros(n, m);
11862
+ const C2 = zeros(n, m);
11861
11863
  for (let i = 0; i < n; i++) {
11862
11864
  for (let l = 0; l < k; l++) {
11863
11865
  const a = A[i][l];
11864
11866
  if (a === 0) continue;
11865
- for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
11867
+ for (let j = 0; j < m; j++) C2[i][j] += a * B[l][j];
11866
11868
  }
11867
11869
  }
11868
- return C;
11870
+ return C2;
11869
11871
  }
11870
11872
  function transpose(A) {
11871
11873
  const n = A.length;
@@ -13045,6 +13047,24 @@ function solvePpp(epochs, sp3, opts) {
13045
13047
  const clockSeeded = /* @__PURE__ */ new Set();
13046
13048
  const ambIdx = /* @__PURE__ */ new Map();
13047
13049
  const lastSeen = /* @__PURE__ */ new Map();
13050
+ const arcs = [];
13051
+ const arcSnap = /* @__PURE__ */ new Map();
13052
+ const flushArc = (prn, minEpochs = 10) => {
13053
+ const s = arcSnap.get(prn);
13054
+ arcSnap.delete(prn);
13055
+ if (!s || s.nEpochs < minEpochs) return;
13056
+ arcs.push({
13057
+ prn,
13058
+ aIF: s.aIF,
13059
+ mwCyc: s.mwCyc,
13060
+ f1: s.f1,
13061
+ f2: s.f2,
13062
+ meanElevDeg: s.elevSum / s.nEpochs,
13063
+ nEpochs: s.nEpochs,
13064
+ startMs: s.startMs,
13065
+ endMs: s.endMs
13066
+ });
13067
+ };
13048
13068
  const mwState = /* @__PURE__ */ new Map();
13049
13069
  const detectSlip = (o, ei) => {
13050
13070
  const lamW = C_LIGHT / (o.f1 - o.f2);
@@ -13162,7 +13182,9 @@ function solvePpp(epochs, sp3, opts) {
13162
13182
  tropoFixed: zhd * mh + corr.rangeM,
13163
13183
  mw,
13164
13184
  windupM: corr.phaseWindupM,
13165
- slip
13185
+ slip,
13186
+ f1: o.f1,
13187
+ f2: o.f2
13166
13188
  });
13167
13189
  }
13168
13190
  const zwd0 = x[IDX_ZWD];
@@ -13187,6 +13209,7 @@ function solvePpp(epochs, sp3, opts) {
13187
13209
  for (const v of vis) {
13188
13210
  const ci = clockOf(v.prn[0]);
13189
13211
  const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
13212
+ if (v.slip) flushArc(v.prn);
13190
13213
  let ai = ambIdx.get(v.prn);
13191
13214
  if (ai === void 0 || v.slip) {
13192
13215
  const bInit = v.lIf - (common + v.windupM);
@@ -13220,6 +13243,7 @@ function solvePpp(epochs, sp3, opts) {
13220
13243
  const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
13221
13244
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
13222
13245
  if (Math.abs(innov) > 0.15) {
13246
+ flushArc(v.prn);
13223
13247
  x[ai] = v.lIf - (commonPh + v.windupM);
13224
13248
  for (let j = 0; j < P.length; j++) {
13225
13249
  P[ai][j] = 0;
@@ -13237,6 +13261,29 @@ function solvePpp(epochs, sp3, opts) {
13237
13261
  ekfUpdateScalar(x, P, h, innov, rPhase, 4);
13238
13262
  }
13239
13263
  }
13264
+ {
13265
+ const elevDeg = v.elRad * 180 / Math.PI;
13266
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
13267
+ const s = arcSnap.get(v.prn);
13268
+ if (s) {
13269
+ s.aIF = x[ai];
13270
+ s.mwCyc = mwMean;
13271
+ s.elevSum += elevDeg;
13272
+ s.nEpochs++;
13273
+ s.endMs = epoch.timeMs;
13274
+ } else {
13275
+ arcSnap.set(v.prn, {
13276
+ aIF: x[ai],
13277
+ mwCyc: mwMean,
13278
+ f1: v.f1,
13279
+ f2: v.f2,
13280
+ elevSum: elevDeg,
13281
+ nEpochs: 1,
13282
+ startMs: epoch.timeMs,
13283
+ endMs: epoch.timeMs
13284
+ });
13285
+ }
13286
+ }
13240
13287
  lastSeen.set(v.prn, ei);
13241
13288
  nSats++;
13242
13289
  }
@@ -13270,6 +13317,7 @@ function solvePpp(epochs, sp3, opts) {
13270
13317
  const idx = ambIdx.get(prn);
13271
13318
  if (idx !== void 0) dropState(idx);
13272
13319
  lastSeen.delete(prn);
13320
+ flushArc(prn);
13273
13321
  }
13274
13322
  }
13275
13323
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
@@ -13309,6 +13357,7 @@ function solvePpp(epochs, sp3, opts) {
13309
13357
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
13310
13358
  });
13311
13359
  }
13360
+ for (const prn of [...arcSnap.keys()]) flushArc(prn);
13312
13361
  const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
13313
13362
  const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
13314
13363
  const finalError3d = truth ? Math.hypot(
@@ -13323,7 +13372,8 @@ function solvePpp(epochs, sp3, opts) {
13323
13372
  ztdWet: x[IDX_ZWD],
13324
13373
  epochsUsed: epochs.length,
13325
13374
  convergenceSec,
13326
- finalError3d
13375
+ finalError3d,
13376
+ arcs
13327
13377
  };
13328
13378
  }
13329
13379
 
@@ -13464,34 +13514,34 @@ function circMean(vals, weights) {
13464
13514
  if (s === 0 && c === 0) return 0;
13465
13515
  return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
13466
13516
  }
13467
- function estimateWidelaneFcb(arcs, opts = {}) {
13517
+ function decomposeFcb(entries, opts = {}) {
13468
13518
  const maxIter = opts.maxIter ?? 50;
13469
13519
  const tol = opts.tol ?? 1e-4;
13470
13520
  const fixWindow = opts.fixWindow ?? 0.25;
13471
- const sats = [...new Set(arcs.map((a) => a.prn))].sort();
13472
- const stations = [...new Set(arcs.map((a) => a.station))].sort();
13521
+ const sats = [...new Set(entries.map((e) => e.sat))].sort();
13522
+ const stations = [...new Set(entries.map((e) => e.station))].sort();
13473
13523
  const refStation = stations[0] ?? "";
13474
13524
  const satFcb = new Map(sats.map((s) => [s, 0]));
13475
13525
  const rcvBias = new Map(stations.map((s) => [s, 0]));
13476
- const nInt = new Array(arcs.length).fill(0);
13526
+ const nInt = new Array(entries.length).fill(0);
13477
13527
  let iterations = 0;
13478
13528
  for (let it = 0; it < maxIter; it++) {
13479
13529
  iterations = it + 1;
13480
- for (let i = 0; i < arcs.length; i++) {
13481
- const a = arcs[i];
13530
+ for (let i = 0; i < entries.length; i++) {
13531
+ const e = entries[i];
13482
13532
  nInt[i] = Math.round(
13483
- a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
13533
+ e.value - rcvBias.get(e.station) - satFcb.get(e.sat)
13484
13534
  );
13485
13535
  }
13486
13536
  let maxChange = 0;
13487
13537
  for (const s of sats) {
13488
13538
  const vals = [];
13489
13539
  const w = [];
13490
- for (let i = 0; i < arcs.length; i++) {
13491
- const a = arcs[i];
13492
- if (a.prn !== s) continue;
13493
- vals.push(a.wlFloat - nInt[i] - rcvBias.get(a.station));
13494
- w.push(a.nObs);
13540
+ for (let i = 0; i < entries.length; i++) {
13541
+ const e = entries[i];
13542
+ if (e.sat !== s) continue;
13543
+ vals.push(e.value - nInt[i] - rcvBias.get(e.station));
13544
+ w.push(e.weight);
13495
13545
  }
13496
13546
  if (!vals.length) continue;
13497
13547
  const nv = circMean(vals, w);
@@ -13501,11 +13551,11 @@ function estimateWidelaneFcb(arcs, opts = {}) {
13501
13551
  for (const r of stations) {
13502
13552
  const vals = [];
13503
13553
  const w = [];
13504
- for (let i = 0; i < arcs.length; i++) {
13505
- const a = arcs[i];
13506
- if (a.station !== r) continue;
13507
- vals.push(a.wlFloat - nInt[i] - satFcb.get(a.prn));
13508
- w.push(a.nObs);
13554
+ for (let i = 0; i < entries.length; i++) {
13555
+ const e = entries[i];
13556
+ if (e.station !== r) continue;
13557
+ vals.push(e.value - nInt[i] - satFcb.get(e.sat));
13558
+ w.push(e.weight);
13509
13559
  }
13510
13560
  if (!vals.length) continue;
13511
13561
  rcvBias.set(r, circMean(vals, w));
@@ -13520,18 +13570,88 @@ function estimateWidelaneFcb(arcs, opts = {}) {
13520
13570
  }
13521
13571
  let sumSq = 0;
13522
13572
  let fixed = 0;
13523
- for (let i = 0; i < arcs.length; i++) {
13524
- const a = arcs[i];
13573
+ for (let i = 0; i < entries.length; i++) {
13574
+ const e = entries[i];
13525
13575
  const resid = wrapHalf(
13526
- a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
13576
+ e.value - nInt[i] - rcvBias.get(e.station) - satFcb.get(e.sat)
13527
13577
  );
13528
13578
  sumSq += resid * resid;
13529
13579
  if (Math.abs(resid) <= fixWindow) fixed++;
13530
13580
  }
13531
- const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
13532
- const fixRate = arcs.length ? fixed / arcs.length : 0;
13581
+ const residRms = entries.length ? Math.sqrt(sumSq / entries.length) : 0;
13582
+ const fixRate = entries.length ? fixed / entries.length : 0;
13533
13583
  return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
13534
13584
  }
13585
+ function estimateWidelaneFcb(arcs, opts = {}) {
13586
+ return decomposeFcb(
13587
+ arcs.map((a) => ({
13588
+ station: a.station,
13589
+ sat: a.prn,
13590
+ value: a.wlFloat,
13591
+ weight: a.nObs
13592
+ })),
13593
+ opts
13594
+ );
13595
+ }
13596
+ var C = 299792458;
13597
+ function estimateNarrowlaneFcb(arcs, wl, opts = {}) {
13598
+ const entries = [];
13599
+ let wlRejected = 0;
13600
+ for (const a of arcs) {
13601
+ const satWl = wl.satFcb.get(a.prn);
13602
+ const rcvWl = wl.rcvBias.get(a.station);
13603
+ if (satWl === void 0 || rcvWl === void 0) continue;
13604
+ const wlFloat = a.mwCyc - satWl - rcvWl;
13605
+ const nWl = Math.round(wlFloat);
13606
+ if (Math.abs(wlFloat - nWl) > (opts.fixWindow ?? 0.25)) {
13607
+ wlRejected++;
13608
+ continue;
13609
+ }
13610
+ const lamNl = C / (a.f1 + a.f2);
13611
+ const n1Float = a.aIF / lamNl - a.f2 / (a.f1 - a.f2) * nWl;
13612
+ entries.push({
13613
+ station: a.station,
13614
+ sat: a.prn,
13615
+ value: n1Float,
13616
+ weight: a.nEpochs
13617
+ });
13618
+ }
13619
+ const res = decomposeFcb(entries, { fixWindow: 0.15, ...opts });
13620
+ return { ...res, usedArcs: entries.length, wlRejected };
13621
+ }
13622
+ function estimateNetworkFcbs(arcs, opts = {}) {
13623
+ const minArcEpochs = opts.minArcEpochs ?? 120;
13624
+ const minWlObs = opts.minWlObs ?? 40;
13625
+ const fixWindow = opts.fixWindow ?? 0.15;
13626
+ const satWlFcb = /* @__PURE__ */ new Map();
13627
+ const satNlFcb = /* @__PURE__ */ new Map();
13628
+ const perSystem = {};
13629
+ const systems = [...new Set(arcs.map((a) => a.prn[0]))].sort();
13630
+ for (const sys of systems) {
13631
+ const sysArcs = arcs.filter((a) => a.prn[0] === sys);
13632
+ const wlArcs = sysArcs.filter((a) => a.nEpochs >= minWlObs).map((a) => ({
13633
+ station: a.station,
13634
+ prn: a.prn,
13635
+ wlFloat: a.mwCyc,
13636
+ nObs: a.nEpochs
13637
+ }));
13638
+ if (wlArcs.length < 3) continue;
13639
+ const wl = estimateWidelaneFcb(wlArcs, { fixWindow });
13640
+ const nlInput = sysArcs.filter((a) => a.nEpochs >= minArcEpochs);
13641
+ const nl = estimateNarrowlaneFcb(nlInput, wl, { fixWindow });
13642
+ for (const [prn, v] of wl.satFcb) satWlFcb.set(prn, v);
13643
+ for (const [prn, v] of nl.satFcb) satNlFcb.set(prn, v);
13644
+ perSystem[sys] = {
13645
+ wlFixRate: wl.fixRate,
13646
+ wlResidRms: wl.residRms,
13647
+ nlUsedArcs: nl.usedArcs,
13648
+ nlFixRate: nl.fixRate,
13649
+ nlResidRms: nl.residRms,
13650
+ wlRejected: nl.wlRejected
13651
+ };
13652
+ }
13653
+ return { satWlFcb, satNlFcb, perSystem };
13654
+ }
13535
13655
 
13536
13656
  // src/positioning/index.ts
13537
13657
  var GM_GPS3 = 3986005e8;
@@ -13943,9 +14063,9 @@ var MultipathAccumulator = class {
13943
14063
  const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
13944
14064
  if (!bandMap) return;
13945
14065
  const bandData = /* @__PURE__ */ new Map();
13946
- for (const [band, { C, L }] of bandMap) {
13947
- if (C === null) continue;
13948
- const cVal = values[C];
14066
+ for (const [band, { C: C2, L }] of bandMap) {
14067
+ if (C2 === null) continue;
14068
+ const cVal = values[C2];
13949
14069
  const lVal = values[L];
13950
14070
  const freq = getFreq(this.gloChannels, prn, band);
13951
14071
  if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
@@ -14157,13 +14277,13 @@ var CycleSlipAccumulator = class {
14157
14277
  if (sys !== "R" && !FREQ[sys]) return;
14158
14278
  const phaseM = /* @__PURE__ */ new Map();
14159
14279
  const codeM = /* @__PURE__ */ new Map();
14160
- for (const [band, { L, C }] of idxMap) {
14280
+ for (const [band, { L, C: C2 }] of idxMap) {
14161
14281
  const freq = this.getFreqForPrn(prn, band);
14162
14282
  if (!freq) continue;
14163
14283
  const lVal = values[L];
14164
14284
  if (lVal != null && lVal !== 0) phaseM.set(band, lVal * (C_LIGHT / freq));
14165
- if (C !== null) {
14166
- const cVal = values[C];
14285
+ if (C2 !== null) {
14286
+ const cVal = values[C2];
14167
14287
  if (cVal != null && cVal !== 0) codeM.set(band, cVal);
14168
14288
  }
14169
14289
  }
@@ -14427,9 +14547,9 @@ var IonoAccumulator = class {
14427
14547
  const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
14428
14548
  if (!bandMap) return;
14429
14549
  const bandData = /* @__PURE__ */ new Map();
14430
- for (const [band, { C, L }] of bandMap) {
14431
- if (C === null) continue;
14432
- const cVal = values[C];
14550
+ for (const [band, { C: C2, L }] of bandMap) {
14551
+ if (C2 === null) continue;
14552
+ const cVal = values[C2];
14433
14553
  const lVal = values[L];
14434
14554
  const freq = getFreq(this.gloChannels, prn, band);
14435
14555
  if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
@@ -14437,7 +14557,7 @@ var IonoAccumulator = class {
14437
14557
  C: cVal,
14438
14558
  L: lVal,
14439
14559
  f: freq,
14440
- code: codes[C] ?? `C${band}`
14560
+ code: codes[C2] ?? `C${band}`
14441
14561
  });
14442
14562
  }
14443
14563
  }
@@ -16150,6 +16270,8 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16150
16270
  ecefToAzEl,
16151
16271
  ecefToGeodetic,
16152
16272
  ephInfoToEphemeris,
16273
+ estimateNarrowlaneFcb,
16274
+ estimateNetworkFcbs,
16153
16275
  estimateWidelaneFcb,
16154
16276
  euclidean3D,
16155
16277
  extractWidelaneArcs,
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, 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';
20
+ export { AntennaOffset, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, NetworkFcbResult, NetworkFcbSummary, NlArc, NlFcbResult, PppAntennaModel, PppArc, PppCorrectionConfig, PppCorrections, PppEpoch, PppEpochContext, PppEpochResult, PppOptions, PppSatObs, PppSolution, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SatGeom, SppOptions, SppSolution, TropoMapping, WlArc, WlFcbOptions, WlFcbResult, WlObs, buildPppAntenna, createPppCorrections, estimateNarrowlaneFcb, estimateNetworkFcbs, 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, 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';
20
+ export { AntennaOffset, ArFixedSat, ArOptions, ArResult, ArSat, CorrectionResult, DgnssOptions, DgnssSolution, EphemerisSource, IONO_L1_M_PER_TECU, KlobucharCoeffs, LambdaResult, NetworkFcbResult, NetworkFcbSummary, NlArc, NlFcbResult, PppAntennaModel, PppArc, PppCorrectionConfig, PppCorrections, PppEpoch, PppEpochContext, PppEpochResult, PppOptions, PppSatObs, PppSolution, RawObservation, RtkEpochMeasurements, RtkFloatEngine, RtkFloatOptions, RtkFloatSolution, RtkMeasurement, SatGeom, SppOptions, SppSolution, TropoMapping, WlArc, WlFcbOptions, WlFcbResult, WlObs, buildPppAntenna, createPppCorrections, estimateNarrowlaneFcb, estimateNetworkFcbs, 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
+ estimateNarrowlaneFcb,
150
+ estimateNetworkFcbs,
149
151
  estimateWidelaneFcb,
150
152
  extractWidelaneArcs,
151
153
  gimSlantIonoDelayL1,
@@ -167,7 +169,7 @@ import {
167
169
  sunEcef,
168
170
  toRtkEpoch,
169
171
  wlWavelength
170
- } from "./chunk-PVOF4IGX.js";
172
+ } from "./chunk-BLGBMRX7.js";
171
173
  import {
172
174
  almanacEpochMs,
173
175
  almanacSatPosition,
@@ -474,6 +476,8 @@ export {
474
476
  ecefToAzEl,
475
477
  ecefToGeodetic,
476
478
  ephInfoToEphemeris,
479
+ estimateNarrowlaneFcb,
480
+ estimateNetworkFcbs,
477
481
  estimateWidelaneFcb,
478
482
  euclidean3D,
479
483
  extractWidelaneArcs,
@@ -24,6 +24,8 @@ __export(positioning_exports, {
24
24
  RtkFloatEngine: () => RtkFloatEngine,
25
25
  buildPppAntenna: () => buildPppAntenna,
26
26
  createPppCorrections: () => createPppCorrections,
27
+ estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
28
+ estimateNetworkFcbs: () => estimateNetworkFcbs,
27
29
  estimateWidelaneFcb: () => estimateWidelaneFcb,
28
30
  extractWidelaneArcs: () => extractWidelaneArcs,
29
31
  gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
@@ -913,15 +915,15 @@ function matMul(A, B) {
913
915
  const n = A.length;
914
916
  const k = B.length;
915
917
  const m = B[0]?.length ?? 0;
916
- const C = zeros(n, m);
918
+ const C2 = zeros(n, m);
917
919
  for (let i = 0; i < n; i++) {
918
920
  for (let l = 0; l < k; l++) {
919
921
  const a = A[i][l];
920
922
  if (a === 0) continue;
921
- for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
923
+ for (let j = 0; j < m; j++) C2[i][j] += a * B[l][j];
922
924
  }
923
925
  }
924
- return C;
926
+ return C2;
925
927
  }
926
928
  function transpose(A) {
927
929
  const n = A.length;
@@ -2173,6 +2175,24 @@ function solvePpp(epochs, sp3, opts) {
2173
2175
  const clockSeeded = /* @__PURE__ */ new Set();
2174
2176
  const ambIdx = /* @__PURE__ */ new Map();
2175
2177
  const lastSeen = /* @__PURE__ */ new Map();
2178
+ const arcs = [];
2179
+ const arcSnap = /* @__PURE__ */ new Map();
2180
+ const flushArc = (prn, minEpochs = 10) => {
2181
+ const s = arcSnap.get(prn);
2182
+ arcSnap.delete(prn);
2183
+ if (!s || s.nEpochs < minEpochs) return;
2184
+ arcs.push({
2185
+ prn,
2186
+ aIF: s.aIF,
2187
+ mwCyc: s.mwCyc,
2188
+ f1: s.f1,
2189
+ f2: s.f2,
2190
+ meanElevDeg: s.elevSum / s.nEpochs,
2191
+ nEpochs: s.nEpochs,
2192
+ startMs: s.startMs,
2193
+ endMs: s.endMs
2194
+ });
2195
+ };
2176
2196
  const mwState = /* @__PURE__ */ new Map();
2177
2197
  const detectSlip = (o, ei) => {
2178
2198
  const lamW = C_LIGHT / (o.f1 - o.f2);
@@ -2290,7 +2310,9 @@ function solvePpp(epochs, sp3, opts) {
2290
2310
  tropoFixed: zhd * mh + corr.rangeM,
2291
2311
  mw,
2292
2312
  windupM: corr.phaseWindupM,
2293
- slip
2313
+ slip,
2314
+ f1: o.f1,
2315
+ f2: o.f2
2294
2316
  });
2295
2317
  }
2296
2318
  const zwd0 = x[IDX_ZWD];
@@ -2315,6 +2337,7 @@ function solvePpp(epochs, sp3, opts) {
2315
2337
  for (const v of vis) {
2316
2338
  const ci = clockOf(v.prn[0]);
2317
2339
  const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
2340
+ if (v.slip) flushArc(v.prn);
2318
2341
  let ai = ambIdx.get(v.prn);
2319
2342
  if (ai === void 0 || v.slip) {
2320
2343
  const bInit = v.lIf - (common + v.windupM);
@@ -2348,6 +2371,7 @@ function solvePpp(epochs, sp3, opts) {
2348
2371
  const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
2349
2372
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
2350
2373
  if (Math.abs(innov) > 0.15) {
2374
+ flushArc(v.prn);
2351
2375
  x[ai] = v.lIf - (commonPh + v.windupM);
2352
2376
  for (let j = 0; j < P.length; j++) {
2353
2377
  P[ai][j] = 0;
@@ -2365,6 +2389,29 @@ function solvePpp(epochs, sp3, opts) {
2365
2389
  ekfUpdateScalar(x, P, h, innov, rPhase, 4);
2366
2390
  }
2367
2391
  }
2392
+ {
2393
+ const elevDeg = v.elRad * 180 / Math.PI;
2394
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
2395
+ const s = arcSnap.get(v.prn);
2396
+ if (s) {
2397
+ s.aIF = x[ai];
2398
+ s.mwCyc = mwMean;
2399
+ s.elevSum += elevDeg;
2400
+ s.nEpochs++;
2401
+ s.endMs = epoch.timeMs;
2402
+ } else {
2403
+ arcSnap.set(v.prn, {
2404
+ aIF: x[ai],
2405
+ mwCyc: mwMean,
2406
+ f1: v.f1,
2407
+ f2: v.f2,
2408
+ elevSum: elevDeg,
2409
+ nEpochs: 1,
2410
+ startMs: epoch.timeMs,
2411
+ endMs: epoch.timeMs
2412
+ });
2413
+ }
2414
+ }
2368
2415
  lastSeen.set(v.prn, ei);
2369
2416
  nSats++;
2370
2417
  }
@@ -2398,6 +2445,7 @@ function solvePpp(epochs, sp3, opts) {
2398
2445
  const idx = ambIdx.get(prn);
2399
2446
  if (idx !== void 0) dropState(idx);
2400
2447
  lastSeen.delete(prn);
2448
+ flushArc(prn);
2401
2449
  }
2402
2450
  }
2403
2451
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
@@ -2437,6 +2485,7 @@ function solvePpp(epochs, sp3, opts) {
2437
2485
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
2438
2486
  });
2439
2487
  }
2488
+ for (const prn of [...arcSnap.keys()]) flushArc(prn);
2440
2489
  const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
2441
2490
  const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
2442
2491
  const finalError3d = truth ? Math.hypot(
@@ -2451,7 +2500,8 @@ function solvePpp(epochs, sp3, opts) {
2451
2500
  ztdWet: x[IDX_ZWD],
2452
2501
  epochsUsed: epochs.length,
2453
2502
  convergenceSec,
2454
- finalError3d
2503
+ finalError3d,
2504
+ arcs
2455
2505
  };
2456
2506
  }
2457
2507
 
@@ -2592,34 +2642,34 @@ function circMean(vals, weights) {
2592
2642
  if (s === 0 && c === 0) return 0;
2593
2643
  return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
2594
2644
  }
2595
- function estimateWidelaneFcb(arcs, opts = {}) {
2645
+ function decomposeFcb(entries, opts = {}) {
2596
2646
  const maxIter = opts.maxIter ?? 50;
2597
2647
  const tol = opts.tol ?? 1e-4;
2598
2648
  const fixWindow = opts.fixWindow ?? 0.25;
2599
- const sats = [...new Set(arcs.map((a) => a.prn))].sort();
2600
- const stations = [...new Set(arcs.map((a) => a.station))].sort();
2649
+ const sats = [...new Set(entries.map((e) => e.sat))].sort();
2650
+ const stations = [...new Set(entries.map((e) => e.station))].sort();
2601
2651
  const refStation = stations[0] ?? "";
2602
2652
  const satFcb = new Map(sats.map((s) => [s, 0]));
2603
2653
  const rcvBias = new Map(stations.map((s) => [s, 0]));
2604
- const nInt = new Array(arcs.length).fill(0);
2654
+ const nInt = new Array(entries.length).fill(0);
2605
2655
  let iterations = 0;
2606
2656
  for (let it = 0; it < maxIter; it++) {
2607
2657
  iterations = it + 1;
2608
- for (let i = 0; i < arcs.length; i++) {
2609
- const a = arcs[i];
2658
+ for (let i = 0; i < entries.length; i++) {
2659
+ const e = entries[i];
2610
2660
  nInt[i] = Math.round(
2611
- a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
2661
+ e.value - rcvBias.get(e.station) - satFcb.get(e.sat)
2612
2662
  );
2613
2663
  }
2614
2664
  let maxChange = 0;
2615
2665
  for (const s of sats) {
2616
2666
  const vals = [];
2617
2667
  const w = [];
2618
- for (let i = 0; i < arcs.length; i++) {
2619
- const a = arcs[i];
2620
- if (a.prn !== s) continue;
2621
- vals.push(a.wlFloat - nInt[i] - rcvBias.get(a.station));
2622
- w.push(a.nObs);
2668
+ for (let i = 0; i < entries.length; i++) {
2669
+ const e = entries[i];
2670
+ if (e.sat !== s) continue;
2671
+ vals.push(e.value - nInt[i] - rcvBias.get(e.station));
2672
+ w.push(e.weight);
2623
2673
  }
2624
2674
  if (!vals.length) continue;
2625
2675
  const nv = circMean(vals, w);
@@ -2629,11 +2679,11 @@ function estimateWidelaneFcb(arcs, opts = {}) {
2629
2679
  for (const r of stations) {
2630
2680
  const vals = [];
2631
2681
  const w = [];
2632
- for (let i = 0; i < arcs.length; i++) {
2633
- const a = arcs[i];
2634
- if (a.station !== r) continue;
2635
- vals.push(a.wlFloat - nInt[i] - satFcb.get(a.prn));
2636
- w.push(a.nObs);
2682
+ for (let i = 0; i < entries.length; i++) {
2683
+ const e = entries[i];
2684
+ if (e.station !== r) continue;
2685
+ vals.push(e.value - nInt[i] - satFcb.get(e.sat));
2686
+ w.push(e.weight);
2637
2687
  }
2638
2688
  if (!vals.length) continue;
2639
2689
  rcvBias.set(r, circMean(vals, w));
@@ -2648,18 +2698,88 @@ function estimateWidelaneFcb(arcs, opts = {}) {
2648
2698
  }
2649
2699
  let sumSq = 0;
2650
2700
  let fixed = 0;
2651
- for (let i = 0; i < arcs.length; i++) {
2652
- const a = arcs[i];
2701
+ for (let i = 0; i < entries.length; i++) {
2702
+ const e = entries[i];
2653
2703
  const resid = wrapHalf(
2654
- a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
2704
+ e.value - nInt[i] - rcvBias.get(e.station) - satFcb.get(e.sat)
2655
2705
  );
2656
2706
  sumSq += resid * resid;
2657
2707
  if (Math.abs(resid) <= fixWindow) fixed++;
2658
2708
  }
2659
- const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
2660
- const fixRate = arcs.length ? fixed / arcs.length : 0;
2709
+ const residRms = entries.length ? Math.sqrt(sumSq / entries.length) : 0;
2710
+ const fixRate = entries.length ? fixed / entries.length : 0;
2661
2711
  return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
2662
2712
  }
2713
+ function estimateWidelaneFcb(arcs, opts = {}) {
2714
+ return decomposeFcb(
2715
+ arcs.map((a) => ({
2716
+ station: a.station,
2717
+ sat: a.prn,
2718
+ value: a.wlFloat,
2719
+ weight: a.nObs
2720
+ })),
2721
+ opts
2722
+ );
2723
+ }
2724
+ var C = 299792458;
2725
+ function estimateNarrowlaneFcb(arcs, wl, opts = {}) {
2726
+ const entries = [];
2727
+ let wlRejected = 0;
2728
+ for (const a of arcs) {
2729
+ const satWl = wl.satFcb.get(a.prn);
2730
+ const rcvWl = wl.rcvBias.get(a.station);
2731
+ if (satWl === void 0 || rcvWl === void 0) continue;
2732
+ const wlFloat = a.mwCyc - satWl - rcvWl;
2733
+ const nWl = Math.round(wlFloat);
2734
+ if (Math.abs(wlFloat - nWl) > (opts.fixWindow ?? 0.25)) {
2735
+ wlRejected++;
2736
+ continue;
2737
+ }
2738
+ const lamNl = C / (a.f1 + a.f2);
2739
+ const n1Float = a.aIF / lamNl - a.f2 / (a.f1 - a.f2) * nWl;
2740
+ entries.push({
2741
+ station: a.station,
2742
+ sat: a.prn,
2743
+ value: n1Float,
2744
+ weight: a.nEpochs
2745
+ });
2746
+ }
2747
+ const res = decomposeFcb(entries, { fixWindow: 0.15, ...opts });
2748
+ return { ...res, usedArcs: entries.length, wlRejected };
2749
+ }
2750
+ function estimateNetworkFcbs(arcs, opts = {}) {
2751
+ const minArcEpochs = opts.minArcEpochs ?? 120;
2752
+ const minWlObs = opts.minWlObs ?? 40;
2753
+ const fixWindow = opts.fixWindow ?? 0.15;
2754
+ const satWlFcb = /* @__PURE__ */ new Map();
2755
+ const satNlFcb = /* @__PURE__ */ new Map();
2756
+ const perSystem = {};
2757
+ const systems = [...new Set(arcs.map((a) => a.prn[0]))].sort();
2758
+ for (const sys of systems) {
2759
+ const sysArcs = arcs.filter((a) => a.prn[0] === sys);
2760
+ const wlArcs = sysArcs.filter((a) => a.nEpochs >= minWlObs).map((a) => ({
2761
+ station: a.station,
2762
+ prn: a.prn,
2763
+ wlFloat: a.mwCyc,
2764
+ nObs: a.nEpochs
2765
+ }));
2766
+ if (wlArcs.length < 3) continue;
2767
+ const wl = estimateWidelaneFcb(wlArcs, { fixWindow });
2768
+ const nlInput = sysArcs.filter((a) => a.nEpochs >= minArcEpochs);
2769
+ const nl = estimateNarrowlaneFcb(nlInput, wl, { fixWindow });
2770
+ for (const [prn, v] of wl.satFcb) satWlFcb.set(prn, v);
2771
+ for (const [prn, v] of nl.satFcb) satNlFcb.set(prn, v);
2772
+ perSystem[sys] = {
2773
+ wlFixRate: wl.fixRate,
2774
+ wlResidRms: wl.residRms,
2775
+ nlUsedArcs: nl.usedArcs,
2776
+ nlFixRate: nl.fixRate,
2777
+ nlResidRms: nl.residRms,
2778
+ wlRejected: nl.wlRejected
2779
+ };
2780
+ }
2781
+ return { satWlFcb, satNlFcb, perSystem };
2782
+ }
2663
2783
 
2664
2784
  // src/positioning/index.ts
2665
2785
  var GM_GPS2 = 3986005e8;
@@ -2894,6 +3014,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
2894
3014
  RtkFloatEngine,
2895
3015
  buildPppAntenna,
2896
3016
  createPppCorrections,
3017
+ estimateNarrowlaneFcb,
3018
+ estimateNetworkFcbs,
2897
3019
  estimateWidelaneFcb,
2898
3020
  extractWidelaneArcs,
2899
3021
  gimSlantIonoDelayL1,
@@ -666,6 +666,26 @@ interface PppSolution {
666
666
  convergenceSec: number | null;
667
667
  /** Final 3D error vs ground truth (m), if given. */
668
668
  finalError3d: number | null;
669
+ /** Per-arc converged float ambiguities — one per continuous satellite
670
+ * tracking arc — the raw material for ambiguity resolution (PPP-AR). */
671
+ arcs: PppArc[];
672
+ }
673
+ /** A converged carrier-phase arc: the float ionosphere-free ambiguity plus
674
+ * the wide-lane, for integer resolution / FCB estimation. */
675
+ interface PppArc {
676
+ prn: string;
677
+ /** Final float ionosphere-free ambiguity (m). */
678
+ aIF: number;
679
+ /** Arc-averaged Melbourne–Wübbena wide-lane (cycles). */
680
+ mwCyc: number;
681
+ f1: number;
682
+ f2: number;
683
+ /** Mean satellite elevation over the arc (deg). */
684
+ meanElevDeg: number;
685
+ /** Epochs in the arc. */
686
+ nEpochs: number;
687
+ startMs: number;
688
+ endMs: number;
669
689
  }
670
690
  declare function solvePpp(epochs: PppEpoch[], sp3: Sp3File, opts: PppOptions): PppSolution;
671
691
 
@@ -879,6 +899,73 @@ interface WlFcbResult {
879
899
  * integer/bias entanglement.
880
900
  */
881
901
  declare function estimateWidelaneFcb(arcs: WlArc[], opts?: WlFcbOptions): WlFcbResult;
902
+ /** One converged arc (from `solvePpp`) tagged with its station, for NL FCB. */
903
+ interface NlArc {
904
+ station: string;
905
+ prn: string;
906
+ /** Float ionosphere-free ambiguity (m). */
907
+ aIF: number;
908
+ /** Arc-averaged Melbourne–Wübbena wide-lane (cycles). */
909
+ mwCyc: number;
910
+ f1: number;
911
+ f2: number;
912
+ nEpochs: number;
913
+ }
914
+ interface NlFcbResult extends WlFcbResult {
915
+ /** Arcs whose wide-lane fixed and were used in the narrow-lane estimation. */
916
+ usedArcs: number;
917
+ /** Arcs dropped because the wide-lane did not round confidently. */
918
+ wlRejected: number;
919
+ }
920
+ /**
921
+ * Estimate per-satellite **narrow-lane** FCBs — the centimetre half of
922
+ * PPP-AR. Needs the wide-lane FCBs already solved (`wl`, from the same
923
+ * network) to fix each arc's N_WL, from which the float narrow-lane
924
+ * ambiguity is recovered:
925
+ *
926
+ * A_IF = λ_NL·N1 + λ_NL·(f2/(f1−f2))·N_WL
927
+ * ⇒ N1_float = A_IF/λ_NL − (f2/(f1−f2))·N_WL, λ_NL = c/(f1+f2)
928
+ *
929
+ * then decomposed across the network exactly like the wide-lane. The
930
+ * narrow-lane wavelength is ~10.7 cm, so this is far more sensitive to
931
+ * residual orbit/clock/troposphere/position error than the ~86 cm wide-lane
932
+ * — the fix rate is the honest measure of how close the network is to cm.
933
+ */
934
+ declare function estimateNarrowlaneFcb(arcs: NlArc[], wl: WlFcbResult, opts?: WlFcbOptions): NlFcbResult;
935
+ interface NetworkFcbSummary {
936
+ wlFixRate: number;
937
+ wlResidRms: number;
938
+ nlUsedArcs: number;
939
+ nlFixRate: number;
940
+ nlResidRms: number;
941
+ wlRejected: number;
942
+ }
943
+ interface NetworkFcbResult {
944
+ /** Per-satellite wide-lane FCB (cycles), all constellations merged. */
945
+ satWlFcb: Map<string, number>;
946
+ /** Per-satellite narrow-lane FCB (cycles), all constellations merged. */
947
+ satNlFcb: Map<string, number>;
948
+ /** Per-constellation fix-rate / residual summary, keyed by system letter. */
949
+ perSystem: Record<string, NetworkFcbSummary>;
950
+ }
951
+ /**
952
+ * Full network FCB calibration for PPP-AR from a set of `solvePpp` arcs (each
953
+ * tagged with its station), **per constellation** — the receiver wide- and
954
+ * narrow-lane biases are system-specific, so lumping GPS/Galileo/BeiDou under
955
+ * one datum corrupts the fit. Runs the wide-lane then narrow-lane
956
+ * decomposition for each system and merges the per-satellite biases.
957
+ *
958
+ * The satellite FCBs (`satWlFcb`/`satNlFcb`) are the reusable product: a rover
959
+ * applies them and solves only its own receiver bias. Multi-GNSS input
960
+ * multiplies the fixable arcs (Galileo roughly doubles GPS-only) without
961
+ * changing per-arc behaviour — each satellite's arc length is independent of
962
+ * how many constellations are present.
963
+ */
964
+ declare function estimateNetworkFcbs(arcs: NlArc[], opts?: {
965
+ minArcEpochs?: number;
966
+ minWlObs?: number;
967
+ fixWindow?: number;
968
+ }): NetworkFcbResult;
882
969
 
883
970
  /**
884
971
  * Single-point positioning (SPP) from pseudoranges and broadcast
@@ -969,4 +1056,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
969
1056
  */
970
1057
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
971
1058
 
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 };
1059
+ 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 NetworkFcbResult, type NetworkFcbSummary, type NlArc, type NlFcbResult, type PppAntennaModel, type PppArc, 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, estimateNarrowlaneFcb, estimateNetworkFcbs, estimateWidelaneFcb, extractWidelaneArcs, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength };
@@ -666,6 +666,26 @@ interface PppSolution {
666
666
  convergenceSec: number | null;
667
667
  /** Final 3D error vs ground truth (m), if given. */
668
668
  finalError3d: number | null;
669
+ /** Per-arc converged float ambiguities — one per continuous satellite
670
+ * tracking arc — the raw material for ambiguity resolution (PPP-AR). */
671
+ arcs: PppArc[];
672
+ }
673
+ /** A converged carrier-phase arc: the float ionosphere-free ambiguity plus
674
+ * the wide-lane, for integer resolution / FCB estimation. */
675
+ interface PppArc {
676
+ prn: string;
677
+ /** Final float ionosphere-free ambiguity (m). */
678
+ aIF: number;
679
+ /** Arc-averaged Melbourne–Wübbena wide-lane (cycles). */
680
+ mwCyc: number;
681
+ f1: number;
682
+ f2: number;
683
+ /** Mean satellite elevation over the arc (deg). */
684
+ meanElevDeg: number;
685
+ /** Epochs in the arc. */
686
+ nEpochs: number;
687
+ startMs: number;
688
+ endMs: number;
669
689
  }
670
690
  declare function solvePpp(epochs: PppEpoch[], sp3: Sp3File, opts: PppOptions): PppSolution;
671
691
 
@@ -879,6 +899,73 @@ interface WlFcbResult {
879
899
  * integer/bias entanglement.
880
900
  */
881
901
  declare function estimateWidelaneFcb(arcs: WlArc[], opts?: WlFcbOptions): WlFcbResult;
902
+ /** One converged arc (from `solvePpp`) tagged with its station, for NL FCB. */
903
+ interface NlArc {
904
+ station: string;
905
+ prn: string;
906
+ /** Float ionosphere-free ambiguity (m). */
907
+ aIF: number;
908
+ /** Arc-averaged Melbourne–Wübbena wide-lane (cycles). */
909
+ mwCyc: number;
910
+ f1: number;
911
+ f2: number;
912
+ nEpochs: number;
913
+ }
914
+ interface NlFcbResult extends WlFcbResult {
915
+ /** Arcs whose wide-lane fixed and were used in the narrow-lane estimation. */
916
+ usedArcs: number;
917
+ /** Arcs dropped because the wide-lane did not round confidently. */
918
+ wlRejected: number;
919
+ }
920
+ /**
921
+ * Estimate per-satellite **narrow-lane** FCBs — the centimetre half of
922
+ * PPP-AR. Needs the wide-lane FCBs already solved (`wl`, from the same
923
+ * network) to fix each arc's N_WL, from which the float narrow-lane
924
+ * ambiguity is recovered:
925
+ *
926
+ * A_IF = λ_NL·N1 + λ_NL·(f2/(f1−f2))·N_WL
927
+ * ⇒ N1_float = A_IF/λ_NL − (f2/(f1−f2))·N_WL, λ_NL = c/(f1+f2)
928
+ *
929
+ * then decomposed across the network exactly like the wide-lane. The
930
+ * narrow-lane wavelength is ~10.7 cm, so this is far more sensitive to
931
+ * residual orbit/clock/troposphere/position error than the ~86 cm wide-lane
932
+ * — the fix rate is the honest measure of how close the network is to cm.
933
+ */
934
+ declare function estimateNarrowlaneFcb(arcs: NlArc[], wl: WlFcbResult, opts?: WlFcbOptions): NlFcbResult;
935
+ interface NetworkFcbSummary {
936
+ wlFixRate: number;
937
+ wlResidRms: number;
938
+ nlUsedArcs: number;
939
+ nlFixRate: number;
940
+ nlResidRms: number;
941
+ wlRejected: number;
942
+ }
943
+ interface NetworkFcbResult {
944
+ /** Per-satellite wide-lane FCB (cycles), all constellations merged. */
945
+ satWlFcb: Map<string, number>;
946
+ /** Per-satellite narrow-lane FCB (cycles), all constellations merged. */
947
+ satNlFcb: Map<string, number>;
948
+ /** Per-constellation fix-rate / residual summary, keyed by system letter. */
949
+ perSystem: Record<string, NetworkFcbSummary>;
950
+ }
951
+ /**
952
+ * Full network FCB calibration for PPP-AR from a set of `solvePpp` arcs (each
953
+ * tagged with its station), **per constellation** — the receiver wide- and
954
+ * narrow-lane biases are system-specific, so lumping GPS/Galileo/BeiDou under
955
+ * one datum corrupts the fit. Runs the wide-lane then narrow-lane
956
+ * decomposition for each system and merges the per-satellite biases.
957
+ *
958
+ * The satellite FCBs (`satWlFcb`/`satNlFcb`) are the reusable product: a rover
959
+ * applies them and solves only its own receiver bias. Multi-GNSS input
960
+ * multiplies the fixable arcs (Galileo roughly doubles GPS-only) without
961
+ * changing per-arc behaviour — each satellite's arc length is independent of
962
+ * how many constellations are present.
963
+ */
964
+ declare function estimateNetworkFcbs(arcs: NlArc[], opts?: {
965
+ minArcEpochs?: number;
966
+ minWlObs?: number;
967
+ fixWindow?: number;
968
+ }): NetworkFcbResult;
882
969
 
883
970
  /**
884
971
  * Single-point positioning (SPP) from pseudoranges and broadcast
@@ -969,4 +1056,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
969
1056
  */
970
1057
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
971
1058
 
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 };
1059
+ 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 NetworkFcbResult, type NetworkFcbSummary, type NlArc, type NlFcbResult, type PppAntennaModel, type PppArc, 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, estimateNarrowlaneFcb, estimateNetworkFcbs, 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
+ estimateNarrowlaneFcb,
7
+ estimateNetworkFcbs,
6
8
  estimateWidelaneFcb,
7
9
  extractWidelaneArcs,
8
10
  gimSlantIonoDelayL1,
@@ -24,7 +26,7 @@ import {
24
26
  sunEcef,
25
27
  toRtkEpoch,
26
28
  wlWavelength
27
- } from "./chunk-PVOF4IGX.js";
29
+ } from "./chunk-BLGBMRX7.js";
28
30
  import "./chunk-MHGNKSSH.js";
29
31
  import "./chunk-37QNKGTC.js";
30
32
  import "./chunk-6FAL6P4G.js";
@@ -37,6 +39,8 @@ export {
37
39
  RtkFloatEngine,
38
40
  buildPppAntenna,
39
41
  createPppCorrections,
42
+ estimateNarrowlaneFcb,
43
+ estimateNetworkFcbs,
40
44
  estimateWidelaneFcb,
41
45
  extractWidelaneArcs,
42
46
  gimSlantIonoDelayL1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.33.0",
3
+ "version": "1.35.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,