gnss-js 1.22.0 → 1.23.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.
@@ -34,6 +34,243 @@ interface KlobucharCoeffs {
34
34
  */
35
35
  declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad: number, azRad: number, elRad: number, gpsTowSec: number): number;
36
36
 
37
+ /**
38
+ * RTK positioning, stage 1: epoch-wise double-differenced (DD)
39
+ * processing of a base/rover receiver pair.
40
+ *
41
+ * Two solvers share the same DD construction:
42
+ *
43
+ * - `solveDgnss` — code-based DGNSS: between-receiver single
44
+ * differences on pseudoranges, double-differenced against a
45
+ * reference satellite (highest elevation per signal group), iterated
46
+ * weighted least squares for the rover position with the full DD
47
+ * covariance (DDs sharing a reference satellite are correlated).
48
+ *
49
+ * - `RtkFloatEngine` — a stateful extended Kalman filter over DD
50
+ * carrier phase + code with one float DD ambiguity state per
51
+ * satellite/signal. An EKF (rather than epoch-wise iterated least
52
+ * squares with appended ambiguity columns) was chosen because the
53
+ * float ambiguities must persist across epochs anyway, and stage 2
54
+ * (LAMBDA) needs the joint float ambiguity covariance — which the
55
+ * EKF maintains natively. This mirrors RTKLIB's float-filter
56
+ * structure (rtkpos.c), which the implementation was validated
57
+ * against; no integer fixing is attempted (`ratio` is reserved).
58
+ *
59
+ * Differencing model: for satellites s and reference r observed by
60
+ * rover R and base B, the DD pseudorange/phase cancels satellite
61
+ * clocks (single difference) and receiver clocks (double difference).
62
+ * On short baselines the ionospheric and most of the tropospheric
63
+ * delay cancel too; the residual troposphere is modelled by the same
64
+ * simple elevation-mapped zenith model `solveSpp` uses, applied
65
+ * differentially. DDs are formed per *signal group* (constellation
66
+ * letter + RINEX code, e.g. `G1C`, `C2I`) so that only like signals
67
+ * are differenced.
68
+ *
69
+ * GLONASS (FDMA): DD phase is formed in metres with per-satellite
70
+ * wavelengths (λ_s·SDφ_s − λ_r·SDφ_r). The common receiver clock
71
+ * term cancels exactly (λ·f = c on every channel); the per-channel
72
+ * receiver phase bias (inter-frequency bias, IFB) does not, but it is
73
+ * constant while lock holds and is absorbed into the float DD
74
+ * ambiguity — the standard reason GLONASS float works while GLONASS
75
+ * integer fixing is hard. DD *code* retains the differential
76
+ * inter-channel code bias between the two receivers, which is small
77
+ * (well below the code noise) for same-model receivers — IFB-safe
78
+ * enough for stage 1, and documented as such.
79
+ *
80
+ * Time convention: `timeMs` is GPS-scale epoch milliseconds, exactly
81
+ * as produced by `parseNovatelRange` / the RINEX and RTCM parsers —
82
+ * the same convention `solveSpp` uses. No system clock is read.
83
+ *
84
+ * Adapting decoder output (see `toRtkEpoch`):
85
+ * - NovAtel: `parseNovatelRange(bytes).epochs[i]` gives
86
+ * `{ timeMs, meas }`; `toRtkEpoch(meas)` picks each satellite's
87
+ * L1-band measurement. When a log carries both RANGE and RANGECMP
88
+ * the same epoch appears twice — keep one epoch per `timeMs`.
89
+ * - RTCM MSM: for each satellite/signal cell build
90
+ * `{ code, pr: pseudorangeM, cp: phaserangeM / lambda, lockTimeMs }`
91
+ * (MSM phaseranges are metres; divide by the carrier wavelength to
92
+ * get cycles) and pass a `prn → measurement` map for the epoch.
93
+ */
94
+
95
+ /** One satellite's measurement on one signal, for one epoch. */
96
+ interface RtkMeasurement {
97
+ /** RINEX band+attribute, e.g. "1C", "2I". */
98
+ code: string;
99
+ /** Pseudorange (m). */
100
+ pr: number | null;
101
+ /** Carrier phase (cycles, RINEX sign), optional — DGNSS needs none. */
102
+ cp?: number | null;
103
+ /** Continuous lock time (ms) for cycle-slip detection, optional. */
104
+ lockTimeMs?: number;
105
+ /** GLONASS frequency channel k (−7…+6), for FDMA wavelengths. */
106
+ gloChannel?: number | null;
107
+ }
108
+ /** One epoch of measurements: PRN → measurement (one signal per PRN). */
109
+ type RtkEpochMeasurements = ReadonlyMap<string, RtkMeasurement>;
110
+ /** Decoder-shaped observation (structurally `NovatelMeasurement`). */
111
+ interface RawObservation {
112
+ prn: string;
113
+ code: string;
114
+ pr: number | null;
115
+ cp?: number | null;
116
+ lockTimeS?: number;
117
+ gloChannel?: number | null;
118
+ }
119
+ /**
120
+ * Adapt one epoch of decoder observations (e.g.
121
+ * `parseNovatelRange(...).epochs[i].meas`) to the RTK input map:
122
+ * keeps each satellite's preferred L1-band code measurement with a
123
+ * valid pseudorange, drops unsupported systems (SBAS, NavIC).
124
+ */
125
+ declare function toRtkEpoch(meas: readonly RawObservation[]): Map<string, RtkMeasurement>;
126
+ /** Ephemeris source: a per-PRN map, or a list searched per epoch. */
127
+ type EphemerisSource = ReadonlyMap<string, Ephemeris> | readonly Ephemeris[];
128
+ interface DgnssOptions {
129
+ /** Elevation mask at the base, degrees. Default 10. */
130
+ elevationMaskDeg?: number;
131
+ /** Model the differential troposphere. Default true. */
132
+ troposphere?: boolean;
133
+ /** Maximum Gauss-Newton iterations. Default 15. */
134
+ maxIterations?: number;
135
+ /** Convergence threshold on the position update (m). Default 1e-4. */
136
+ convergenceM?: number;
137
+ /** Zenith code sigma per receiver (m). Default 0.3. */
138
+ codeSigmaM?: number;
139
+ /** Worst-first DD residual rejection threshold (m). Default 20. */
140
+ rejectThresholdM?: number;
141
+ /** Initial rover position; defaults to the base position. */
142
+ initialPosition?: [number, number, number];
143
+ }
144
+ interface DgnssSolution {
145
+ /** Rover position, ECEF metres. */
146
+ position: [number, number, number];
147
+ /** Baseline rover − base, ECEF metres. */
148
+ baseline: [number, number, number];
149
+ /** Formal position sigmas from the LSQ covariance, ECEF metres. */
150
+ sigmas: [number, number, number];
151
+ /** Satellites used (including references). */
152
+ usedSatellites: string[];
153
+ /** Reference satellite per signal group. */
154
+ refSatellites: Record<string, string>;
155
+ /** Post-fit DD residual (m) per non-reference PRN. */
156
+ residuals: Record<string, number>;
157
+ /** Satellites rejected by the residual screen. */
158
+ rejectedSatellites: string[];
159
+ nSats: number;
160
+ iterations: number;
161
+ converged: boolean;
162
+ }
163
+ /**
164
+ * Solve the rover position from one epoch of DD pseudoranges.
165
+ *
166
+ * @param rover Rover measurements (PRN → measurement).
167
+ * @param base Base measurements (PRN → measurement, same codes).
168
+ * @param basePos Known base position, ECEF metres.
169
+ * @param ephemerides Broadcast ephemerides (per-PRN map, or a list
170
+ * searched by closest epoch — pass `parseNovatelNav(...).ephemerides`
171
+ * or `parseNavFile(...).ephemerides` directly).
172
+ * @param timeMs Receiver epoch, GPS-scale milliseconds.
173
+ */
174
+ declare function solveDgnss(rover: RtkEpochMeasurements, base: RtkEpochMeasurements, basePos: readonly [number, number, number], ephemerides: EphemerisSource, timeMs: number, opts?: DgnssOptions): DgnssSolution | null;
175
+ interface RtkFloatOptions {
176
+ /**
177
+ * 'static': the rover position is a constant state (random walk
178
+ * `processNoisePosM`·√s, default 0). 'kinematic': the position is
179
+ * re-initialised each epoch with `kinematicSigmaM` uncertainty
180
+ * (no velocity states at stage 1 — RTKLIB's default kinematic
181
+ * handling without doppler). Default 'kinematic'.
182
+ */
183
+ mode?: 'static' | 'kinematic';
184
+ /** Elevation mask at the base, degrees. Default 10. */
185
+ elevationMaskDeg?: number;
186
+ /** Model the differential troposphere. Default true. */
187
+ troposphere?: boolean;
188
+ /** Zenith code sigma per receiver (m). Default 0.3. */
189
+ codeSigmaM?: number;
190
+ /** Zenith phase sigma per receiver (m). Default 0.003. */
191
+ phaseSigmaM?: number;
192
+ /** Static-mode position random walk (m/√s). Default 0. */
193
+ processNoisePosM?: number;
194
+ /** Kinematic per-epoch position reset sigma (m). Default 30. */
195
+ kinematicSigmaM?: number;
196
+ /** Ambiguity random walk (cycles/√s). Default 1e-4. */
197
+ ambProcessNoiseCycles?: number;
198
+ /** New float ambiguity sigma (cycles). Default 30. */
199
+ ambInitSigmaCycles?: number;
200
+ /** Drop ambiguity states unseen for this long (ms). Default 10000. */
201
+ maxGapMs?: number;
202
+ /** DD code innovation gate (m): drop the satellite. Default 30. */
203
+ codeGateM?: number;
204
+ /**
205
+ * Phase-minus-code innovation divergence gate (m): treat as an
206
+ * undetected cycle slip and re-initialise the ambiguity. Default 5.
207
+ */
208
+ slipGateM?: number;
209
+ /** Measurement-update relinearisations (IEKF). Default 2. */
210
+ updateIterations?: number;
211
+ }
212
+ interface RtkFloatSolution {
213
+ /** Epoch, GPS-scale milliseconds. */
214
+ timeMs: number;
215
+ /** Rover position, ECEF metres. */
216
+ position: [number, number, number];
217
+ /** Float baseline rover − base, ECEF metres. */
218
+ floatBaseline: [number, number, number];
219
+ /** Satellites contributing DD rows this epoch (incl. references). */
220
+ nSats: number;
221
+ /** Ambiguity-validation ratio — undefined at stage 1 (no LAMBDA). */
222
+ ratio?: number;
223
+ /** Formal position sigmas (filter covariance), ECEF metres. */
224
+ sigmas: [number, number, number];
225
+ /** Float DD ambiguity (cycles) per non-reference PRN. */
226
+ ambiguities: Record<string, number>;
227
+ /** Reference satellite per signal group. */
228
+ refSatellites: Record<string, string>;
229
+ }
230
+ /**
231
+ * Epoch-by-epoch float RTK filter: EKF states = rover position
232
+ * (static or kinematic) + one float DD ambiguity (cycles) per
233
+ * satellite/signal. Cycle slips are handled via lock-time regressions
234
+ * (LLI-style resets) and a phase/code divergence gate; reference-
235
+ * satellite switches re-map the DD ambiguity states algebraically
236
+ * (N_i' = N_i − N_r', old reference gains −N_r') so no information is
237
+ * lost. Feed epochs in time order via `process`.
238
+ */
239
+ declare class RtkFloatEngine {
240
+ private readonly basePos;
241
+ private readonly ephemerides;
242
+ private readonly o;
243
+ /** State vector [x, y, z, N₁…] and covariance; null before init. */
244
+ private x;
245
+ private P;
246
+ private amb;
247
+ private refs;
248
+ private track;
249
+ private lastMs;
250
+ constructor(basePos: readonly [number, number, number], ephemerides: EphemerisSource, opts?: RtkFloatOptions);
251
+ /** Clear all filter state (position, ambiguities, lock history). */
252
+ reset(): void;
253
+ private ambIndex;
254
+ /** Remove one ambiguity state (row/col) from x and P. */
255
+ private dropAmb;
256
+ /** Append an ambiguity state with the given value and variance. */
257
+ private addAmb;
258
+ /**
259
+ * Re-map a group's DD ambiguities from the old reference r to the
260
+ * new reference r' (which must hold a state): N_i' = N_i − N_r' and
261
+ * the old reference's slot becomes N_r = −N_r'. Exact linear
262
+ * transform of state and covariance (P' = T P Tᵀ).
263
+ */
264
+ private retarget;
265
+ /**
266
+ * Process one synchronized epoch (same nominal `timeMs` for both
267
+ * receivers). Returns null until a first position can be
268
+ * initialised (via an internal DGNSS solve) or when the epoch has
269
+ * fewer than 3 usable DD rows.
270
+ */
271
+ process(rover: RtkEpochMeasurements, base: RtkEpochMeasurements, timeMs: number): RtkFloatSolution | null;
272
+ }
273
+
37
274
  /**
38
275
  * Single-point positioning (SPP) from pseudoranges and broadcast
39
276
  * ephemerides.
@@ -114,4 +351,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
114
351
  */
115
352
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
116
353
 
117
- export { type KlobucharCoeffs, type SppOptions, type SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveSpp };
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 };
@@ -34,6 +34,243 @@ interface KlobucharCoeffs {
34
34
  */
35
35
  declare function klobucharDelay(coeffs: KlobucharCoeffs, latRad: number, lonRad: number, azRad: number, elRad: number, gpsTowSec: number): number;
36
36
 
37
+ /**
38
+ * RTK positioning, stage 1: epoch-wise double-differenced (DD)
39
+ * processing of a base/rover receiver pair.
40
+ *
41
+ * Two solvers share the same DD construction:
42
+ *
43
+ * - `solveDgnss` — code-based DGNSS: between-receiver single
44
+ * differences on pseudoranges, double-differenced against a
45
+ * reference satellite (highest elevation per signal group), iterated
46
+ * weighted least squares for the rover position with the full DD
47
+ * covariance (DDs sharing a reference satellite are correlated).
48
+ *
49
+ * - `RtkFloatEngine` — a stateful extended Kalman filter over DD
50
+ * carrier phase + code with one float DD ambiguity state per
51
+ * satellite/signal. An EKF (rather than epoch-wise iterated least
52
+ * squares with appended ambiguity columns) was chosen because the
53
+ * float ambiguities must persist across epochs anyway, and stage 2
54
+ * (LAMBDA) needs the joint float ambiguity covariance — which the
55
+ * EKF maintains natively. This mirrors RTKLIB's float-filter
56
+ * structure (rtkpos.c), which the implementation was validated
57
+ * against; no integer fixing is attempted (`ratio` is reserved).
58
+ *
59
+ * Differencing model: for satellites s and reference r observed by
60
+ * rover R and base B, the DD pseudorange/phase cancels satellite
61
+ * clocks (single difference) and receiver clocks (double difference).
62
+ * On short baselines the ionospheric and most of the tropospheric
63
+ * delay cancel too; the residual troposphere is modelled by the same
64
+ * simple elevation-mapped zenith model `solveSpp` uses, applied
65
+ * differentially. DDs are formed per *signal group* (constellation
66
+ * letter + RINEX code, e.g. `G1C`, `C2I`) so that only like signals
67
+ * are differenced.
68
+ *
69
+ * GLONASS (FDMA): DD phase is formed in metres with per-satellite
70
+ * wavelengths (λ_s·SDφ_s − λ_r·SDφ_r). The common receiver clock
71
+ * term cancels exactly (λ·f = c on every channel); the per-channel
72
+ * receiver phase bias (inter-frequency bias, IFB) does not, but it is
73
+ * constant while lock holds and is absorbed into the float DD
74
+ * ambiguity — the standard reason GLONASS float works while GLONASS
75
+ * integer fixing is hard. DD *code* retains the differential
76
+ * inter-channel code bias between the two receivers, which is small
77
+ * (well below the code noise) for same-model receivers — IFB-safe
78
+ * enough for stage 1, and documented as such.
79
+ *
80
+ * Time convention: `timeMs` is GPS-scale epoch milliseconds, exactly
81
+ * as produced by `parseNovatelRange` / the RINEX and RTCM parsers —
82
+ * the same convention `solveSpp` uses. No system clock is read.
83
+ *
84
+ * Adapting decoder output (see `toRtkEpoch`):
85
+ * - NovAtel: `parseNovatelRange(bytes).epochs[i]` gives
86
+ * `{ timeMs, meas }`; `toRtkEpoch(meas)` picks each satellite's
87
+ * L1-band measurement. When a log carries both RANGE and RANGECMP
88
+ * the same epoch appears twice — keep one epoch per `timeMs`.
89
+ * - RTCM MSM: for each satellite/signal cell build
90
+ * `{ code, pr: pseudorangeM, cp: phaserangeM / lambda, lockTimeMs }`
91
+ * (MSM phaseranges are metres; divide by the carrier wavelength to
92
+ * get cycles) and pass a `prn → measurement` map for the epoch.
93
+ */
94
+
95
+ /** One satellite's measurement on one signal, for one epoch. */
96
+ interface RtkMeasurement {
97
+ /** RINEX band+attribute, e.g. "1C", "2I". */
98
+ code: string;
99
+ /** Pseudorange (m). */
100
+ pr: number | null;
101
+ /** Carrier phase (cycles, RINEX sign), optional — DGNSS needs none. */
102
+ cp?: number | null;
103
+ /** Continuous lock time (ms) for cycle-slip detection, optional. */
104
+ lockTimeMs?: number;
105
+ /** GLONASS frequency channel k (−7…+6), for FDMA wavelengths. */
106
+ gloChannel?: number | null;
107
+ }
108
+ /** One epoch of measurements: PRN → measurement (one signal per PRN). */
109
+ type RtkEpochMeasurements = ReadonlyMap<string, RtkMeasurement>;
110
+ /** Decoder-shaped observation (structurally `NovatelMeasurement`). */
111
+ interface RawObservation {
112
+ prn: string;
113
+ code: string;
114
+ pr: number | null;
115
+ cp?: number | null;
116
+ lockTimeS?: number;
117
+ gloChannel?: number | null;
118
+ }
119
+ /**
120
+ * Adapt one epoch of decoder observations (e.g.
121
+ * `parseNovatelRange(...).epochs[i].meas`) to the RTK input map:
122
+ * keeps each satellite's preferred L1-band code measurement with a
123
+ * valid pseudorange, drops unsupported systems (SBAS, NavIC).
124
+ */
125
+ declare function toRtkEpoch(meas: readonly RawObservation[]): Map<string, RtkMeasurement>;
126
+ /** Ephemeris source: a per-PRN map, or a list searched per epoch. */
127
+ type EphemerisSource = ReadonlyMap<string, Ephemeris> | readonly Ephemeris[];
128
+ interface DgnssOptions {
129
+ /** Elevation mask at the base, degrees. Default 10. */
130
+ elevationMaskDeg?: number;
131
+ /** Model the differential troposphere. Default true. */
132
+ troposphere?: boolean;
133
+ /** Maximum Gauss-Newton iterations. Default 15. */
134
+ maxIterations?: number;
135
+ /** Convergence threshold on the position update (m). Default 1e-4. */
136
+ convergenceM?: number;
137
+ /** Zenith code sigma per receiver (m). Default 0.3. */
138
+ codeSigmaM?: number;
139
+ /** Worst-first DD residual rejection threshold (m). Default 20. */
140
+ rejectThresholdM?: number;
141
+ /** Initial rover position; defaults to the base position. */
142
+ initialPosition?: [number, number, number];
143
+ }
144
+ interface DgnssSolution {
145
+ /** Rover position, ECEF metres. */
146
+ position: [number, number, number];
147
+ /** Baseline rover − base, ECEF metres. */
148
+ baseline: [number, number, number];
149
+ /** Formal position sigmas from the LSQ covariance, ECEF metres. */
150
+ sigmas: [number, number, number];
151
+ /** Satellites used (including references). */
152
+ usedSatellites: string[];
153
+ /** Reference satellite per signal group. */
154
+ refSatellites: Record<string, string>;
155
+ /** Post-fit DD residual (m) per non-reference PRN. */
156
+ residuals: Record<string, number>;
157
+ /** Satellites rejected by the residual screen. */
158
+ rejectedSatellites: string[];
159
+ nSats: number;
160
+ iterations: number;
161
+ converged: boolean;
162
+ }
163
+ /**
164
+ * Solve the rover position from one epoch of DD pseudoranges.
165
+ *
166
+ * @param rover Rover measurements (PRN → measurement).
167
+ * @param base Base measurements (PRN → measurement, same codes).
168
+ * @param basePos Known base position, ECEF metres.
169
+ * @param ephemerides Broadcast ephemerides (per-PRN map, or a list
170
+ * searched by closest epoch — pass `parseNovatelNav(...).ephemerides`
171
+ * or `parseNavFile(...).ephemerides` directly).
172
+ * @param timeMs Receiver epoch, GPS-scale milliseconds.
173
+ */
174
+ declare function solveDgnss(rover: RtkEpochMeasurements, base: RtkEpochMeasurements, basePos: readonly [number, number, number], ephemerides: EphemerisSource, timeMs: number, opts?: DgnssOptions): DgnssSolution | null;
175
+ interface RtkFloatOptions {
176
+ /**
177
+ * 'static': the rover position is a constant state (random walk
178
+ * `processNoisePosM`·√s, default 0). 'kinematic': the position is
179
+ * re-initialised each epoch with `kinematicSigmaM` uncertainty
180
+ * (no velocity states at stage 1 — RTKLIB's default kinematic
181
+ * handling without doppler). Default 'kinematic'.
182
+ */
183
+ mode?: 'static' | 'kinematic';
184
+ /** Elevation mask at the base, degrees. Default 10. */
185
+ elevationMaskDeg?: number;
186
+ /** Model the differential troposphere. Default true. */
187
+ troposphere?: boolean;
188
+ /** Zenith code sigma per receiver (m). Default 0.3. */
189
+ codeSigmaM?: number;
190
+ /** Zenith phase sigma per receiver (m). Default 0.003. */
191
+ phaseSigmaM?: number;
192
+ /** Static-mode position random walk (m/√s). Default 0. */
193
+ processNoisePosM?: number;
194
+ /** Kinematic per-epoch position reset sigma (m). Default 30. */
195
+ kinematicSigmaM?: number;
196
+ /** Ambiguity random walk (cycles/√s). Default 1e-4. */
197
+ ambProcessNoiseCycles?: number;
198
+ /** New float ambiguity sigma (cycles). Default 30. */
199
+ ambInitSigmaCycles?: number;
200
+ /** Drop ambiguity states unseen for this long (ms). Default 10000. */
201
+ maxGapMs?: number;
202
+ /** DD code innovation gate (m): drop the satellite. Default 30. */
203
+ codeGateM?: number;
204
+ /**
205
+ * Phase-minus-code innovation divergence gate (m): treat as an
206
+ * undetected cycle slip and re-initialise the ambiguity. Default 5.
207
+ */
208
+ slipGateM?: number;
209
+ /** Measurement-update relinearisations (IEKF). Default 2. */
210
+ updateIterations?: number;
211
+ }
212
+ interface RtkFloatSolution {
213
+ /** Epoch, GPS-scale milliseconds. */
214
+ timeMs: number;
215
+ /** Rover position, ECEF metres. */
216
+ position: [number, number, number];
217
+ /** Float baseline rover − base, ECEF metres. */
218
+ floatBaseline: [number, number, number];
219
+ /** Satellites contributing DD rows this epoch (incl. references). */
220
+ nSats: number;
221
+ /** Ambiguity-validation ratio — undefined at stage 1 (no LAMBDA). */
222
+ ratio?: number;
223
+ /** Formal position sigmas (filter covariance), ECEF metres. */
224
+ sigmas: [number, number, number];
225
+ /** Float DD ambiguity (cycles) per non-reference PRN. */
226
+ ambiguities: Record<string, number>;
227
+ /** Reference satellite per signal group. */
228
+ refSatellites: Record<string, string>;
229
+ }
230
+ /**
231
+ * Epoch-by-epoch float RTK filter: EKF states = rover position
232
+ * (static or kinematic) + one float DD ambiguity (cycles) per
233
+ * satellite/signal. Cycle slips are handled via lock-time regressions
234
+ * (LLI-style resets) and a phase/code divergence gate; reference-
235
+ * satellite switches re-map the DD ambiguity states algebraically
236
+ * (N_i' = N_i − N_r', old reference gains −N_r') so no information is
237
+ * lost. Feed epochs in time order via `process`.
238
+ */
239
+ declare class RtkFloatEngine {
240
+ private readonly basePos;
241
+ private readonly ephemerides;
242
+ private readonly o;
243
+ /** State vector [x, y, z, N₁…] and covariance; null before init. */
244
+ private x;
245
+ private P;
246
+ private amb;
247
+ private refs;
248
+ private track;
249
+ private lastMs;
250
+ constructor(basePos: readonly [number, number, number], ephemerides: EphemerisSource, opts?: RtkFloatOptions);
251
+ /** Clear all filter state (position, ambiguities, lock history). */
252
+ reset(): void;
253
+ private ambIndex;
254
+ /** Remove one ambiguity state (row/col) from x and P. */
255
+ private dropAmb;
256
+ /** Append an ambiguity state with the given value and variance. */
257
+ private addAmb;
258
+ /**
259
+ * Re-map a group's DD ambiguities from the old reference r to the
260
+ * new reference r' (which must hold a state): N_i' = N_i − N_r' and
261
+ * the old reference's slot becomes N_r = −N_r'. Exact linear
262
+ * transform of state and covariance (P' = T P Tᵀ).
263
+ */
264
+ private retarget;
265
+ /**
266
+ * Process one synchronized epoch (same nominal `timeMs` for both
267
+ * receivers). Returns null until a first position can be
268
+ * initialised (via an internal DGNSS solve) or when the epoch has
269
+ * fewer than 3 usable DD rows.
270
+ */
271
+ process(rover: RtkEpochMeasurements, base: RtkEpochMeasurements, timeMs: number): RtkFloatSolution | null;
272
+ }
273
+
37
274
  /**
38
275
  * Single-point positioning (SPP) from pseudoranges and broadcast
39
276
  * ephemerides.
@@ -114,4 +351,4 @@ declare function ionoFree(p1: number, p2: number, f1: number, f2: number): numbe
114
351
  */
115
352
  declare function solveSpp(pseudoranges: Map<string, number>, ephemerides: Map<string, Ephemeris>, timeMs: number, opts?: SppOptions): SppSolution | null;
116
353
 
117
- export { type KlobucharCoeffs, type SppOptions, type SppSolution, ionoFree, klobucharDelay, satClockCorrection, solveSpp };
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 };
@@ -1,18 +1,24 @@
1
1
  import {
2
+ RtkFloatEngine,
2
3
  ionoFree,
3
4
  klobucharDelay,
4
5
  satClockCorrection,
5
- solveSpp
6
- } from "./chunk-B5LU4746.js";
7
- import "./chunk-JNFV5BYB.js";
6
+ solveDgnss,
7
+ solveSpp,
8
+ toRtkEpoch
9
+ } from "./chunk-CUWF56ST.js";
10
+ import "./chunk-MHGNKSSH.js";
8
11
  import "./chunk-37QNKGTC.js";
9
12
  import "./chunk-6FAL6P4G.js";
10
13
  import "./chunk-HVXYFUCB.js";
11
14
  import "./chunk-LEEU5OIO.js";
12
15
  import "./chunk-FIEWO4J4.js";
13
16
  export {
17
+ RtkFloatEngine,
14
18
  ionoFree,
15
19
  klobucharDelay,
16
20
  satClockCorrection,
17
- solveSpp
21
+ solveDgnss,
22
+ solveSpp,
23
+ toRtkEpoch
18
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnss-js",
3
- "version": "1.22.0",
3
+ "version": "1.23.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,