gnss-js 2.13.0 → 2.14.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.
- package/dist/analysis.cjs +525 -0
- package/dist/analysis.d.cts +135 -1
- package/dist/analysis.d.ts +135 -1
- package/dist/analysis.js +9 -1
- package/dist/{chunk-MFCPYUEI.js → chunk-GGWK7XLZ.js} +496 -1
- package/dist/{chunk-RWFJEVLJ.js → chunk-TETPRPZ2.js} +149 -6
- package/dist/index.cjs +647 -6
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -2
- package/dist/positioning.cjs +152 -6
- package/dist/positioning.d.cts +47 -1
- package/dist/positioning.d.ts +47 -1
- package/dist/positioning.js +7 -1
- package/dist/troposphere.cjs +5056 -0
- package/dist/troposphere.d.cts +78 -0
- package/dist/troposphere.d.ts +78 -0
- package/dist/troposphere.js +5025 -0
- package/package.json +6 -1
package/dist/analysis.cjs
CHANGED
|
@@ -25,9 +25,12 @@ __export(analysis_exports, {
|
|
|
25
25
|
IonoAccumulator: () => IonoAccumulator,
|
|
26
26
|
MultipathAccumulator: () => MultipathAccumulator,
|
|
27
27
|
analyzeQuality: () => analyzeQuality,
|
|
28
|
+
applyHelmertFit: () => applyHelmertFit,
|
|
28
29
|
applyIonoDcb: () => applyIonoDcb,
|
|
29
30
|
computeIonoRate: () => computeIonoRate,
|
|
30
31
|
detrendIonoArcs: () => detrendIonoArcs,
|
|
32
|
+
estimateHelmert: () => estimateHelmert,
|
|
33
|
+
fitCoordinateSeries: () => fitCoordinateSeries,
|
|
31
34
|
parseSinexBiasDcb: () => parseSinexBiasDcb
|
|
32
35
|
});
|
|
33
36
|
module.exports = __toCommonJS(analysis_exports);
|
|
@@ -1924,6 +1927,525 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
1924
1927
|
iono: ionoAccum.finalize()
|
|
1925
1928
|
};
|
|
1926
1929
|
}
|
|
1930
|
+
|
|
1931
|
+
// src/constants/wgs84.ts
|
|
1932
|
+
var WGS84_SEMI_MAJOR_AXIS = 6378137;
|
|
1933
|
+
var WGS84_FLATTENING = 1 / 298.257223563;
|
|
1934
|
+
var WGS84_ECCENTRICITY_SQUARED = 0.006694379990197;
|
|
1935
|
+
|
|
1936
|
+
// src/coordinates/ecef.ts
|
|
1937
|
+
function ecefToGeodetic(x, y, z) {
|
|
1938
|
+
const lon = Math.atan2(y, x);
|
|
1939
|
+
const p = Math.sqrt(x * x + y * y);
|
|
1940
|
+
if (p < 1e-10) {
|
|
1941
|
+
const b = WGS84_SEMI_MAJOR_AXIS * Math.sqrt(1 - WGS84_ECCENTRICITY_SQUARED);
|
|
1942
|
+
const lat2 = z >= 0 ? Math.PI / 2 : -Math.PI / 2;
|
|
1943
|
+
const alt2 = Math.abs(z) - b;
|
|
1944
|
+
return [lat2, lon, alt2];
|
|
1945
|
+
}
|
|
1946
|
+
let lat = Math.atan2(z, p * (1 - WGS84_ECCENTRICITY_SQUARED));
|
|
1947
|
+
let N;
|
|
1948
|
+
for (let i = 0; i < 10; i++) {
|
|
1949
|
+
const sinLat2 = Math.sin(lat);
|
|
1950
|
+
N = WGS84_SEMI_MAJOR_AXIS / Math.sqrt(1 - WGS84_ECCENTRICITY_SQUARED * sinLat2 * sinLat2);
|
|
1951
|
+
const prevLat = lat;
|
|
1952
|
+
lat = Math.atan2(z + WGS84_ECCENTRICITY_SQUARED * N * sinLat2, p);
|
|
1953
|
+
if (Math.abs(lat - prevLat) < 1e-15) break;
|
|
1954
|
+
}
|
|
1955
|
+
const sinLat = Math.sin(lat);
|
|
1956
|
+
N = WGS84_SEMI_MAJOR_AXIS / Math.sqrt(1 - WGS84_ECCENTRICITY_SQUARED * sinLat * sinLat);
|
|
1957
|
+
const alt = p / Math.cos(lat) - N;
|
|
1958
|
+
return [lat, lon, alt];
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/analysis/noise-model.ts
|
|
1962
|
+
function cholesky(M, n) {
|
|
1963
|
+
for (let j = 0; j < n; j++) {
|
|
1964
|
+
let d = M[j * n + j];
|
|
1965
|
+
for (let k = 0; k < j; k++) d -= M[j * n + k] ** 2;
|
|
1966
|
+
if (d <= 0) return false;
|
|
1967
|
+
d = Math.sqrt(d);
|
|
1968
|
+
M[j * n + j] = d;
|
|
1969
|
+
for (let i = j + 1; i < n; i++) {
|
|
1970
|
+
let s = M[i * n + j];
|
|
1971
|
+
for (let k = 0; k < j; k++) s -= M[i * n + k] * M[j * n + k];
|
|
1972
|
+
M[i * n + j] = s / d;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
return true;
|
|
1976
|
+
}
|
|
1977
|
+
function forward(L, n, b) {
|
|
1978
|
+
for (let i = 0; i < n; i++) {
|
|
1979
|
+
let s = b[i];
|
|
1980
|
+
for (let k = 0; k < i; k++) s -= L[i * n + k] * b[k];
|
|
1981
|
+
b[i] = s / L[i * n + i];
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
function flickerCovariance(idx) {
|
|
1985
|
+
const N = idx[idx.length - 1] + 1;
|
|
1986
|
+
const h = new Float64Array(N);
|
|
1987
|
+
h[0] = 1;
|
|
1988
|
+
for (let i = 1; i < N; i++) h[i] = h[i - 1] * (i - 1 + 0.5) / i;
|
|
1989
|
+
const n = idx.length;
|
|
1990
|
+
const F = new Float64Array(n * n);
|
|
1991
|
+
for (let a = 0; a < n; a++)
|
|
1992
|
+
for (let b = 0; b <= a; b++) {
|
|
1993
|
+
const ia = idx[a];
|
|
1994
|
+
const ib = idx[b];
|
|
1995
|
+
let s = 0;
|
|
1996
|
+
for (let k = 0; k <= ib; k++) s += h[ia - k] * h[ib - k];
|
|
1997
|
+
F[a * n + b] = s;
|
|
1998
|
+
F[b * n + a] = s;
|
|
1999
|
+
}
|
|
2000
|
+
let tr = 0;
|
|
2001
|
+
for (let a = 0; a < n; a++) tr += F[a * n + a];
|
|
2002
|
+
const scale = n / tr;
|
|
2003
|
+
for (let i = 0; i < F.length; i++) F[i] *= scale;
|
|
2004
|
+
return F;
|
|
2005
|
+
}
|
|
2006
|
+
var MAX_SAMPLES = 360;
|
|
2007
|
+
function fitWhiteFlicker(A, y, tYears) {
|
|
2008
|
+
let rows = A.map((_, i) => i);
|
|
2009
|
+
if (rows.length > MAX_SAMPLES) {
|
|
2010
|
+
const step = rows.length / MAX_SAMPLES;
|
|
2011
|
+
rows = Array.from({ length: MAX_SAMPLES }, (_, k) => Math.floor(k * step));
|
|
2012
|
+
}
|
|
2013
|
+
const n = rows.length;
|
|
2014
|
+
const p = A[0]?.length ?? 0;
|
|
2015
|
+
if (n < p + 5) return null;
|
|
2016
|
+
const dts = rows.slice(1).map((r, k) => tYears[r] - tYears[rows[k]]).filter((d) => d > 0).sort((a, b) => a - b);
|
|
2017
|
+
const dt = dts[dts.length >> 1] ?? 1 / 365.25;
|
|
2018
|
+
const t0 = tYears[rows[0]];
|
|
2019
|
+
const idx = rows.map((r) => Math.round((tYears[r] - t0) / dt));
|
|
2020
|
+
for (let k = 1; k < n; k++)
|
|
2021
|
+
if (idx[k] <= idx[k - 1]) idx[k] = idx[k - 1] + 1;
|
|
2022
|
+
const F = flickerCovariance(idx);
|
|
2023
|
+
const evaluate = (phi) => {
|
|
2024
|
+
const C = new Float64Array(n * n);
|
|
2025
|
+
for (let i = 0; i < n * n; i++) C[i] = phi * F[i];
|
|
2026
|
+
for (let i = 0; i < n; i++) C[i * n + i] += 1 - phi;
|
|
2027
|
+
if (!cholesky(C, n)) return null;
|
|
2028
|
+
const Aw = Array.from({ length: p }, (_, j) => {
|
|
2029
|
+
const col = Float64Array.from(rows, (r) => A[r][j]);
|
|
2030
|
+
forward(C, n, col);
|
|
2031
|
+
return col;
|
|
2032
|
+
});
|
|
2033
|
+
const yw = Float64Array.from(rows, (r) => y[r]);
|
|
2034
|
+
forward(C, n, yw);
|
|
2035
|
+
const N = Array.from(
|
|
2036
|
+
{ length: p },
|
|
2037
|
+
(_, a) => Array.from({ length: p }, (_2, b) => {
|
|
2038
|
+
let s = 0;
|
|
2039
|
+
for (let i = 0; i < n; i++) s += Aw[a][i] * Aw[b][i];
|
|
2040
|
+
return s;
|
|
2041
|
+
})
|
|
2042
|
+
);
|
|
2043
|
+
const rhs = Array.from({ length: p }, (_, a) => {
|
|
2044
|
+
let s = 0;
|
|
2045
|
+
for (let i = 0; i < n; i++) s += Aw[a][i] * yw[i];
|
|
2046
|
+
return s;
|
|
2047
|
+
});
|
|
2048
|
+
const Ninv = invert(N);
|
|
2049
|
+
if (!Ninv) return null;
|
|
2050
|
+
const beta = Ninv.map((row) => row.reduce((s, v, k) => s + v * rhs[k], 0));
|
|
2051
|
+
let rss = 0;
|
|
2052
|
+
for (let i = 0; i < n; i++) {
|
|
2053
|
+
let m = 0;
|
|
2054
|
+
for (let j = 0; j < p; j++) m += Aw[j][i] * beta[j];
|
|
2055
|
+
rss += (yw[i] - m) ** 2;
|
|
2056
|
+
}
|
|
2057
|
+
const sigma2 = rss / n;
|
|
2058
|
+
let logdet = 0;
|
|
2059
|
+
for (let i = 0; i < n; i++) logdet += 2 * Math.log(C[i * n + i]);
|
|
2060
|
+
const logL = -0.5 * (n * Math.log(sigma2) + logdet + n);
|
|
2061
|
+
return {
|
|
2062
|
+
logL,
|
|
2063
|
+
velocity: beta[1],
|
|
2064
|
+
velocitySigma: Math.sqrt(sigma2 * Ninv[1][1])
|
|
2065
|
+
};
|
|
2066
|
+
};
|
|
2067
|
+
let best = null;
|
|
2068
|
+
for (let k = 0; k <= 20; k++) {
|
|
2069
|
+
const phi = Math.min(k / 20, 0.995);
|
|
2070
|
+
const r = evaluate(phi);
|
|
2071
|
+
if (r && (!best || r.logL > best.r.logL)) best = { phi, r };
|
|
2072
|
+
}
|
|
2073
|
+
if (!best) return null;
|
|
2074
|
+
let lo = Math.max(0, best.phi - 0.05);
|
|
2075
|
+
let hi = Math.min(0.995, best.phi + 0.05);
|
|
2076
|
+
const g = (Math.sqrt(5) - 1) / 2;
|
|
2077
|
+
for (let it = 0; it < 12; it++) {
|
|
2078
|
+
const a = hi - g * (hi - lo);
|
|
2079
|
+
const b = lo + g * (hi - lo);
|
|
2080
|
+
const ra = evaluate(a);
|
|
2081
|
+
const rb = evaluate(b);
|
|
2082
|
+
if (!ra || !rb) break;
|
|
2083
|
+
if (ra.logL > rb.logL) {
|
|
2084
|
+
hi = b;
|
|
2085
|
+
if (ra.logL > best.r.logL) best = { phi: a, r: ra };
|
|
2086
|
+
} else {
|
|
2087
|
+
lo = a;
|
|
2088
|
+
if (rb.logL > best.r.logL) best = { phi: b, r: rb };
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
return {
|
|
2092
|
+
flickerShare: best.phi,
|
|
2093
|
+
velocitySigma: best.r.velocitySigma,
|
|
2094
|
+
velocity: best.r.velocity
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function invert(N) {
|
|
2098
|
+
const p = N.length;
|
|
2099
|
+
const M = N.map((row, i) => [
|
|
2100
|
+
...row,
|
|
2101
|
+
...Array.from({ length: p }, (_, j) => i === j ? 1 : 0)
|
|
2102
|
+
]);
|
|
2103
|
+
for (let c = 0; c < p; c++) {
|
|
2104
|
+
let piv = c;
|
|
2105
|
+
for (let r = c + 1; r < p; r++)
|
|
2106
|
+
if (Math.abs(M[r][c]) > Math.abs(M[piv][c])) piv = r;
|
|
2107
|
+
if (Math.abs(M[piv][c]) < 1e-14) return null;
|
|
2108
|
+
[M[c], M[piv]] = [M[piv], M[c]];
|
|
2109
|
+
const d = M[c][c];
|
|
2110
|
+
for (let k = 0; k < 2 * p; k++) M[c][k] /= d;
|
|
2111
|
+
for (let r = 0; r < p; r++) {
|
|
2112
|
+
if (r === c) continue;
|
|
2113
|
+
const f = M[r][c];
|
|
2114
|
+
if (f === 0) continue;
|
|
2115
|
+
for (let k = 0; k < 2 * p; k++) M[r][k] -= f * M[c][k];
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return M.map((row) => row.slice(p));
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// src/analysis/coordinate-series.ts
|
|
2122
|
+
var MS_PER_YEAR = 365.25 * 864e5;
|
|
2123
|
+
var TWO_PI = 2 * Math.PI;
|
|
2124
|
+
var MIN_SIGMA_M = 5e-4;
|
|
2125
|
+
function lsq(A, y, w) {
|
|
2126
|
+
const p = A[0]?.length ?? 0;
|
|
2127
|
+
const N = Array.from({ length: p }, () => new Array(p).fill(0));
|
|
2128
|
+
const b = new Array(p).fill(0);
|
|
2129
|
+
for (let i = 0; i < A.length; i++) {
|
|
2130
|
+
const a = A[i];
|
|
2131
|
+
for (let j = 0; j < p; j++) {
|
|
2132
|
+
b[j] += w[i] * a[j] * y[i];
|
|
2133
|
+
for (let k = 0; k < p; k++) N[j][k] += w[i] * a[j] * a[k];
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
const M = N.map((row, i) => [
|
|
2137
|
+
...row,
|
|
2138
|
+
...Array.from({ length: p }, (_, j) => i === j ? 1 : 0)
|
|
2139
|
+
]);
|
|
2140
|
+
for (let c = 0; c < p; c++) {
|
|
2141
|
+
let piv = c;
|
|
2142
|
+
for (let r = c + 1; r < p; r++)
|
|
2143
|
+
if (Math.abs(M[r][c]) > Math.abs(M[piv][c])) piv = r;
|
|
2144
|
+
if (Math.abs(M[piv][c]) < 1e-12) return null;
|
|
2145
|
+
[M[c], M[piv]] = [M[piv], M[c]];
|
|
2146
|
+
const d = M[c][c];
|
|
2147
|
+
for (let k = 0; k < 2 * p; k++) M[c][k] /= d;
|
|
2148
|
+
for (let r = 0; r < p; r++) {
|
|
2149
|
+
if (r === c) continue;
|
|
2150
|
+
const f = M[r][c];
|
|
2151
|
+
if (f === 0) continue;
|
|
2152
|
+
for (let k = 0; k < 2 * p; k++) M[r][k] -= f * M[c][k];
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
const Ninv = M.map((row) => row.slice(p));
|
|
2156
|
+
const x = Ninv.map((row) => row.reduce((s, v, k) => s + v * b[k], 0));
|
|
2157
|
+
return { x, Ninv };
|
|
2158
|
+
}
|
|
2159
|
+
function median2(xs) {
|
|
2160
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
2161
|
+
const m = s.length >> 1;
|
|
2162
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
2163
|
+
}
|
|
2164
|
+
function robustSigma(xs) {
|
|
2165
|
+
if (xs.length < 2) return 0;
|
|
2166
|
+
const m = median2(xs);
|
|
2167
|
+
return 1.4826 * median2(xs.map((x) => Math.abs(x - m)));
|
|
2168
|
+
}
|
|
2169
|
+
function designRow(tMs, d) {
|
|
2170
|
+
const dt = (tMs - d.refEpochMs) / MS_PER_YEAR;
|
|
2171
|
+
const row = [1, dt];
|
|
2172
|
+
for (const j of d.jumps) row.push(tMs >= j ? 1 : 0);
|
|
2173
|
+
if (d.seasonal) {
|
|
2174
|
+
const ph = TWO_PI * (tMs / MS_PER_YEAR);
|
|
2175
|
+
row.push(Math.sin(ph), Math.cos(ph), Math.sin(2 * ph), Math.cos(2 * ph));
|
|
2176
|
+
}
|
|
2177
|
+
return row;
|
|
2178
|
+
}
|
|
2179
|
+
function fitComponent(t, y, w, used, d) {
|
|
2180
|
+
const A = [];
|
|
2181
|
+
const yy = [];
|
|
2182
|
+
const ww = [];
|
|
2183
|
+
for (let i = 0; i < t.length; i++) {
|
|
2184
|
+
if (!used[i]) continue;
|
|
2185
|
+
A.push(designRow(t[i], d));
|
|
2186
|
+
yy.push(y[i]);
|
|
2187
|
+
ww.push(w[i]);
|
|
2188
|
+
}
|
|
2189
|
+
if (A.length <= (A[0]?.length ?? 0)) return null;
|
|
2190
|
+
const s = lsq(A, yy, ww);
|
|
2191
|
+
if (!s) return null;
|
|
2192
|
+
const residuals = t.map(
|
|
2193
|
+
(ti, i) => y[i] - designRow(ti, d).reduce((acc, a, k) => acc + a * s.x[k], 0)
|
|
2194
|
+
);
|
|
2195
|
+
let wrss = 0;
|
|
2196
|
+
let sumW = 0;
|
|
2197
|
+
for (let i = 0; i < t.length; i++) {
|
|
2198
|
+
if (!used[i]) continue;
|
|
2199
|
+
wrss += w[i] * residuals[i] ** 2;
|
|
2200
|
+
sumW += w[i];
|
|
2201
|
+
}
|
|
2202
|
+
return { ...s, wrss, sumW, residuals };
|
|
2203
|
+
}
|
|
2204
|
+
function fitCoordinateSeries(samples, opts = {}) {
|
|
2205
|
+
const pts = [...samples].sort((a, b) => a.tMs - b.tMs);
|
|
2206
|
+
if (pts.length < 5) return null;
|
|
2207
|
+
const t = pts.map((p) => p.tMs);
|
|
2208
|
+
const spanYears = (t[t.length - 1] - t[0]) / MS_PER_YEAR;
|
|
2209
|
+
if (spanYears <= 0) return null;
|
|
2210
|
+
const ref = [0, 1, 2].map(
|
|
2211
|
+
(k2) => median2(pts.map((p) => p.ecef[k2]))
|
|
2212
|
+
);
|
|
2213
|
+
const [lat, lon] = ecefToGeodetic(ref[0], ref[1], ref[2]);
|
|
2214
|
+
const sl = Math.sin(lat);
|
|
2215
|
+
const cl = Math.cos(lat);
|
|
2216
|
+
const so = Math.sin(lon);
|
|
2217
|
+
const co = Math.cos(lon);
|
|
2218
|
+
const enu = pts.map((p) => {
|
|
2219
|
+
const dx = p.ecef[0] - ref[0];
|
|
2220
|
+
const dy = p.ecef[1] - ref[1];
|
|
2221
|
+
const dz = p.ecef[2] - ref[2];
|
|
2222
|
+
return [
|
|
2223
|
+
-so * dx + co * dy,
|
|
2224
|
+
-sl * co * dx - sl * so * dy + cl * dz,
|
|
2225
|
+
cl * co * dx + cl * so * dy + sl * dz
|
|
2226
|
+
];
|
|
2227
|
+
});
|
|
2228
|
+
const weights = [0, 1, 2].map(
|
|
2229
|
+
(k2) => pts.map((p) => {
|
|
2230
|
+
const s = p.sigmaEnu?.[k2];
|
|
2231
|
+
return s && s > 0 ? 1 / Math.max(s, MIN_SIGMA_M) ** 2 : 1;
|
|
2232
|
+
})
|
|
2233
|
+
);
|
|
2234
|
+
const series = [0, 1, 2].map((k2) => enu.map((v) => v[k2]));
|
|
2235
|
+
const seasonal = opts.seasonal === "auto" || opts.seasonal === void 0 ? spanYears >= 2 : opts.seasonal;
|
|
2236
|
+
const design = {
|
|
2237
|
+
refEpochMs: opts.refEpochMs ?? (t[0] + t[t.length - 1]) / 2,
|
|
2238
|
+
jumps: [...opts.jumps ?? []].filter((j) => j > t[0] && j <= t[t.length - 1]).sort((a, b) => a - b),
|
|
2239
|
+
seasonal
|
|
2240
|
+
};
|
|
2241
|
+
const detected = /* @__PURE__ */ new Set();
|
|
2242
|
+
const k = opts.outlierK ?? 4;
|
|
2243
|
+
const used = pts.map(() => true);
|
|
2244
|
+
const fitAll = () => [0, 1, 2].map(
|
|
2245
|
+
(c) => fitComponent(t, series[c], weights[c], used, design)
|
|
2246
|
+
);
|
|
2247
|
+
const robustPass = () => {
|
|
2248
|
+
for (let iter = 0; iter < 10; iter++) {
|
|
2249
|
+
const sol2 = fitAll();
|
|
2250
|
+
if (sol2.some((s) => !s)) return null;
|
|
2251
|
+
const scales = sol2.map(
|
|
2252
|
+
(s) => Math.max(robustSigma(s.residuals.filter((_, i) => used[i])), 1e-4)
|
|
2253
|
+
);
|
|
2254
|
+
let changed = false;
|
|
2255
|
+
for (let i = 0; i < t.length; i++) {
|
|
2256
|
+
const bad = sol2.some(
|
|
2257
|
+
(s, c) => Math.abs(s.residuals[i]) > k * scales[c]
|
|
2258
|
+
);
|
|
2259
|
+
if (bad === used[i]) {
|
|
2260
|
+
used[i] = !bad;
|
|
2261
|
+
changed = true;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
if (!changed) return sol2;
|
|
2265
|
+
}
|
|
2266
|
+
return fitAll();
|
|
2267
|
+
};
|
|
2268
|
+
let sol = robustPass();
|
|
2269
|
+
if (!sol) return null;
|
|
2270
|
+
if (opts.detectJumps ?? true) {
|
|
2271
|
+
const minSide = opts.minSamplesPerSide ?? 5;
|
|
2272
|
+
for (let added = 0; added < (opts.maxJumps ?? 3); added++) {
|
|
2273
|
+
const idx = t.map((_, i) => i).filter((i) => used[i]);
|
|
2274
|
+
const nUsed = idx.length;
|
|
2275
|
+
const bicOf = (s, p) => s.reduce((acc, c) => acc + nUsed * Math.log(c.wrss / c.sumW), 0) + 3 * p * Math.log(nUsed);
|
|
2276
|
+
const p0 = designRow(t[0], design).length;
|
|
2277
|
+
const bic0 = bicOf(sol, p0);
|
|
2278
|
+
let best = null;
|
|
2279
|
+
for (let j = minSide; j <= nUsed - minSide; j++) {
|
|
2280
|
+
const tj = (t[idx[j - 1]] + t[idx[j]]) / 2;
|
|
2281
|
+
if (design.jumps.some((x) => Math.abs(x - tj) < 7 * 864e5))
|
|
2282
|
+
continue;
|
|
2283
|
+
const trial = {
|
|
2284
|
+
...design,
|
|
2285
|
+
jumps: [...design.jumps, tj].sort((a, b) => a - b)
|
|
2286
|
+
};
|
|
2287
|
+
const s = [0, 1, 2].map(
|
|
2288
|
+
(c) => fitComponent(t, series[c], weights[c], used, trial)
|
|
2289
|
+
);
|
|
2290
|
+
if (s.some((x) => !x)) continue;
|
|
2291
|
+
const bic = bicOf(s, p0 + 1);
|
|
2292
|
+
if (!best || bic < best.bic)
|
|
2293
|
+
best = { tMs: tj, bic, sol: s };
|
|
2294
|
+
}
|
|
2295
|
+
if (!best || bic0 - best.bic <= 10) break;
|
|
2296
|
+
design.jumps = [...design.jumps, best.tMs].sort((a, b) => a - b);
|
|
2297
|
+
detected.add(best.tMs);
|
|
2298
|
+
sol = robustPass();
|
|
2299
|
+
if (!sol) return null;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
const nJ = design.jumps.length;
|
|
2303
|
+
const component = (c) => {
|
|
2304
|
+
const s = sol[c];
|
|
2305
|
+
const nUsed = used.filter(Boolean).length;
|
|
2306
|
+
const p = s.x.length;
|
|
2307
|
+
const vf = nUsed > p ? s.wrss / (nUsed - p) : 1;
|
|
2308
|
+
const amp = (i) => seasonal ? Math.hypot(s.x[i], s.x[i + 1]) : null;
|
|
2309
|
+
const noise = opts.noiseModel ?? true ? fitWhiteFlicker(
|
|
2310
|
+
t.flatMap((ti, i) => used[i] ? [designRow(ti, design)] : []),
|
|
2311
|
+
series[c].filter((_, i) => used[i]),
|
|
2312
|
+
t.flatMap((ti, i) => used[i] ? [ti / MS_PER_YEAR] : [])
|
|
2313
|
+
) : null;
|
|
2314
|
+
return {
|
|
2315
|
+
offset: s.x[0],
|
|
2316
|
+
velocity: s.x[1],
|
|
2317
|
+
velocitySigma: Math.sqrt(s.Ninv[1][1] * vf),
|
|
2318
|
+
velocitySigmaRealistic: noise?.velocitySigma ?? null,
|
|
2319
|
+
flickerShare: noise?.flickerShare ?? null,
|
|
2320
|
+
steps: s.x.slice(2, 2 + nJ),
|
|
2321
|
+
annualAmplitude: amp(2 + nJ),
|
|
2322
|
+
semiannualAmplitude: amp(4 + nJ),
|
|
2323
|
+
wrms: Math.sqrt(s.wrss / s.sumW)
|
|
2324
|
+
};
|
|
2325
|
+
};
|
|
2326
|
+
const points = pts.map((p, i) => ({
|
|
2327
|
+
tMs: p.tMs,
|
|
2328
|
+
enu: enu[i],
|
|
2329
|
+
model: [0, 1, 2].map((c) => series[c][i] - sol[c].residuals[i]),
|
|
2330
|
+
outlier: !used[i]
|
|
2331
|
+
}));
|
|
2332
|
+
return {
|
|
2333
|
+
refEcef: ref,
|
|
2334
|
+
refEpochMs: design.refEpochMs,
|
|
2335
|
+
e: component(0),
|
|
2336
|
+
n: component(1),
|
|
2337
|
+
u: component(2),
|
|
2338
|
+
jumps: design.jumps.map((tMs) => ({ tMs, detected: detected.has(tMs) })),
|
|
2339
|
+
points,
|
|
2340
|
+
spanYears,
|
|
2341
|
+
used: used.filter(Boolean).length,
|
|
2342
|
+
outliers: used.filter((u) => !u).length,
|
|
2343
|
+
seasonal
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/analysis/network-alignment.ts
|
|
2348
|
+
function applyHelmertFit(x, f) {
|
|
2349
|
+
const [r1, r2, r3] = f.r;
|
|
2350
|
+
return [
|
|
2351
|
+
x[0] + f.t[0] + f.d * x[0] - r3 * x[1] + r2 * x[2],
|
|
2352
|
+
x[1] + f.t[1] + r3 * x[0] + f.d * x[1] - r1 * x[2],
|
|
2353
|
+
x[2] + f.t[2] - r2 * x[0] + r1 * x[1] + f.d * x[2]
|
|
2354
|
+
];
|
|
2355
|
+
}
|
|
2356
|
+
function solve(N, b) {
|
|
2357
|
+
const n = b.length;
|
|
2358
|
+
const M = N.map((row, i) => [...row, b[i]]);
|
|
2359
|
+
for (let c = 0; c < n; c++) {
|
|
2360
|
+
let p = c;
|
|
2361
|
+
for (let r = c + 1; r < n; r++)
|
|
2362
|
+
if (Math.abs(M[r][c]) > Math.abs(M[p][c])) p = r;
|
|
2363
|
+
if (Math.abs(M[p][c]) < 1e-6) return null;
|
|
2364
|
+
[M[c], M[p]] = [M[p], M[c]];
|
|
2365
|
+
for (let r = 0; r < n; r++) {
|
|
2366
|
+
if (r === c) continue;
|
|
2367
|
+
const f = M[r][c] / M[c][c];
|
|
2368
|
+
for (let k = c; k <= n; k++) M[r][k] -= f * M[c][k];
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
return M.map((row, i) => row[n] / row[i]);
|
|
2372
|
+
}
|
|
2373
|
+
function estimateHelmert(from, to, opts = {}) {
|
|
2374
|
+
const model = opts.model ?? "translation";
|
|
2375
|
+
const nPar = model === "translation" ? 3 : 7;
|
|
2376
|
+
const minRefs = opts.minRefs ?? (model === "translation" ? 3 : 4);
|
|
2377
|
+
const maxRes = opts.maxResidualM ?? 0.015;
|
|
2378
|
+
if (from.length !== to.length) return null;
|
|
2379
|
+
const used = from.map(() => true);
|
|
2380
|
+
const c = [0, 1, 2].map(
|
|
2381
|
+
(k) => from.reduce((s, p) => s + p[k], 0) / Math.max(from.length, 1)
|
|
2382
|
+
);
|
|
2383
|
+
for (; ; ) {
|
|
2384
|
+
const idx = used.flatMap((u, i) => u ? [i] : []);
|
|
2385
|
+
if (idx.length < minRefs || idx.length * 3 <= nPar) return null;
|
|
2386
|
+
const N = Array.from(
|
|
2387
|
+
{ length: nPar },
|
|
2388
|
+
() => new Array(nPar).fill(0)
|
|
2389
|
+
);
|
|
2390
|
+
const b = new Array(nPar).fill(0);
|
|
2391
|
+
for (const i of idx) {
|
|
2392
|
+
const x = [from[i][0] - c[0], from[i][1] - c[1], from[i][2] - c[2]];
|
|
2393
|
+
for (let k = 0; k < 3; k++) {
|
|
2394
|
+
const row = new Array(nPar).fill(0);
|
|
2395
|
+
row[k] = 1;
|
|
2396
|
+
if (nPar === 7) {
|
|
2397
|
+
if (k === 0) {
|
|
2398
|
+
row[4] = x[2];
|
|
2399
|
+
row[5] = -x[1];
|
|
2400
|
+
} else if (k === 1) {
|
|
2401
|
+
row[3] = -x[2];
|
|
2402
|
+
row[5] = x[0];
|
|
2403
|
+
} else {
|
|
2404
|
+
row[3] = x[1];
|
|
2405
|
+
row[4] = -x[0];
|
|
2406
|
+
}
|
|
2407
|
+
row[6] = x[k];
|
|
2408
|
+
}
|
|
2409
|
+
const y = to[i][k] - from[i][k];
|
|
2410
|
+
for (let a = 0; a < nPar; a++) {
|
|
2411
|
+
b[a] += row[a] * y;
|
|
2412
|
+
for (let bb = 0; bb < nPar; bb++) N[a][bb] += row[a] * row[bb];
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
const p = solve(N, b);
|
|
2417
|
+
if (!p) return null;
|
|
2418
|
+
const r = nPar === 7 ? [p[3], p[4], p[5]] : [0, 0, 0];
|
|
2419
|
+
const d = nPar === 7 ? p[6] : 0;
|
|
2420
|
+
const t = [
|
|
2421
|
+
p[0] - (d * c[0] - r[2] * c[1] + r[1] * c[2]),
|
|
2422
|
+
p[1] - (r[2] * c[0] + d * c[1] - r[0] * c[2]),
|
|
2423
|
+
p[2] - (-r[1] * c[0] + r[0] * c[1] + d * c[2])
|
|
2424
|
+
];
|
|
2425
|
+
const fit = { model, t, r, d, residuals: [], used, rms: 0 };
|
|
2426
|
+
fit.residuals = from.map((x, i) => {
|
|
2427
|
+
const y = applyHelmertFit(x, fit);
|
|
2428
|
+
return [to[i][0] - y[0], to[i][1] - y[1], to[i][2] - y[2]];
|
|
2429
|
+
});
|
|
2430
|
+
let worst = -1;
|
|
2431
|
+
let worstNorm = maxRes;
|
|
2432
|
+
let ss = 0;
|
|
2433
|
+
for (const i of idx) {
|
|
2434
|
+
const n = Math.hypot(...fit.residuals[i]);
|
|
2435
|
+
ss += n * n;
|
|
2436
|
+
if (n > worstNorm) {
|
|
2437
|
+
worstNorm = n;
|
|
2438
|
+
worst = i;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
fit.rms = Math.sqrt(ss / (3 * idx.length));
|
|
2442
|
+
if (worst < 0 || idx.length <= minRefs) {
|
|
2443
|
+
fit.used = [...used];
|
|
2444
|
+
return fit;
|
|
2445
|
+
}
|
|
2446
|
+
used[worst] = false;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
1927
2449
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1928
2450
|
0 && (module.exports = {
|
|
1929
2451
|
CompletenessAccumulator,
|
|
@@ -1931,8 +2453,11 @@ async function analyzeQuality(file, header, onProgress, signal) {
|
|
|
1931
2453
|
IonoAccumulator,
|
|
1932
2454
|
MultipathAccumulator,
|
|
1933
2455
|
analyzeQuality,
|
|
2456
|
+
applyHelmertFit,
|
|
1934
2457
|
applyIonoDcb,
|
|
1935
2458
|
computeIonoRate,
|
|
1936
2459
|
detrendIonoArcs,
|
|
2460
|
+
estimateHelmert,
|
|
2461
|
+
fitCoordinateSeries,
|
|
1937
2462
|
parseSinexBiasDcb
|
|
1938
2463
|
});
|
package/dist/analysis.d.cts
CHANGED
|
@@ -363,4 +363,138 @@ interface QualityResult {
|
|
|
363
363
|
}
|
|
364
364
|
declare function analyzeQuality(file: File, header: RinexHeader, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<QualityResult>;
|
|
365
365
|
|
|
366
|
-
|
|
366
|
+
/** One position solution (e.g. a daily PPP result). */
|
|
367
|
+
interface CoordinateSample {
|
|
368
|
+
/** Epoch of the solution (ms), e.g. the session midpoint. */
|
|
369
|
+
tMs: number;
|
|
370
|
+
/** Position, ECEF (m), all in one reference frame. */
|
|
371
|
+
ecef: readonly [number, number, number];
|
|
372
|
+
/** Formal 1σ in East/North/Up (m), if known. */
|
|
373
|
+
sigmaEnu?: readonly [number, number, number];
|
|
374
|
+
}
|
|
375
|
+
interface SeriesFitOptions {
|
|
376
|
+
/** Known step epochs (ms): antenna/receiver changes, earthquakes… */
|
|
377
|
+
jumps?: number[];
|
|
378
|
+
/** Search for further jumps (BIC). Default true. */
|
|
379
|
+
detectJumps?: boolean;
|
|
380
|
+
/** Most jumps the search adds. Default 3. */
|
|
381
|
+
maxJumps?: number;
|
|
382
|
+
/** Annual + semi-annual terms: true, false or 'auto' (span ≥ 2 years). */
|
|
383
|
+
seasonal?: boolean | 'auto';
|
|
384
|
+
/** Outlier threshold in robust standard deviations. Default 4. */
|
|
385
|
+
outlierK?: number;
|
|
386
|
+
/** Reference epoch for the offset (ms). Default: middle of the span. */
|
|
387
|
+
refEpochMs?: number;
|
|
388
|
+
/** Samples a detected jump needs on each side. Default 5. */
|
|
389
|
+
minSamplesPerSide?: number;
|
|
390
|
+
/** Fit white + flicker noise for realistic velocity sigmas. Default true. */
|
|
391
|
+
noiseModel?: boolean;
|
|
392
|
+
}
|
|
393
|
+
interface SeriesComponent {
|
|
394
|
+
/** Value at the reference epoch (m), relative to the reference position. */
|
|
395
|
+
offset: number;
|
|
396
|
+
/** Velocity (m/yr). */
|
|
397
|
+
velocity: number;
|
|
398
|
+
/** White-noise formal 1σ of the velocity, scaled by the variance factor. */
|
|
399
|
+
velocitySigma: number;
|
|
400
|
+
/** Velocity 1σ under a white + flicker noise model fitted by maximum
|
|
401
|
+
* likelihood — the realistic uncertainty; null if the series is too
|
|
402
|
+
* short or `noiseModel` is off. */
|
|
403
|
+
velocitySigmaRealistic: number | null;
|
|
404
|
+
/** Share of the residual variance the noise fit attributes to flicker. */
|
|
405
|
+
flickerShare: number | null;
|
|
406
|
+
/** Step size at each jump (m), in the order of `SeriesFit.jumps`. */
|
|
407
|
+
steps: number[];
|
|
408
|
+
/** Annual / semi-annual amplitudes (m), when fitted. */
|
|
409
|
+
annualAmplitude: number | null;
|
|
410
|
+
semiannualAmplitude: number | null;
|
|
411
|
+
/** Weighted RMS of the residuals of the used samples (m). */
|
|
412
|
+
wrms: number;
|
|
413
|
+
}
|
|
414
|
+
interface SeriesPoint {
|
|
415
|
+
tMs: number;
|
|
416
|
+
/** Position relative to the reference position, local ENU (m). */
|
|
417
|
+
enu: [number, number, number];
|
|
418
|
+
/** Model value at tMs (m). */
|
|
419
|
+
model: [number, number, number];
|
|
420
|
+
/** Rejected as an outlier. */
|
|
421
|
+
outlier: boolean;
|
|
422
|
+
}
|
|
423
|
+
interface SeriesFit {
|
|
424
|
+
/** Median position the ENU values are relative to (ECEF, m). */
|
|
425
|
+
refEcef: [number, number, number];
|
|
426
|
+
refEpochMs: number;
|
|
427
|
+
e: SeriesComponent;
|
|
428
|
+
n: SeriesComponent;
|
|
429
|
+
u: SeriesComponent;
|
|
430
|
+
/** Jumps in the model, chronological; `detected` = found by the search. */
|
|
431
|
+
jumps: {
|
|
432
|
+
tMs: number;
|
|
433
|
+
detected: boolean;
|
|
434
|
+
}[];
|
|
435
|
+
points: SeriesPoint[];
|
|
436
|
+
spanYears: number;
|
|
437
|
+
used: number;
|
|
438
|
+
outliers: number;
|
|
439
|
+
seasonal: boolean;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Fit the trajectory model to a station's position series. Needs at least
|
|
443
|
+
* five samples spanning some time; the velocity is meaningful once the span
|
|
444
|
+
* covers a good part of a year (seasonal signals otherwise alias into it).
|
|
445
|
+
*/
|
|
446
|
+
declare function fitCoordinateSeries(samples: readonly CoordinateSample[], opts?: SeriesFitOptions): SeriesFit | null;
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Network alignment: the transformation that maps one day's solutions of
|
|
450
|
+
* reference stations onto their known coordinates, applied to every station
|
|
451
|
+
* of that day.
|
|
452
|
+
*
|
|
453
|
+
* Daily PPP positions share errors over a region — orbit and clock errors
|
|
454
|
+
* of the day, the frame realisation of the product — so a station's series
|
|
455
|
+
* scatters more than the station moves. Estimating those common errors from
|
|
456
|
+
* reference stations with trusted coordinates (IGS/EUREF, propagated to the
|
|
457
|
+
* day by their velocities) and removing them is the regional counterpart of
|
|
458
|
+
* Bernese's datum definition by minimum constraints, and of "common-mode
|
|
459
|
+
* filtering" in GNSS time-series work.
|
|
460
|
+
*
|
|
461
|
+
* Model (position-vector convention, small angles, as `applyHelmert`):
|
|
462
|
+
* x_ref = x + t + d·x + r × x
|
|
463
|
+
* 'translation' estimates t only — the robust choice for a regional network,
|
|
464
|
+
* where rotations are nearly indistinguishable from translations; 'similarity'
|
|
465
|
+
* adds rotation and scale (7 parameters) for a continental or wider spread.
|
|
466
|
+
*/
|
|
467
|
+
type HelmertModel = 'translation' | 'similarity';
|
|
468
|
+
interface HelmertFit {
|
|
469
|
+
model: HelmertModel;
|
|
470
|
+
/** Translation (m). */
|
|
471
|
+
t: [number, number, number];
|
|
472
|
+
/** Rotation (rad), zero for 'translation'. */
|
|
473
|
+
r: [number, number, number];
|
|
474
|
+
/** Scale (unitless, e.g. 1e-9 = 1 ppb), zero for 'translation'. */
|
|
475
|
+
d: number;
|
|
476
|
+
/** Residual (reference − transformed) per input pair, ECEF (m). */
|
|
477
|
+
residuals: [number, number, number][];
|
|
478
|
+
/** Pairs kept in the final fit (false = rejected reference). */
|
|
479
|
+
used: boolean[];
|
|
480
|
+
/** RMS of the used residuals, per coordinate (m). */
|
|
481
|
+
rms: number;
|
|
482
|
+
}
|
|
483
|
+
interface HelmertOptions {
|
|
484
|
+
model?: HelmertModel;
|
|
485
|
+
/** A reference whose 3-D residual exceeds this is dropped (m). Default 0.015. */
|
|
486
|
+
maxResidualM?: number;
|
|
487
|
+
/** Keep at least this many references. Default: 3 (translation) or 4. */
|
|
488
|
+
minRefs?: number;
|
|
489
|
+
}
|
|
490
|
+
type V3 = readonly [number, number, number];
|
|
491
|
+
/** Apply an estimated transformation to one position. */
|
|
492
|
+
declare function applyHelmertFit(x: V3, f: HelmertFit): [number, number, number];
|
|
493
|
+
/**
|
|
494
|
+
* Estimate the transformation from `from` (e.g. the day's PPP positions of
|
|
495
|
+
* the reference stations) to `to` (their known coordinates at that epoch).
|
|
496
|
+
* Rejects the worst-fitting reference while any exceeds `maxResidualM`.
|
|
497
|
+
*/
|
|
498
|
+
declare function estimateHelmert(from: readonly V3[], to: readonly V3[], opts?: HelmertOptions): HelmertFit | null;
|
|
499
|
+
|
|
500
|
+
export { type CompleteSatSignal, type CompleteSignalStats, CompletenessAccumulator, type CompletenessResult, type CoordinateSample, CycleSlipAccumulator, type CycleSlipEvent, type CycleSlipResult, type CycleSlipSignalStats, type HelmertFit, type HelmertModel, type HelmertOptions, IonoAccumulator, type IonoDcbResult, type IonoPoint, type IonoRatePoint, type IonoRateSeries, type IonoResult, type IonoSeries, MultipathAccumulator, type MultipathPoint, type MultipathResult, type MultipathSeries, type MultipathSignalStat, type QualityResult, type SatDcbMap, type SeriesComponent, type SeriesFit, type SeriesFitOptions, type SeriesPoint, analyzeQuality, applyHelmertFit, applyIonoDcb, computeIonoRate, detrendIonoArcs, estimateHelmert, fitCoordinateSeries, parseSinexBiasDcb };
|