gnss-js 1.33.0 → 1.34.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,55 @@ 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
+ }
2139
2224
 
2140
2225
  // src/positioning/index.ts
2141
2226
  var GM_GPS = 3986005e8;
@@ -2388,6 +2473,7 @@ export {
2388
2473
  resolvePppAmbiguities,
2389
2474
  extractWidelaneArcs,
2390
2475
  estimateWidelaneFcb,
2476
+ estimateNarrowlaneFcb,
2391
2477
  satClockCorrection,
2392
2478
  ionoFree,
2393
2479
  solveSpp
package/dist/index.cjs CHANGED
@@ -149,6 +149,7 @@ __export(src_exports, {
149
149
  ecefToAzEl: () => ecefToAzEl,
150
150
  ecefToGeodetic: () => ecefToGeodetic,
151
151
  ephInfoToEphemeris: () => ephInfoToEphemeris,
152
+ estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
152
153
  estimateWidelaneFcb: () => estimateWidelaneFcb,
153
154
  euclidean3D: () => euclidean3D,
154
155
  extractWidelaneArcs: () => extractWidelaneArcs,
@@ -1067,7 +1068,7 @@ function vincenty(lat1, lon1, lat2, lon2) {
1067
1068
  let lambdaPrev;
1068
1069
  let sinSigma, cosSigma, sigma;
1069
1070
  let sinAlpha, cos2Alpha, cos2SigmaM;
1070
- let C;
1071
+ let C2;
1071
1072
  let iter = 0;
1072
1073
  do {
1073
1074
  const sinLambda2 = Math.sin(lambda);
@@ -1083,9 +1084,9 @@ function vincenty(lat1, lon1, lat2, lon2) {
1083
1084
  sinAlpha = cosU1 * cosU2 * sinLambda2 / sinSigma;
1084
1085
  cos2Alpha = 1 - sinAlpha ** 2;
1085
1086
  cos2SigmaM = cos2Alpha !== 0 ? cosSigma - 2 * sinU1 * sinU2 / cos2Alpha : 0;
1086
- C = WGS84_FLATTENING / 16 * cos2Alpha * (4 + WGS84_FLATTENING * (4 - 3 * cos2Alpha));
1087
+ C2 = WGS84_FLATTENING / 16 * cos2Alpha * (4 + WGS84_FLATTENING * (4 - 3 * cos2Alpha));
1087
1088
  lambdaPrev = lambda;
1088
- lambda = lon2 - lon1 + (1 - C) * WGS84_FLATTENING * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)));
1089
+ lambda = lon2 - lon1 + (1 - C2) * WGS84_FLATTENING * sinAlpha * (sigma + C2 * sinSigma * (cos2SigmaM + C2 * cosSigma * (-1 + 2 * cos2SigmaM ** 2)));
1089
1090
  } while (Math.abs(lambda - lambdaPrev) > 1e-12 && ++iter < 200);
1090
1091
  if (iter >= 200) {
1091
1092
  const cosLat1 = Math.cos(lat1), sinLat1 = Math.sin(lat1);
@@ -1189,11 +1190,11 @@ function geodeticToUtm(lat, lon) {
1189
1190
  const ep2 = e2 / (1 - e2);
1190
1191
  const N = WGS84_SEMI_MAJOR_AXIS / Math.sqrt(1 - e2 * Math.sin(lat) ** 2);
1191
1192
  const T = Math.tan(lat) ** 2;
1192
- const C = ep2 * Math.cos(lat) ** 2;
1193
+ const C2 = ep2 * Math.cos(lat) ** 2;
1193
1194
  const A = Math.cos(lat) * (lon - lon0);
1194
1195
  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));
1196
+ const easting = k0 * N * (A + (1 - T + C2) * A ** 3 / 6 + (5 - 18 * T + T ** 2 + 72 * C2 - 58 * ep2) * A ** 5 / 120) + 5e5;
1197
+ 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
1198
  const hemisphere = latDeg >= 0 ? "N" : "S";
1198
1199
  if (hemisphere === "S") {
1199
1200
  northing += 1e7;
@@ -11857,15 +11858,15 @@ function matMul(A, B) {
11857
11858
  const n = A.length;
11858
11859
  const k = B.length;
11859
11860
  const m = B[0]?.length ?? 0;
11860
- const C = zeros(n, m);
11861
+ const C2 = zeros(n, m);
11861
11862
  for (let i = 0; i < n; i++) {
11862
11863
  for (let l = 0; l < k; l++) {
11863
11864
  const a = A[i][l];
11864
11865
  if (a === 0) continue;
11865
- for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
11866
+ for (let j = 0; j < m; j++) C2[i][j] += a * B[l][j];
11866
11867
  }
11867
11868
  }
11868
- return C;
11869
+ return C2;
11869
11870
  }
11870
11871
  function transpose(A) {
11871
11872
  const n = A.length;
@@ -13045,6 +13046,24 @@ function solvePpp(epochs, sp3, opts) {
13045
13046
  const clockSeeded = /* @__PURE__ */ new Set();
13046
13047
  const ambIdx = /* @__PURE__ */ new Map();
13047
13048
  const lastSeen = /* @__PURE__ */ new Map();
13049
+ const arcs = [];
13050
+ const arcSnap = /* @__PURE__ */ new Map();
13051
+ const flushArc = (prn, minEpochs = 10) => {
13052
+ const s = arcSnap.get(prn);
13053
+ arcSnap.delete(prn);
13054
+ if (!s || s.nEpochs < minEpochs) return;
13055
+ arcs.push({
13056
+ prn,
13057
+ aIF: s.aIF,
13058
+ mwCyc: s.mwCyc,
13059
+ f1: s.f1,
13060
+ f2: s.f2,
13061
+ meanElevDeg: s.elevSum / s.nEpochs,
13062
+ nEpochs: s.nEpochs,
13063
+ startMs: s.startMs,
13064
+ endMs: s.endMs
13065
+ });
13066
+ };
13048
13067
  const mwState = /* @__PURE__ */ new Map();
13049
13068
  const detectSlip = (o, ei) => {
13050
13069
  const lamW = C_LIGHT / (o.f1 - o.f2);
@@ -13162,7 +13181,9 @@ function solvePpp(epochs, sp3, opts) {
13162
13181
  tropoFixed: zhd * mh + corr.rangeM,
13163
13182
  mw,
13164
13183
  windupM: corr.phaseWindupM,
13165
- slip
13184
+ slip,
13185
+ f1: o.f1,
13186
+ f2: o.f2
13166
13187
  });
13167
13188
  }
13168
13189
  const zwd0 = x[IDX_ZWD];
@@ -13187,6 +13208,7 @@ function solvePpp(epochs, sp3, opts) {
13187
13208
  for (const v of vis) {
13188
13209
  const ci = clockOf(v.prn[0]);
13189
13210
  const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
13211
+ if (v.slip) flushArc(v.prn);
13190
13212
  let ai = ambIdx.get(v.prn);
13191
13213
  if (ai === void 0 || v.slip) {
13192
13214
  const bInit = v.lIf - (common + v.windupM);
@@ -13220,6 +13242,7 @@ function solvePpp(epochs, sp3, opts) {
13220
13242
  const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
13221
13243
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
13222
13244
  if (Math.abs(innov) > 0.15) {
13245
+ flushArc(v.prn);
13223
13246
  x[ai] = v.lIf - (commonPh + v.windupM);
13224
13247
  for (let j = 0; j < P.length; j++) {
13225
13248
  P[ai][j] = 0;
@@ -13237,6 +13260,29 @@ function solvePpp(epochs, sp3, opts) {
13237
13260
  ekfUpdateScalar(x, P, h, innov, rPhase, 4);
13238
13261
  }
13239
13262
  }
13263
+ {
13264
+ const elevDeg = v.elRad * 180 / Math.PI;
13265
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
13266
+ const s = arcSnap.get(v.prn);
13267
+ if (s) {
13268
+ s.aIF = x[ai];
13269
+ s.mwCyc = mwMean;
13270
+ s.elevSum += elevDeg;
13271
+ s.nEpochs++;
13272
+ s.endMs = epoch.timeMs;
13273
+ } else {
13274
+ arcSnap.set(v.prn, {
13275
+ aIF: x[ai],
13276
+ mwCyc: mwMean,
13277
+ f1: v.f1,
13278
+ f2: v.f2,
13279
+ elevSum: elevDeg,
13280
+ nEpochs: 1,
13281
+ startMs: epoch.timeMs,
13282
+ endMs: epoch.timeMs
13283
+ });
13284
+ }
13285
+ }
13240
13286
  lastSeen.set(v.prn, ei);
13241
13287
  nSats++;
13242
13288
  }
@@ -13270,6 +13316,7 @@ function solvePpp(epochs, sp3, opts) {
13270
13316
  const idx = ambIdx.get(prn);
13271
13317
  if (idx !== void 0) dropState(idx);
13272
13318
  lastSeen.delete(prn);
13319
+ flushArc(prn);
13273
13320
  }
13274
13321
  }
13275
13322
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
@@ -13309,6 +13356,7 @@ function solvePpp(epochs, sp3, opts) {
13309
13356
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
13310
13357
  });
13311
13358
  }
13359
+ for (const prn of [...arcSnap.keys()]) flushArc(prn);
13312
13360
  const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
13313
13361
  const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
13314
13362
  const finalError3d = truth ? Math.hypot(
@@ -13323,7 +13371,8 @@ function solvePpp(epochs, sp3, opts) {
13323
13371
  ztdWet: x[IDX_ZWD],
13324
13372
  epochsUsed: epochs.length,
13325
13373
  convergenceSec,
13326
- finalError3d
13374
+ finalError3d,
13375
+ arcs
13327
13376
  };
13328
13377
  }
13329
13378
 
@@ -13464,34 +13513,34 @@ function circMean(vals, weights) {
13464
13513
  if (s === 0 && c === 0) return 0;
13465
13514
  return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
13466
13515
  }
13467
- function estimateWidelaneFcb(arcs, opts = {}) {
13516
+ function decomposeFcb(entries, opts = {}) {
13468
13517
  const maxIter = opts.maxIter ?? 50;
13469
13518
  const tol = opts.tol ?? 1e-4;
13470
13519
  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();
13520
+ const sats = [...new Set(entries.map((e) => e.sat))].sort();
13521
+ const stations = [...new Set(entries.map((e) => e.station))].sort();
13473
13522
  const refStation = stations[0] ?? "";
13474
13523
  const satFcb = new Map(sats.map((s) => [s, 0]));
13475
13524
  const rcvBias = new Map(stations.map((s) => [s, 0]));
13476
- const nInt = new Array(arcs.length).fill(0);
13525
+ const nInt = new Array(entries.length).fill(0);
13477
13526
  let iterations = 0;
13478
13527
  for (let it = 0; it < maxIter; it++) {
13479
13528
  iterations = it + 1;
13480
- for (let i = 0; i < arcs.length; i++) {
13481
- const a = arcs[i];
13529
+ for (let i = 0; i < entries.length; i++) {
13530
+ const e = entries[i];
13482
13531
  nInt[i] = Math.round(
13483
- a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
13532
+ e.value - rcvBias.get(e.station) - satFcb.get(e.sat)
13484
13533
  );
13485
13534
  }
13486
13535
  let maxChange = 0;
13487
13536
  for (const s of sats) {
13488
13537
  const vals = [];
13489
13538
  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);
13539
+ for (let i = 0; i < entries.length; i++) {
13540
+ const e = entries[i];
13541
+ if (e.sat !== s) continue;
13542
+ vals.push(e.value - nInt[i] - rcvBias.get(e.station));
13543
+ w.push(e.weight);
13495
13544
  }
13496
13545
  if (!vals.length) continue;
13497
13546
  const nv = circMean(vals, w);
@@ -13501,11 +13550,11 @@ function estimateWidelaneFcb(arcs, opts = {}) {
13501
13550
  for (const r of stations) {
13502
13551
  const vals = [];
13503
13552
  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);
13553
+ for (let i = 0; i < entries.length; i++) {
13554
+ const e = entries[i];
13555
+ if (e.station !== r) continue;
13556
+ vals.push(e.value - nInt[i] - satFcb.get(e.sat));
13557
+ w.push(e.weight);
13509
13558
  }
13510
13559
  if (!vals.length) continue;
13511
13560
  rcvBias.set(r, circMean(vals, w));
@@ -13520,18 +13569,55 @@ function estimateWidelaneFcb(arcs, opts = {}) {
13520
13569
  }
13521
13570
  let sumSq = 0;
13522
13571
  let fixed = 0;
13523
- for (let i = 0; i < arcs.length; i++) {
13524
- const a = arcs[i];
13572
+ for (let i = 0; i < entries.length; i++) {
13573
+ const e = entries[i];
13525
13574
  const resid = wrapHalf(
13526
- a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
13575
+ e.value - nInt[i] - rcvBias.get(e.station) - satFcb.get(e.sat)
13527
13576
  );
13528
13577
  sumSq += resid * resid;
13529
13578
  if (Math.abs(resid) <= fixWindow) fixed++;
13530
13579
  }
13531
- const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
13532
- const fixRate = arcs.length ? fixed / arcs.length : 0;
13580
+ const residRms = entries.length ? Math.sqrt(sumSq / entries.length) : 0;
13581
+ const fixRate = entries.length ? fixed / entries.length : 0;
13533
13582
  return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
13534
13583
  }
13584
+ function estimateWidelaneFcb(arcs, opts = {}) {
13585
+ return decomposeFcb(
13586
+ arcs.map((a) => ({
13587
+ station: a.station,
13588
+ sat: a.prn,
13589
+ value: a.wlFloat,
13590
+ weight: a.nObs
13591
+ })),
13592
+ opts
13593
+ );
13594
+ }
13595
+ var C = 299792458;
13596
+ function estimateNarrowlaneFcb(arcs, wl, opts = {}) {
13597
+ const entries = [];
13598
+ let wlRejected = 0;
13599
+ for (const a of arcs) {
13600
+ const satWl = wl.satFcb.get(a.prn);
13601
+ const rcvWl = wl.rcvBias.get(a.station);
13602
+ if (satWl === void 0 || rcvWl === void 0) continue;
13603
+ const wlFloat = a.mwCyc - satWl - rcvWl;
13604
+ const nWl = Math.round(wlFloat);
13605
+ if (Math.abs(wlFloat - nWl) > (opts.fixWindow ?? 0.25)) {
13606
+ wlRejected++;
13607
+ continue;
13608
+ }
13609
+ const lamNl = C / (a.f1 + a.f2);
13610
+ const n1Float = a.aIF / lamNl - a.f2 / (a.f1 - a.f2) * nWl;
13611
+ entries.push({
13612
+ station: a.station,
13613
+ sat: a.prn,
13614
+ value: n1Float,
13615
+ weight: a.nEpochs
13616
+ });
13617
+ }
13618
+ const res = decomposeFcb(entries, { fixWindow: 0.15, ...opts });
13619
+ return { ...res, usedArcs: entries.length, wlRejected };
13620
+ }
13535
13621
 
13536
13622
  // src/positioning/index.ts
13537
13623
  var GM_GPS3 = 3986005e8;
@@ -13943,9 +14029,9 @@ var MultipathAccumulator = class {
13943
14029
  const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
13944
14030
  if (!bandMap) return;
13945
14031
  const bandData = /* @__PURE__ */ new Map();
13946
- for (const [band, { C, L }] of bandMap) {
13947
- if (C === null) continue;
13948
- const cVal = values[C];
14032
+ for (const [band, { C: C2, L }] of bandMap) {
14033
+ if (C2 === null) continue;
14034
+ const cVal = values[C2];
13949
14035
  const lVal = values[L];
13950
14036
  const freq = getFreq(this.gloChannels, prn, band);
13951
14037
  if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
@@ -14157,13 +14243,13 @@ var CycleSlipAccumulator = class {
14157
14243
  if (sys !== "R" && !FREQ[sys]) return;
14158
14244
  const phaseM = /* @__PURE__ */ new Map();
14159
14245
  const codeM = /* @__PURE__ */ new Map();
14160
- for (const [band, { L, C }] of idxMap) {
14246
+ for (const [band, { L, C: C2 }] of idxMap) {
14161
14247
  const freq = this.getFreqForPrn(prn, band);
14162
14248
  if (!freq) continue;
14163
14249
  const lVal = values[L];
14164
14250
  if (lVal != null && lVal !== 0) phaseM.set(band, lVal * (C_LIGHT / freq));
14165
- if (C !== null) {
14166
- const cVal = values[C];
14251
+ if (C2 !== null) {
14252
+ const cVal = values[C2];
14167
14253
  if (cVal != null && cVal !== 0) codeM.set(band, cVal);
14168
14254
  }
14169
14255
  }
@@ -14427,9 +14513,9 @@ var IonoAccumulator = class {
14427
14513
  const bandMap = this.obsIndices.get(sys) ?? this.obsIndices.get("_v2");
14428
14514
  if (!bandMap) return;
14429
14515
  const bandData = /* @__PURE__ */ new Map();
14430
- for (const [band, { C, L }] of bandMap) {
14431
- if (C === null) continue;
14432
- const cVal = values[C];
14516
+ for (const [band, { C: C2, L }] of bandMap) {
14517
+ if (C2 === null) continue;
14518
+ const cVal = values[C2];
14433
14519
  const lVal = values[L];
14434
14520
  const freq = getFreq(this.gloChannels, prn, band);
14435
14521
  if (cVal != null && cVal !== 0 && lVal != null && lVal !== 0 && freq) {
@@ -14437,7 +14523,7 @@ var IonoAccumulator = class {
14437
14523
  C: cVal,
14438
14524
  L: lVal,
14439
14525
  f: freq,
14440
- code: codes[C] ?? `C${band}`
14526
+ code: codes[C2] ?? `C${band}`
14441
14527
  });
14442
14528
  }
14443
14529
  }
@@ -16150,6 +16236,7 @@ function computePsdDb(centerMHz, halfSpanChips, numPoints, psdFn, f0 = 1023e3, f
16150
16236
  ecefToAzEl,
16151
16237
  ecefToGeodetic,
16152
16238
  ephInfoToEphemeris,
16239
+ estimateNarrowlaneFcb,
16153
16240
  estimateWidelaneFcb,
16154
16241
  euclidean3D,
16155
16242
  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, 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, 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, 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, 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,7 @@ import {
146
146
  RtkFloatEngine,
147
147
  buildPppAntenna,
148
148
  createPppCorrections,
149
+ estimateNarrowlaneFcb,
149
150
  estimateWidelaneFcb,
150
151
  extractWidelaneArcs,
151
152
  gimSlantIonoDelayL1,
@@ -167,7 +168,7 @@ import {
167
168
  sunEcef,
168
169
  toRtkEpoch,
169
170
  wlWavelength
170
- } from "./chunk-PVOF4IGX.js";
171
+ } from "./chunk-H73PZB5C.js";
171
172
  import {
172
173
  almanacEpochMs,
173
174
  almanacSatPosition,
@@ -474,6 +475,7 @@ export {
474
475
  ecefToAzEl,
475
476
  ecefToGeodetic,
476
477
  ephInfoToEphemeris,
478
+ estimateNarrowlaneFcb,
477
479
  estimateWidelaneFcb,
478
480
  euclidean3D,
479
481
  extractWidelaneArcs,
@@ -24,6 +24,7 @@ __export(positioning_exports, {
24
24
  RtkFloatEngine: () => RtkFloatEngine,
25
25
  buildPppAntenna: () => buildPppAntenna,
26
26
  createPppCorrections: () => createPppCorrections,
27
+ estimateNarrowlaneFcb: () => estimateNarrowlaneFcb,
27
28
  estimateWidelaneFcb: () => estimateWidelaneFcb,
28
29
  extractWidelaneArcs: () => extractWidelaneArcs,
29
30
  gimSlantIonoDelayL1: () => gimSlantIonoDelayL1,
@@ -913,15 +914,15 @@ function matMul(A, B) {
913
914
  const n = A.length;
914
915
  const k = B.length;
915
916
  const m = B[0]?.length ?? 0;
916
- const C = zeros(n, m);
917
+ const C2 = zeros(n, m);
917
918
  for (let i = 0; i < n; i++) {
918
919
  for (let l = 0; l < k; l++) {
919
920
  const a = A[i][l];
920
921
  if (a === 0) continue;
921
- for (let j = 0; j < m; j++) C[i][j] += a * B[l][j];
922
+ for (let j = 0; j < m; j++) C2[i][j] += a * B[l][j];
922
923
  }
923
924
  }
924
- return C;
925
+ return C2;
925
926
  }
926
927
  function transpose(A) {
927
928
  const n = A.length;
@@ -2173,6 +2174,24 @@ function solvePpp(epochs, sp3, opts) {
2173
2174
  const clockSeeded = /* @__PURE__ */ new Set();
2174
2175
  const ambIdx = /* @__PURE__ */ new Map();
2175
2176
  const lastSeen = /* @__PURE__ */ new Map();
2177
+ const arcs = [];
2178
+ const arcSnap = /* @__PURE__ */ new Map();
2179
+ const flushArc = (prn, minEpochs = 10) => {
2180
+ const s = arcSnap.get(prn);
2181
+ arcSnap.delete(prn);
2182
+ if (!s || s.nEpochs < minEpochs) return;
2183
+ arcs.push({
2184
+ prn,
2185
+ aIF: s.aIF,
2186
+ mwCyc: s.mwCyc,
2187
+ f1: s.f1,
2188
+ f2: s.f2,
2189
+ meanElevDeg: s.elevSum / s.nEpochs,
2190
+ nEpochs: s.nEpochs,
2191
+ startMs: s.startMs,
2192
+ endMs: s.endMs
2193
+ });
2194
+ };
2176
2195
  const mwState = /* @__PURE__ */ new Map();
2177
2196
  const detectSlip = (o, ei) => {
2178
2197
  const lamW = C_LIGHT / (o.f1 - o.f2);
@@ -2290,7 +2309,9 @@ function solvePpp(epochs, sp3, opts) {
2290
2309
  tropoFixed: zhd * mh + corr.rangeM,
2291
2310
  mw,
2292
2311
  windupM: corr.phaseWindupM,
2293
- slip
2312
+ slip,
2313
+ f1: o.f1,
2314
+ f2: o.f2
2294
2315
  });
2295
2316
  }
2296
2317
  const zwd0 = x[IDX_ZWD];
@@ -2315,6 +2336,7 @@ function solvePpp(epochs, sp3, opts) {
2315
2336
  for (const v of vis) {
2316
2337
  const ci = clockOf(v.prn[0]);
2317
2338
  const common = v.range + x[ci] - v.satClkM + v.tropoFixed + zwd0 * v.mw;
2339
+ if (v.slip) flushArc(v.prn);
2318
2340
  let ai = ambIdx.get(v.prn);
2319
2341
  if (ai === void 0 || v.slip) {
2320
2342
  const bInit = v.lIf - (common + v.windupM);
@@ -2348,6 +2370,7 @@ function solvePpp(epochs, sp3, opts) {
2348
2370
  const commonPh = v.range + x[ci] - v.satClkM + v.tropoFixed + x[IDX_ZWD] * v.mw;
2349
2371
  const innov = v.lIf - (commonPh + v.windupM + x[ai]);
2350
2372
  if (Math.abs(innov) > 0.15) {
2373
+ flushArc(v.prn);
2351
2374
  x[ai] = v.lIf - (commonPh + v.windupM);
2352
2375
  for (let j = 0; j < P.length; j++) {
2353
2376
  P[ai][j] = 0;
@@ -2365,6 +2388,29 @@ function solvePpp(epochs, sp3, opts) {
2365
2388
  ekfUpdateScalar(x, P, h, innov, rPhase, 4);
2366
2389
  }
2367
2390
  }
2391
+ {
2392
+ const elevDeg = v.elRad * 180 / Math.PI;
2393
+ const mwMean = mwState.get(v.prn)?.mean ?? 0;
2394
+ const s = arcSnap.get(v.prn);
2395
+ if (s) {
2396
+ s.aIF = x[ai];
2397
+ s.mwCyc = mwMean;
2398
+ s.elevSum += elevDeg;
2399
+ s.nEpochs++;
2400
+ s.endMs = epoch.timeMs;
2401
+ } else {
2402
+ arcSnap.set(v.prn, {
2403
+ aIF: x[ai],
2404
+ mwCyc: mwMean,
2405
+ f1: v.f1,
2406
+ f2: v.f2,
2407
+ elevSum: elevDeg,
2408
+ nEpochs: 1,
2409
+ startMs: epoch.timeMs,
2410
+ endMs: epoch.timeMs
2411
+ });
2412
+ }
2413
+ }
2368
2414
  lastSeen.set(v.prn, ei);
2369
2415
  nSats++;
2370
2416
  }
@@ -2398,6 +2444,7 @@ function solvePpp(epochs, sp3, opts) {
2398
2444
  const idx = ambIdx.get(prn);
2399
2445
  if (idx !== void 0) dropState(idx);
2400
2446
  lastSeen.delete(prn);
2447
+ flushArc(prn);
2401
2448
  }
2402
2449
  }
2403
2450
  const pos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
@@ -2437,6 +2484,7 @@ function solvePpp(epochs, sp3, opts) {
2437
2484
  phaseResRms: nPhaseRes > 0 ? Math.sqrt(sumPhaseResSq / nPhaseRes) : 0
2438
2485
  });
2439
2486
  }
2487
+ for (const prn of [...arcSnap.keys()]) flushArc(prn);
2440
2488
  const finalPos = [x[IDX_X], x[IDX_Y], x[IDX_Z]];
2441
2489
  const llh = ecefToGeodetic(finalPos[0], finalPos[1], finalPos[2]);
2442
2490
  const finalError3d = truth ? Math.hypot(
@@ -2451,7 +2499,8 @@ function solvePpp(epochs, sp3, opts) {
2451
2499
  ztdWet: x[IDX_ZWD],
2452
2500
  epochsUsed: epochs.length,
2453
2501
  convergenceSec,
2454
- finalError3d
2502
+ finalError3d,
2503
+ arcs
2455
2504
  };
2456
2505
  }
2457
2506
 
@@ -2592,34 +2641,34 @@ function circMean(vals, weights) {
2592
2641
  if (s === 0 && c === 0) return 0;
2593
2642
  return wrapHalf(Math.atan2(s, c) / (2 * Math.PI));
2594
2643
  }
2595
- function estimateWidelaneFcb(arcs, opts = {}) {
2644
+ function decomposeFcb(entries, opts = {}) {
2596
2645
  const maxIter = opts.maxIter ?? 50;
2597
2646
  const tol = opts.tol ?? 1e-4;
2598
2647
  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();
2648
+ const sats = [...new Set(entries.map((e) => e.sat))].sort();
2649
+ const stations = [...new Set(entries.map((e) => e.station))].sort();
2601
2650
  const refStation = stations[0] ?? "";
2602
2651
  const satFcb = new Map(sats.map((s) => [s, 0]));
2603
2652
  const rcvBias = new Map(stations.map((s) => [s, 0]));
2604
- const nInt = new Array(arcs.length).fill(0);
2653
+ const nInt = new Array(entries.length).fill(0);
2605
2654
  let iterations = 0;
2606
2655
  for (let it = 0; it < maxIter; it++) {
2607
2656
  iterations = it + 1;
2608
- for (let i = 0; i < arcs.length; i++) {
2609
- const a = arcs[i];
2657
+ for (let i = 0; i < entries.length; i++) {
2658
+ const e = entries[i];
2610
2659
  nInt[i] = Math.round(
2611
- a.wlFloat - rcvBias.get(a.station) - satFcb.get(a.prn)
2660
+ e.value - rcvBias.get(e.station) - satFcb.get(e.sat)
2612
2661
  );
2613
2662
  }
2614
2663
  let maxChange = 0;
2615
2664
  for (const s of sats) {
2616
2665
  const vals = [];
2617
2666
  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);
2667
+ for (let i = 0; i < entries.length; i++) {
2668
+ const e = entries[i];
2669
+ if (e.sat !== s) continue;
2670
+ vals.push(e.value - nInt[i] - rcvBias.get(e.station));
2671
+ w.push(e.weight);
2623
2672
  }
2624
2673
  if (!vals.length) continue;
2625
2674
  const nv = circMean(vals, w);
@@ -2629,11 +2678,11 @@ function estimateWidelaneFcb(arcs, opts = {}) {
2629
2678
  for (const r of stations) {
2630
2679
  const vals = [];
2631
2680
  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);
2681
+ for (let i = 0; i < entries.length; i++) {
2682
+ const e = entries[i];
2683
+ if (e.station !== r) continue;
2684
+ vals.push(e.value - nInt[i] - satFcb.get(e.sat));
2685
+ w.push(e.weight);
2637
2686
  }
2638
2687
  if (!vals.length) continue;
2639
2688
  rcvBias.set(r, circMean(vals, w));
@@ -2648,18 +2697,55 @@ function estimateWidelaneFcb(arcs, opts = {}) {
2648
2697
  }
2649
2698
  let sumSq = 0;
2650
2699
  let fixed = 0;
2651
- for (let i = 0; i < arcs.length; i++) {
2652
- const a = arcs[i];
2700
+ for (let i = 0; i < entries.length; i++) {
2701
+ const e = entries[i];
2653
2702
  const resid = wrapHalf(
2654
- a.wlFloat - nInt[i] - rcvBias.get(a.station) - satFcb.get(a.prn)
2703
+ e.value - nInt[i] - rcvBias.get(e.station) - satFcb.get(e.sat)
2655
2704
  );
2656
2705
  sumSq += resid * resid;
2657
2706
  if (Math.abs(resid) <= fixWindow) fixed++;
2658
2707
  }
2659
- const residRms = arcs.length ? Math.sqrt(sumSq / arcs.length) : 0;
2660
- const fixRate = arcs.length ? fixed / arcs.length : 0;
2708
+ const residRms = entries.length ? Math.sqrt(sumSq / entries.length) : 0;
2709
+ const fixRate = entries.length ? fixed / entries.length : 0;
2661
2710
  return { satFcb, rcvBias, residRms, fixRate, refStation, iterations };
2662
2711
  }
2712
+ function estimateWidelaneFcb(arcs, opts = {}) {
2713
+ return decomposeFcb(
2714
+ arcs.map((a) => ({
2715
+ station: a.station,
2716
+ sat: a.prn,
2717
+ value: a.wlFloat,
2718
+ weight: a.nObs
2719
+ })),
2720
+ opts
2721
+ );
2722
+ }
2723
+ var C = 299792458;
2724
+ function estimateNarrowlaneFcb(arcs, wl, opts = {}) {
2725
+ const entries = [];
2726
+ let wlRejected = 0;
2727
+ for (const a of arcs) {
2728
+ const satWl = wl.satFcb.get(a.prn);
2729
+ const rcvWl = wl.rcvBias.get(a.station);
2730
+ if (satWl === void 0 || rcvWl === void 0) continue;
2731
+ const wlFloat = a.mwCyc - satWl - rcvWl;
2732
+ const nWl = Math.round(wlFloat);
2733
+ if (Math.abs(wlFloat - nWl) > (opts.fixWindow ?? 0.25)) {
2734
+ wlRejected++;
2735
+ continue;
2736
+ }
2737
+ const lamNl = C / (a.f1 + a.f2);
2738
+ const n1Float = a.aIF / lamNl - a.f2 / (a.f1 - a.f2) * nWl;
2739
+ entries.push({
2740
+ station: a.station,
2741
+ sat: a.prn,
2742
+ value: n1Float,
2743
+ weight: a.nEpochs
2744
+ });
2745
+ }
2746
+ const res = decomposeFcb(entries, { fixWindow: 0.15, ...opts });
2747
+ return { ...res, usedArcs: entries.length, wlRejected };
2748
+ }
2663
2749
 
2664
2750
  // src/positioning/index.ts
2665
2751
  var GM_GPS2 = 3986005e8;
@@ -2894,6 +2980,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
2894
2980
  RtkFloatEngine,
2895
2981
  buildPppAntenna,
2896
2982
  createPppCorrections,
2983
+ estimateNarrowlaneFcb,
2897
2984
  estimateWidelaneFcb,
2898
2985
  extractWidelaneArcs,
2899
2986
  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,39 @@ 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;
882
935
 
883
936
  /**
884
937
  * Single-point positioning (SPP) from pseudoranges and broadcast
@@ -969,4 +1022,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
969
1022
  */
970
1023
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
971
1024
 
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 };
1025
+ 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 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, 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,39 @@ 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;
882
935
 
883
936
  /**
884
937
  * Single-point positioning (SPP) from pseudoranges and broadcast
@@ -969,4 +1022,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
969
1022
  */
970
1023
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
971
1024
 
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 };
1025
+ 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 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, estimateWidelaneFcb, extractWidelaneArcs, gimSlantIonoDelayL1, gimVerticalTec, gmst, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, moonEcef, niellMapping, nlWavelength, resolvePppAmbiguities, satClockCorrection, solidEarthTide, solveDgnss, solvePpp, solveSpp, sunEcef, toRtkEpoch, wlWavelength };
@@ -3,6 +3,7 @@ import {
3
3
  RtkFloatEngine,
4
4
  buildPppAntenna,
5
5
  createPppCorrections,
6
+ estimateNarrowlaneFcb,
6
7
  estimateWidelaneFcb,
7
8
  extractWidelaneArcs,
8
9
  gimSlantIonoDelayL1,
@@ -24,7 +25,7 @@ import {
24
25
  sunEcef,
25
26
  toRtkEpoch,
26
27
  wlWavelength
27
- } from "./chunk-PVOF4IGX.js";
28
+ } from "./chunk-H73PZB5C.js";
28
29
  import "./chunk-MHGNKSSH.js";
29
30
  import "./chunk-37QNKGTC.js";
30
31
  import "./chunk-6FAL6P4G.js";
@@ -37,6 +38,7 @@ export {
37
38
  RtkFloatEngine,
38
39
  buildPppAntenna,
39
40
  createPppCorrections,
41
+ estimateNarrowlaneFcb,
40
42
  estimateWidelaneFcb,
41
43
  extractWidelaneArcs,
42
44
  gimSlantIonoDelayL1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.33.0",
3
+ "version": "1.34.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,