gnss-js 1.23.0 → 1.25.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.
@@ -43,6 +43,252 @@ function klobucharDelay(coeffs, latRad, lonRad, azRad, elRad, gpsTowSec) {
43
43
  return F * (5e-9 + amp * (1 - x2 / 2 + x2 * x2 / 24));
44
44
  }
45
45
 
46
+ // src/positioning/gim.ts
47
+ var R_E = 6371e3;
48
+ var H_ION = 45e4;
49
+ var F_L1 = 157542e4;
50
+ var IONO_L1_M_PER_TECU = 403e15 / (F_L1 * F_L1);
51
+ function tecAtMap(grid, mapIdx, latDeg, lonDeg) {
52
+ const m = grid.maps[mapIdx];
53
+ const { lats, lons } = grid;
54
+ const w = lons.length;
55
+ const fi = (latDeg - lats[0]) / (lats[lats.length - 1] - lats[0]) * (lats.length - 1);
56
+ const fj = (lonDeg - lons[0]) / (lons[w - 1] - lons[0]) * (w - 1);
57
+ const i0 = Math.max(0, Math.min(lats.length - 2, Math.floor(fi)));
58
+ const j0 = Math.max(0, Math.min(w - 2, Math.floor(fj)));
59
+ const di = Math.max(0, Math.min(1, fi - i0));
60
+ const dj = Math.max(0, Math.min(1, fj - j0));
61
+ return m[i0 * w + j0] * (1 - di) * (1 - dj) + m[i0 * w + j0 + 1] * (1 - di) * dj + m[(i0 + 1) * w + j0] * di * (1 - dj) + m[(i0 + 1) * w + j0 + 1] * di * dj;
62
+ }
63
+ function gimVerticalTec(grid, timeMs, latDeg, lonDeg) {
64
+ const e = grid.epochs;
65
+ if (e.length === 0 || timeMs < e[0] || timeMs > e[e.length - 1]) return null;
66
+ let i = 0;
67
+ while (i < e.length - 2 && e[i + 1] <= timeMs) i++;
68
+ const span = e[i + 1] - e[i];
69
+ const f = span > 0 ? (timeMs - e[i]) / span : 0;
70
+ const a = tecAtMap(grid, i, latDeg, lonDeg);
71
+ const b = tecAtMap(grid, i + 1, latDeg, lonDeg);
72
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
73
+ return a * (1 - f) + b * f;
74
+ }
75
+ function gimSlantIonoDelayL1(grid, latRad, lonRad, azRad, elRad, timeMs) {
76
+ if (elRad <= 0) return null;
77
+ const sinZp = R_E / (R_E + H_ION) * Math.cos(elRad);
78
+ const mapping = 1 / Math.sqrt(1 - sinZp * sinZp);
79
+ const psi = Math.PI / 2 - elRad - Math.asin(sinZp);
80
+ const latI = Math.asin(
81
+ Math.sin(latRad) * Math.cos(psi) + Math.cos(latRad) * Math.sin(psi) * Math.cos(azRad)
82
+ );
83
+ const lonI = lonRad + Math.asin(Math.sin(psi) * Math.sin(azRad) / Math.cos(latI));
84
+ const latDeg = latI * 180 / Math.PI;
85
+ const lonDeg = ((lonI * 180 / Math.PI + 540) % 360 + 360) % 360 - 180;
86
+ const vtec = gimVerticalTec(grid, timeMs, latDeg, lonDeg);
87
+ if (vtec === null) return null;
88
+ return IONO_L1_M_PER_TECU * vtec * mapping;
89
+ }
90
+
91
+ // src/positioning/lambda.ts
92
+ var LOOPMAX = 1e4;
93
+ var ROUND = (x) => Math.floor(x + 0.5);
94
+ var SGN = (x) => x <= 0 ? -1 : 1;
95
+ function ldFactor(n, Q, L, D) {
96
+ const A = Float64Array.from(Q);
97
+ for (let i = n - 1; i >= 0; i--) {
98
+ D[i] = A[i + i * n];
99
+ if (D[i] <= 0) return false;
100
+ const a = Math.sqrt(D[i]);
101
+ for (let j = 0; j <= i; j++) L[i + j * n] = A[i + j * n] / a;
102
+ for (let j = 0; j <= i - 1; j++)
103
+ for (let k = 0; k <= j; k++) A[j + k * n] -= L[i + k * n] * L[i + j * n];
104
+ for (let j = 0; j <= i; j++) L[i + j * n] /= L[i + i * n];
105
+ }
106
+ return true;
107
+ }
108
+ function gauss(n, L, Z, i, j) {
109
+ const mu = ROUND(L[i + j * n]);
110
+ if (mu !== 0) {
111
+ for (let k = i; k < n; k++) L[k + n * j] -= mu * L[k + i * n];
112
+ for (let k = 0; k < n; k++) Z[k + n * j] -= mu * Z[k + i * n];
113
+ }
114
+ }
115
+ function perm(n, L, D, j, del, Z) {
116
+ const eta = D[j] / del;
117
+ const lam = D[j + 1] * L[j + 1 + j * n] / del;
118
+ D[j] = eta * D[j + 1];
119
+ D[j + 1] = del;
120
+ for (let k = 0; k <= j - 1; k++) {
121
+ const a0 = L[j + k * n];
122
+ const a1 = L[j + 1 + k * n];
123
+ L[j + k * n] = -L[j + 1 + j * n] * a0 + a1;
124
+ L[j + 1 + k * n] = eta * a0 + lam * a1;
125
+ }
126
+ L[j + 1 + j * n] = lam;
127
+ for (let k = j + 2; k < n; k++) {
128
+ const t = L[k + j * n];
129
+ L[k + j * n] = L[k + (j + 1) * n];
130
+ L[k + (j + 1) * n] = t;
131
+ }
132
+ for (let k = 0; k < n; k++) {
133
+ const t = Z[k + j * n];
134
+ Z[k + j * n] = Z[k + (j + 1) * n];
135
+ Z[k + (j + 1) * n] = t;
136
+ }
137
+ }
138
+ function reduction(n, L, D, Z) {
139
+ let j = n - 2;
140
+ let k = n - 2;
141
+ while (j >= 0) {
142
+ if (j <= k) for (let i = j + 1; i < n; i++) gauss(n, L, Z, i, j);
143
+ const del = D[j] + L[j + 1 + j * n] * L[j + 1 + j * n] * D[j + 1];
144
+ if (del + 1e-6 < D[j + 1]) {
145
+ perm(n, L, D, j, del, Z);
146
+ k = j;
147
+ j = n - 2;
148
+ } else j--;
149
+ }
150
+ }
151
+ function search(n, m, L, D, zs, zn, s) {
152
+ let nn = 0;
153
+ let imax = 0;
154
+ let maxdist = 1e99;
155
+ const S = new Float64Array(n * n);
156
+ const dist = new Float64Array(n);
157
+ const zb = new Float64Array(n);
158
+ const z = new Float64Array(n);
159
+ const step = new Float64Array(n);
160
+ let k = n - 1;
161
+ dist[k] = 0;
162
+ zb[k] = zs[k];
163
+ z[k] = ROUND(zb[k]);
164
+ let y = zb[k] - z[k];
165
+ step[k] = SGN(y);
166
+ let c = 0;
167
+ for (; c < LOOPMAX; c++) {
168
+ const newdist = dist[k] + y * y / D[k];
169
+ if (newdist < maxdist) {
170
+ if (k !== 0) {
171
+ dist[--k] = newdist;
172
+ for (let i = 0; i <= k; i++)
173
+ S[k + i * n] = S[k + 1 + i * n] + (z[k + 1] - zb[k + 1]) * L[k + 1 + i * n];
174
+ zb[k] = zs[k] + S[k + k * n];
175
+ z[k] = ROUND(zb[k]);
176
+ y = zb[k] - z[k];
177
+ step[k] = SGN(y);
178
+ } else {
179
+ if (nn < m) {
180
+ if (nn === 0 || newdist > s[imax]) imax = nn;
181
+ for (let i = 0; i < n; i++) zn[i + nn * n] = z[i];
182
+ s[nn++] = newdist;
183
+ } else {
184
+ if (newdist < s[imax]) {
185
+ for (let i = 0; i < n; i++) zn[i + imax * n] = z[i];
186
+ s[imax] = newdist;
187
+ imax = 0;
188
+ for (let i = 0; i < m; i++) if (s[imax] < s[i]) imax = i;
189
+ }
190
+ maxdist = s[imax];
191
+ }
192
+ z[0] += step[0];
193
+ y = zb[0] - z[0];
194
+ step[0] = -step[0] - SGN(step[0]);
195
+ }
196
+ } else {
197
+ if (k === n - 1) break;
198
+ k++;
199
+ z[k] += step[k];
200
+ y = zb[k] - z[k];
201
+ step[k] = -step[k] - SGN(step[k]);
202
+ }
203
+ }
204
+ for (let i = 0; i < m - 1; i++) {
205
+ for (let j = i + 1; j < m; j++) {
206
+ if (s[i] < s[j]) continue;
207
+ const t = s[i];
208
+ s[i] = s[j];
209
+ s[j] = t;
210
+ for (let l = 0; l < n; l++) {
211
+ const u = zn[l + i * n];
212
+ zn[l + i * n] = zn[l + j * n];
213
+ zn[l + j * n] = u;
214
+ }
215
+ }
216
+ }
217
+ return c < LOOPMAX;
218
+ }
219
+ function solveZt(n, m, Z, E) {
220
+ const w = n + m;
221
+ const M = new Float64Array(n * w);
222
+ for (let r = 0; r < n; r++) {
223
+ for (let cIdx = 0; cIdx < n; cIdx++) M[r * w + cIdx] = Z[cIdx + r * n];
224
+ for (let cIdx = 0; cIdx < m; cIdx++) M[r * w + n + cIdx] = E[r + cIdx * n];
225
+ }
226
+ for (let col = 0; col < n; col++) {
227
+ let piv = col;
228
+ for (let r = col + 1; r < n; r++)
229
+ if (Math.abs(M[r * w + col]) > Math.abs(M[piv * w + col])) piv = r;
230
+ if (Math.abs(M[piv * w + col]) < 1e-12) return null;
231
+ if (piv !== col) {
232
+ for (let cIdx = col; cIdx < w; cIdx++) {
233
+ const t = M[col * w + cIdx];
234
+ M[col * w + cIdx] = M[piv * w + cIdx];
235
+ M[piv * w + cIdx] = t;
236
+ }
237
+ }
238
+ const d = M[col * w + col];
239
+ for (let cIdx = col; cIdx < w; cIdx++) M[col * w + cIdx] /= d;
240
+ for (let r = 0; r < n; r++) {
241
+ if (r === col) continue;
242
+ const f = M[r * w + col];
243
+ if (f === 0) continue;
244
+ for (let cIdx = col; cIdx < w; cIdx++)
245
+ M[r * w + cIdx] -= f * M[col * w + cIdx];
246
+ }
247
+ }
248
+ const X = new Float64Array(n * m);
249
+ for (let r = 0; r < n; r++)
250
+ for (let cIdx = 0; cIdx < m; cIdx++) X[r + cIdx * n] = M[r * w + n + cIdx];
251
+ return X;
252
+ }
253
+ function lambdaSearch(a, Q, n, m = 2) {
254
+ if (n <= 0 || m <= 0 || a.length < n || Q.length < n * n) return null;
255
+ const L = new Float64Array(n * n);
256
+ const D = new Float64Array(n);
257
+ if (!ldFactor(n, Q, L, D)) return null;
258
+ const Z = new Float64Array(n * n);
259
+ for (let i = 0; i < n; i++) Z[i + i * n] = 1;
260
+ reduction(n, L, D, Z);
261
+ const z = new Float64Array(n);
262
+ for (let j = 0; j < n; j++) {
263
+ let sum = 0;
264
+ for (let k = 0; k < n; k++) sum += Z[k + j * n] * a[k];
265
+ z[j] = sum;
266
+ }
267
+ const E = new Float64Array(n * m);
268
+ const s = new Float64Array(m);
269
+ if (!search(n, m, L, D, z, E, s)) return null;
270
+ const F = solveZt(n, m, Z, E);
271
+ if (!F) return null;
272
+ const candidates = [];
273
+ for (let c = 0; c < m; c++) {
274
+ const v = new Float64Array(n);
275
+ for (let i = 0; i < n; i++) v[i] = ROUND(F[i + c * n]);
276
+ candidates.push(v);
277
+ }
278
+ const ratio = m >= 2 && s[0] > 0 ? s[1] / s[0] : Infinity;
279
+ return { candidates, residuals: s, ratio };
280
+ }
281
+ function lambdaReduction(Q, n) {
282
+ if (n <= 0 || Q.length < n * n) return null;
283
+ const L = new Float64Array(n * n);
284
+ const D = new Float64Array(n);
285
+ if (!ldFactor(n, Q, L, D)) return null;
286
+ const Z = new Float64Array(n * n);
287
+ for (let i = 0; i < n; i++) Z[i + i * n] = 1;
288
+ reduction(n, L, D, Z);
289
+ return Z;
290
+ }
291
+
46
292
  // src/positioning/rtk.ts
47
293
  var L1_CODES = {
48
294
  G: ["1C"],
@@ -403,7 +649,13 @@ var RtkFloatEngine = class {
403
649
  maxGapMs: opts.maxGapMs ?? 1e4,
404
650
  codeGateM: opts.codeGateM ?? 30,
405
651
  slipGateM: opts.slipGateM ?? 5,
406
- updateIterations: opts.updateIterations ?? 2
652
+ updateIterations: opts.updateIterations ?? 2,
653
+ ambiguityResolution: opts.ambiguityResolution ?? "off",
654
+ ratioThreshold: opts.ratioThreshold ?? 3,
655
+ arElevationMaskDeg: opts.arElevationMaskDeg ?? 0,
656
+ glonassAr: opts.glonassAr ?? false,
657
+ partialFixing: opts.partialFixing ?? true,
658
+ minFixAmbiguities: opts.minFixAmbiguities ?? 4
407
659
  };
408
660
  }
409
661
  /** Clear all filter state (position, ambiguities, lock history). */
@@ -461,6 +713,56 @@ var RtkFloatEngine = class {
461
713
  const entry = this.amb[newRefIdx];
462
714
  entry.prn = oldRef;
463
715
  }
716
+ /**
717
+ * Try to fix the ambiguity subset `idxs` (indices into `this.amb`):
718
+ * extract the float subvector b and covariance Q_b from the filter,
719
+ * run MLAMBDA and, without touching the filter state, condition the
720
+ * position on the best integer candidate,
721
+ * xa = x − P_xb·Q_b⁻¹·(b − ǎ)
722
+ * (RTKLIB rtkpos.c `resamb_LAMBDA`). Returns null when Q_b is not
723
+ * positive definite / invertible or the search fails; ratio
724
+ * validation is the caller's job.
725
+ */
726
+ tryFix(idxs) {
727
+ const k = idxs.length;
728
+ const b = new Float64Array(k);
729
+ const Qb = new Float64Array(k * k);
730
+ const QbRows = [];
731
+ for (let i = 0; i < k; i++) {
732
+ const si = 3 + idxs[i];
733
+ b[i] = this.x[si];
734
+ const row = [];
735
+ for (let j = 0; j < k; j++) {
736
+ const sj = 3 + idxs[j];
737
+ const q = (this.P[si][sj] + this.P[sj][si]) / 2;
738
+ Qb[i + j * k] = q;
739
+ row.push(q);
740
+ }
741
+ QbRows.push(row);
742
+ }
743
+ const res = lambdaSearch(b, Qb, k, 2);
744
+ if (!res) return null;
745
+ const best = res.candidates[0];
746
+ const QbInv = matInv(QbRows);
747
+ if (!QbInv) return null;
748
+ const db = [];
749
+ for (let i = 0; i < k; i++) db.push(b[i] - best[i]);
750
+ const position = [
751
+ this.x[0],
752
+ this.x[1],
753
+ this.x[2]
754
+ ];
755
+ for (let r = 0; r < 3; r++) {
756
+ let dx = 0;
757
+ for (let i = 0; i < k; i++) {
758
+ let yi = 0;
759
+ for (let j = 0; j < k; j++) yi += QbInv[i][j] * db[j];
760
+ dx += this.P[r][3 + idxs[i]] * yi;
761
+ }
762
+ position[r] -= dx;
763
+ }
764
+ return { ratio: res.ratio, position, intAmb: [...best] };
765
+ }
464
766
  /**
465
767
  * Process one synchronized epoch (same nominal `timeMs` for both
466
768
  * receivers). Returns null until a first position can be
@@ -711,6 +1013,47 @@ var RtkFloatEngine = class {
711
1013
  Pnew[j][i] = s;
712
1014
  }
713
1015
  this.P = Pnew;
1016
+ let status = rows.some(
1017
+ (r) => r.kind === "phase"
1018
+ ) ? "float" : "dgnss";
1019
+ let ratio;
1020
+ let nFixed;
1021
+ let fixedPos = null;
1022
+ let fixedAmbiguities;
1023
+ if (o.ambiguityResolution === "instant") {
1024
+ const cands = [];
1025
+ for (const row of rows) {
1026
+ if (row.kind !== "phase") continue;
1027
+ if (row.g.prn[0] === "R" && !o.glonassAr) continue;
1028
+ if (row.g.elB < o.arElevationMaskDeg * Math.PI / 180) continue;
1029
+ cands.push({ idx: row.ambIdx, el: row.g.elB });
1030
+ }
1031
+ cands.sort((a, b) => b.el - a.el);
1032
+ const floor = Math.max(
1033
+ o.minFixAmbiguities,
1034
+ Math.ceil(cands.length / 2),
1035
+ 1
1036
+ );
1037
+ while (cands.length >= floor) {
1038
+ const fix = this.tryFix(cands.map((c) => c.idx));
1039
+ if (!fix) break;
1040
+ const capped = Math.min(fix.ratio, 999.9);
1041
+ ratio ??= capped;
1042
+ if (fix.ratio >= o.ratioThreshold) {
1043
+ ratio = capped;
1044
+ status = "fixed";
1045
+ nFixed = cands.length;
1046
+ fixedPos = fix.position;
1047
+ fixedAmbiguities = {};
1048
+ cands.forEach((c, i) => {
1049
+ fixedAmbiguities[this.amb[c.idx].prn] = fix.intAmb[i];
1050
+ });
1051
+ break;
1052
+ }
1053
+ if (!o.partialFixing) break;
1054
+ cands.pop();
1055
+ }
1056
+ }
714
1057
  for (const g of geom) {
715
1058
  if (g.cpR === null || g.cpB === null) continue;
716
1059
  this.track.set(g.prn, { lockR: g.lockR, lockB: g.lockB, lastMs: timeMs });
@@ -724,14 +1067,22 @@ var RtkFloatEngine = class {
724
1067
  const nSats = new Set(rows.flatMap((r) => [r.g.prn, r.ref.prn])).size;
725
1068
  return {
726
1069
  timeMs,
727
- position: [this.x[0], this.x[1], this.x[2]],
1070
+ position: fixedPos ?? [this.x[0], this.x[1], this.x[2]],
728
1071
  floatBaseline: [
729
1072
  this.x[0] - this.basePos[0],
730
1073
  this.x[1] - this.basePos[1],
731
1074
  this.x[2] - this.basePos[2]
732
1075
  ],
1076
+ status,
733
1077
  nSats,
734
- ratio: void 0,
1078
+ ratio,
1079
+ nFixed,
1080
+ fixedBaseline: fixedPos ? [
1081
+ fixedPos[0] - this.basePos[0],
1082
+ fixedPos[1] - this.basePos[1],
1083
+ fixedPos[2] - this.basePos[2]
1084
+ ] : void 0,
1085
+ fixedAmbiguities,
735
1086
  sigmas: [
736
1087
  Math.sqrt(Math.max(this.P[0][0], 0)),
737
1088
  Math.sqrt(Math.max(this.P[1][1], 0)),
@@ -778,10 +1129,17 @@ var PRIMARY_FREQ_HZ = {
778
1129
  C: 1561098e3,
779
1130
  R: 1602e6
780
1131
  };
781
- var F_L1 = 157542e4;
782
- function tropoDelay2(elevationRad) {
783
- const sinEl = Math.sin(elevationRad);
784
- return 2.47 / (sinEl + 0.0121);
1132
+ var F_L12 = 157542e4;
1133
+ function tropoDelay2(elevationRad, latRad, heightM) {
1134
+ if (elevationRad <= 0) return 0;
1135
+ const h = heightM < 0 ? 0 : heightM > 1e4 ? 1e4 : heightM;
1136
+ const humi = 0.7;
1137
+ const pres = 1013.25 * Math.pow(1 - 22557e-9 * h, 5.2568);
1138
+ const temp = 15 - 65e-4 * h + 273.16;
1139
+ const e = 6.108 * humi * Math.exp((17.15 * temp - 4684) / (temp - 38.45));
1140
+ const zhd = 22768e-7 * pres / (1 - 266e-5 * Math.cos(2 * latRad) - 28e-5 * (h / 1e3));
1141
+ const zwd = 2277e-6 * (1255 / temp + 0.05) * e;
1142
+ return (zhd + zwd) / Math.sin(elevationRad);
785
1143
  }
786
1144
  function sagnac2(pos, travelTimeS) {
787
1145
  const theta = OMEGA_E * travelTimeS;
@@ -814,7 +1172,8 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
814
1172
  maxIterations = 15,
815
1173
  convergenceM = 1e-4,
816
1174
  tgd = true,
817
- iono
1175
+ iono,
1176
+ gim
818
1177
  } = opts;
819
1178
  const gpsTow = (timeMs - GPS_EPOCH_MS_SPP) / 1e3 % 604800;
820
1179
  const all = [...pseudoranges.keys()].filter((p) => ephemerides.has(p));
@@ -841,7 +1200,7 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
841
1200
  let rows = 0;
842
1201
  for (const k of Object.keys(residuals2)) delete residuals2[k];
843
1202
  const positionSaneIter = x2 * x2 + y2 * y2 + z2 * z2 > 1e12;
844
- const [rxLat, rxLon] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0];
1203
+ const [rxLat, rxLon, rxHgt] = positionSaneIter ? ecefToGeodetic(x2, y2, z2) : [0, 0, 0];
845
1204
  for (const prn of prns) {
846
1205
  const psr = pseudoranges.get(prn);
847
1206
  const eph = ephemerides.get(prn);
@@ -871,11 +1230,18 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
871
1230
  const sinEl = Math.max(Math.sin(elev), 0.1);
872
1231
  weight = sinEl * sinEl;
873
1232
  }
874
- const tropo = troposphere && positionSane ? tropoDelay2(elev) : 0;
1233
+ const tropo = troposphere && positionSane ? tropoDelay2(elev, rxLat, rxHgt) : 0;
875
1234
  let ionoM = 0;
876
- if (iono && positionSane) {
877
- const f = PRIMARY_FREQ_HZ[sys] ?? F_L1;
878
- ionoM = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow) * (F_L1 / f * (F_L1 / f));
1235
+ if ((gim || iono) && positionSane) {
1236
+ let l1M = null;
1237
+ if (gim) l1M = gimSlantIonoDelayL1(gim, rxLat, rxLon, azim, elev, timeMs);
1238
+ if (l1M === null && iono) {
1239
+ l1M = C_LIGHT * klobucharDelay(iono, rxLat, rxLon, azim, elev, gpsTow);
1240
+ }
1241
+ if (l1M !== null) {
1242
+ const f = PRIMARY_FREQ_HZ[sys] ?? F_L12;
1243
+ ionoM = l1M * (F_L12 / f * (F_L12 / f));
1244
+ }
879
1245
  }
880
1246
  const predicted = rho + clk - C_LIGHT * dts + tropo + ionoM;
881
1247
  const v = psr - predicted;
@@ -957,6 +1323,11 @@ function solveSpp(pseudoranges, ephemerides, timeMs, opts = {}) {
957
1323
 
958
1324
  export {
959
1325
  klobucharDelay,
1326
+ IONO_L1_M_PER_TECU,
1327
+ gimVerticalTec,
1328
+ gimSlantIonoDelayL1,
1329
+ lambdaSearch,
1330
+ lambdaReduction,
960
1331
  toRtkEpoch,
961
1332
  solveDgnss,
962
1333
  RtkFloatEngine,