gnss-js 1.23.0 → 1.24.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.
@@ -54,7 +54,29 @@ declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad:
54
54
  * (LAMBDA) needs the joint float ambiguity covariance — which the
55
55
  * EKF maintains natively. This mirrors RTKLIB's float-filter
56
56
  * structure (rtkpos.c), which the implementation was validated
57
- * against; no integer fixing is attempted (`ratio` is reserved).
57
+ * against.
58
+ *
59
+ * Stage 2 — integer ambiguity resolution (opt-in via
60
+ * `ambiguityResolution: 'instant'`): after each float update the
61
+ * engine extracts the DD ambiguity subvector and its joint covariance
62
+ * from the filter, runs MLAMBDA (`lambdaSearch`, a port of RTKLIB's
63
+ * lambda.c) and validates the best candidate with the ratio test
64
+ * (s₂/s₁ ≥ `ratioThreshold`, default 3.0 — RTKLIB's default). On
65
+ * acceptance the rover position is conditioned on the integer
66
+ * ambiguities (xa = x − P_xb·Q_b⁻¹·(b − ǎ), RTKLIB rtkpos.c
67
+ * `resamb_LAMBDA`) and reported as a FIXED solution; the filter state
68
+ * itself is *not* modified (instant, per-epoch fixing — RTKLIB's
69
+ * `armode=instantaneous`; continuous AR/fix-and-hold, which feed the
70
+ * fix back into the filter, are stage-3 continuity options). When the
71
+ * full set fails the ratio test, partial fixing retries after
72
+ * dropping the lowest-elevation ambiguity, one at a time, down to
73
+ * half the candidate set (and never below `minFixAmbiguities`) — an
74
+ * elevation-ordered subset walk in the spirit of RTKLIB demo5's
75
+ * satellite exclusion, floored so an unconverged float cannot buy a
76
+ * lucky ratio on a tiny subset. GLONASS ambiguities are excluded from
77
+ * fixing by
78
+ * default (DD IFB is only integer-safe across same-model receivers;
79
+ * enable with `glonassAr`).
58
80
  *
59
81
  * Differencing model: for satellites s and reference r observed by
60
82
  * rover R and base B, the DD pseudorange/phase cancels satellite
@@ -208,18 +230,65 @@ interface RtkFloatOptions {
208
230
  slipGateM?: number;
209
231
  /** Measurement-update relinearisations (IEKF). Default 2. */
210
232
  updateIterations?: number;
233
+ /**
234
+ * Integer ambiguity resolution: 'off' keeps the stage-1 float-only
235
+ * behaviour; 'instant' attempts a per-epoch LAMBDA fix after each
236
+ * float update (no state feedback — RTKLIB `armode=instantaneous`).
237
+ * Default 'off'.
238
+ */
239
+ ambiguityResolution?: 'off' | 'instant';
240
+ /** Ratio-test acceptance threshold (s₂/s₁). Default 3.0 (RTKLIB). */
241
+ ratioThreshold?: number;
242
+ /**
243
+ * Additional elevation mask (deg) for ambiguities entering the fix
244
+ * (RTKLIB `elmaskar`); 0 = use every measured ambiguity. Default 0.
245
+ */
246
+ arElevationMaskDeg?: number;
247
+ /**
248
+ * Include GLONASS FDMA ambiguities in the integer fix. Only sound
249
+ * across same-model receivers (differential IFB). Default false.
250
+ */
251
+ glonassAr?: boolean;
252
+ /**
253
+ * Partial fixing: when the full ambiguity set fails the ratio test,
254
+ * drop the lowest-elevation ambiguity and retry (elevation-ordered
255
+ * subset walk), never below half the candidates. Default true.
256
+ */
257
+ partialFixing?: boolean;
258
+ /** Minimum DD ambiguities to attempt/accept a fix. Default 4. */
259
+ minFixAmbiguities?: number;
211
260
  }
212
261
  interface RtkFloatSolution {
213
262
  /** Epoch, GPS-scale milliseconds. */
214
263
  timeMs: number;
215
- /** Rover position, ECEF metres. */
264
+ /**
265
+ * Rover position, ECEF metres — the ambiguity-fixed position when
266
+ * `status === 'fixed'`, the float filter position otherwise.
267
+ */
216
268
  position: [number, number, number];
217
- /** Float baseline rover − base, ECEF metres. */
269
+ /** Float baseline rover − base, ECEF metres (always the float). */
218
270
  floatBaseline: [number, number, number];
271
+ /**
272
+ * Solution quality: 'fixed' when LAMBDA passed the ratio test,
273
+ * 'float' when carrier ambiguities contributed but no (accepted)
274
+ * fix, 'dgnss' when the epoch was updated from code DDs only.
275
+ */
276
+ status: 'fixed' | 'float' | 'dgnss';
219
277
  /** Satellites contributing DD rows this epoch (incl. references). */
220
278
  nSats: number;
221
- /** Ambiguity-validation ratio — undefined at stage 1 (no LAMBDA). */
279
+ /**
280
+ * Ambiguity-validation ratio (s₂/s₁ of the LAMBDA candidates,
281
+ * capped at 999.9 as RTKLIB): the accepted subset's ratio when
282
+ * fixed, the full-set ratio otherwise. Undefined when no fix was
283
+ * attempted (`ambiguityResolution: 'off'` or no eligible set).
284
+ */
222
285
  ratio?: number;
286
+ /** Number of integer-fixed DD ambiguities (when `status==='fixed'`). */
287
+ nFixed?: number;
288
+ /** Fixed baseline rover − base, ECEF metres (when fixed). */
289
+ fixedBaseline?: [number, number, number];
290
+ /** Integer DD ambiguity (cycles) per fixed PRN (when fixed). */
291
+ fixedAmbiguities?: Record<string, number>;
223
292
  /** Formal position sigmas (filter covariance), ECEF metres. */
224
293
  sigmas: [number, number, number];
225
294
  /** Float DD ambiguity (cycles) per non-reference PRN. */
@@ -262,6 +331,17 @@ declare class RtkFloatEngine {
262
331
  * transform of state and covariance (P' = T P Tᵀ).
263
332
  */
264
333
  private retarget;
334
+ /**
335
+ * Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
336
+ * extract the float subvector b and covariance Q_b from the filter,
337
+ * run MLAMBDA and, without touching the filter state, condition the
338
+ * position on the best integer candidate,
339
+ * xa = x − P_xb·Q_b⁻¹·(b − ǎ)
340
+ * (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
341
+ * positive definite / invertible or the search fails; ratio
342
+ * validation is the caller's job.
343
+ */
344
+ private tryFix;
265
345
  /**
266
346
  * Process one synchronized epoch (same nominal `timeMs` for both
267
347
  * receivers). Returns null until a first position can be
@@ -271,6 +351,88 @@ declare class RtkFloatEngine {
271
351
  process(rover: RtkEpochMeasurements, base: RtkEpochMeasurements, timeMs: number): RtkFloatSolution | null;
272
352
  }
273
353
 
354
+ /**
355
+ * Integer ambiguity resolution: LAMBDA decorrelation + MLAMBDA
356
+ * integer least-squares search.
357
+ *
358
+ * Port of RTKLIB's `lambda.c` (demo5 / rtklibexplorer fork,
359
+ * src/lambda.c, Copyright (c) 2007-2008 T. Takasu, BSD-2-Clause),
360
+ * cross-checked against the reference papers where the C comments are
361
+ * thin:
362
+ *
363
+ * - [1] P.J.G. Teunissen, "The least-squares ambiguity decorrelation
364
+ * adjustment: a method for fast GPS ambiguity estimation",
365
+ * J. Geodesy 70, 65-82, 1995.
366
+ * - [2] X.-W. Chang, X. Yang, T. Zhou, "MLAMBDA: A modified LAMBDA
367
+ * method for integer least-squares estimation", J. Geodesy 79,
368
+ * 552-565, 2005.
369
+ *
370
+ * Pipeline (identical to RTKLIB's `lambda()`):
371
+ * 1. LᵀDL factorization of the float covariance, Q = Lᵀ·diag(D)·L
372
+ * with L unit lower triangular (RTKLIB's `LD`).
373
+ * 2. LAMBDA reduction [1]: integer Gauss transformations interleaved
374
+ * with symmetric permutations until the transformed D is
375
+ * (approximately) descending — producing a unimodular Z with
376
+ * Qz = ZᵀQZ = Lᵀ·diag(D)·L far better conditioned than Q.
377
+ * 3. MLAMBDA search [2] in the decorrelated space: depth-first
378
+ * enumeration over a shrinking ellipsoid, keeping the `m` smallest
379
+ * residual quadratic forms (z−ž)ᵀQz⁻¹(z−ž).
380
+ * 4. Back-transform the candidates to the original space, a = Z⁻ᵀ·ž
381
+ * (RTKLIB solves ZᵀF = E; Z is unimodular so F is exactly integer).
382
+ *
383
+ * Deviations from lambda.c:
384
+ * - matrices are `Float64Array` in the same column-major layout, with
385
+ * allocation-failure paths dropped (JS throws instead),
386
+ * - failures (non-positive-definite Q, search loop overflow) return
387
+ * `null` instead of a status code + stderr message,
388
+ * - back-transformed candidates are rounded to the nearest integer:
389
+ * F = Z⁻ᵀ·ž is exactly integer in ℤ arithmetic and the rounding only
390
+ * removes the ~1e-12 fuzz of the floating-point triangular solve,
391
+ * - the ratio s₂/s₁ used by the RTKLIB caller (`rtkpos.c`) is returned
392
+ * alongside the candidates (∞ when s₁ = 0, i.e. noise-free input).
393
+ */
394
+ /** Result of an integer least-squares search. */
395
+ interface LambdaResult {
396
+ /**
397
+ * The `m` best integer candidate vectors (length n each), sorted by
398
+ * ascending residual quadratic form; `candidates[0]` is the integer
399
+ * least-squares minimizer.
400
+ */
401
+ candidates: Float64Array[];
402
+ /**
403
+ * Residual quadratic forms sᵢ = (a−ǎᵢ)ᵀQ⁻¹(a−ǎᵢ) matching
404
+ * `candidates`, ascending.
405
+ */
406
+ residuals: Float64Array;
407
+ /**
408
+ * Ratio-test statistic s₂/s₁ (second-best over best); `Infinity`
409
+ * when the best residual is exactly 0 (noise-free input). Callers
410
+ * typically cap it (RTKLIB uses 999.9) before thresholding.
411
+ */
412
+ ratio: number;
413
+ }
414
+ /**
415
+ * LAMBDA/MLAMBDA integer least-squares estimation (RTKLIB `lambda`):
416
+ * decorrelate the float ambiguities, search the `m` best integer
417
+ * vectors and back-transform them to the original space.
418
+ *
419
+ * @param a Float ambiguities (length ≥ n).
420
+ * @param Q Their covariance, n×n (symmetric — row/column-major agree).
421
+ * @param n Number of ambiguities.
422
+ * @param m Number of candidates to return (default 2: best +
423
+ * second-best, what the ratio test needs).
424
+ * @returns Candidates + residuals + ratio, or null when Q is not
425
+ * positive definite or the search does not terminate.
426
+ */
427
+ declare function lambdaSearch(a: Float64Array, Q: Float64Array, n: number, m?: number): LambdaResult | null;
428
+ /**
429
+ * LAMBDA reduction only (RTKLIB `lambda_reduction`): the unimodular
430
+ * decorrelation matrix Z (n×n, column-major) such that Qz = ZᵀQZ is
431
+ * (near-)diagonally dominant. Exposed for diagnostics/tests; returns
432
+ * null when Q is not positive definite.
433
+ */
434
+ declare function lambdaReduction(Q: Float64Array, n: number): Float64Array | null;
435
+
274
436
  /**
275
437
  * Single-point positioning (SPP) from pseudoranges and broadcast
276
438
  * ephemerides.
@@ -351,4 +513,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
351
513
  */
352
514
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
353
515
 
354
- export { type DgnssOptions, type DgnssSolution, type EphemerisSource, type KlobucharCoeffs, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
516
+ export { type DgnssOptions, type DgnssSolution, type EphemerisSource, type KlobucharCoeffs, type LambdaResult, type RawObservation, type RtkEpochMeasurements, RtkFloatEngine, type RtkFloatOptions, type RtkFloatSolution, type RtkMeasurement, type SppOptions, type SppSolution, ionoFree, klobucharDelay, lambdaReduction, lambdaSearch, satClockCorrection, solveDgnss, solveSpp, toRtkEpoch };
@@ -2,11 +2,13 @@ import {
2
2
  RtkFloatEngine,
3
3
  ionoFree,
4
4
  klobucharDelay,
5
+ lambdaReduction,
6
+ lambdaSearch,
5
7
  satClockCorrection,
6
8
  solveDgnss,
7
9
  solveSpp,
8
10
  toRtkEpoch
9
- } from "./chunk-CUWF56ST.js";
11
+ } from "./chunk-T2ZMNRCY.js";
10
12
  import "./chunk-MHGNKSSH.js";
11
13
  import "./chunk-37QNKGTC.js";
12
14
  import "./chunk-6FAL6P4G.js";
@@ -17,6 +19,8 @@ export {
17
19
  RtkFloatEngine,
18
20
  ionoFree,
19
21
  klobucharDelay,
22
+ lambdaReduction,
23
+ lambdaSearch,
20
24
  satClockCorrection,
21
25
  solveDgnss,
22
26
  solveSpp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.23.0",
3
+ "version": "1.24.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,