generateGyrFromPath.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. "use strict";
  2. /**
  3. * 传入 path_test.json 同类路径点数组,返回模拟真机上传的 IMU 采样数组(与小程序一致:仅最后一段;间隔约 60–70ms;各轴 float32 量化)。
  4. * 单参数、无文件读写。
  5. */
  6. module.exports = function generateGyrFromPath(pathRows) {
  7. const R0 = 6371000;
  8. const D2R = Math.PI / 180;
  9. function parseRow(row) {
  10. if (!row || typeof row.d !== "string") return null;
  11. const [ts] = row.d.trim().split(/\s+/);
  12. const t = parseInt(ts, 10);
  13. if (!Number.isFinite(t)) return null;
  14. const lat = row.a;
  15. const lng = row.o;
  16. if (typeof lat !== "number" || typeof lng !== "number") return null;
  17. const speed = typeof row.s === "number" ? row.s : 0;
  18. return { t, lat, lng, speed };
  19. }
  20. function sortPath(points) {
  21. const list = points.map(parseRow).filter(Boolean);
  22. list.sort((a, b) => a.t - b.t);
  23. if (list.length < 2) return list;
  24. const d = [list[0]];
  25. for (let i = 1; i < list.length; i++) {
  26. if (list[i].t !== list[i - 1].t) d.push(list[i]);
  27. }
  28. return d;
  29. }
  30. function hav(lat1, lng1, lat2, lng2) {
  31. const φ1 = lat1 * D2R;
  32. const φ2 = lat2 * D2R;
  33. const dφ = (lat2 - lat1) * D2R;
  34. const dλ = (lng2 - lng1) * D2R;
  35. const s =
  36. Math.sin(dφ / 2) ** 2 +
  37. Math.cos(φ1) * Math.cos(φ2) * Math.sin(dλ / 2) ** 2;
  38. return 2 * R0 * Math.asin(Math.min(1, Math.sqrt(s)));
  39. }
  40. function bear(lat1, lng1, lat2, lng2) {
  41. const φ1 = lat1 * D2R;
  42. const φ2 = lat2 * D2R;
  43. const dλ = (lng2 - lng1) * D2R;
  44. const y = Math.sin(dλ) * Math.cos(φ2);
  45. const x =
  46. Math.cos(φ1) * Math.sin(φ2) -
  47. Math.sin(φ1) * Math.cos(φ2) * Math.cos(dλ);
  48. return Math.atan2(y, x);
  49. }
  50. function unwrap(prev, next) {
  51. let d = next - prev;
  52. while (d > Math.PI) d -= 2 * Math.PI;
  53. while (d < -Math.PI) d += 2 * Math.PI;
  54. return prev + d;
  55. }
  56. function lerpPath(path, t, key) {
  57. if (t <= path[0].t) return path[0][key];
  58. const L = path[path.length - 1];
  59. if (t >= L.t) return L[key];
  60. let i = 0;
  61. while (i < path.length - 1 && path[i + 1].t < t) i++;
  62. const p0 = path[i];
  63. const p1 = path[i + 1];
  64. const f = (t - p0.t) / (p1.t - p0.t);
  65. return p0[key] + f * (p1[key] - p0[key]);
  66. }
  67. function ll(path, t) {
  68. return { lat: lerpPath(path, t, "lat"), lng: lerpPath(path, t, "lng") };
  69. }
  70. function segmentStartMs(t0, tEnd) {
  71. const sec = Math.max(0, tEnd - t0) / 1000;
  72. return t0 + Math.floor(sec / 60) * 60 * 1000;
  73. }
  74. /** 与真机常见的 60–72ms 间隔起伏一致(含 70→60 这类相邻差) */
  75. function gapMs(i) {
  76. const g = [70, 60, 65, 70, 60, 62, 68, 60, 72, 61];
  77. return g[i % g.length];
  78. }
  79. function f32(x) {
  80. return Math.fround(x);
  81. }
  82. function rngFactory(path) {
  83. let h = 2166136261 >>> 0;
  84. for (let i = 0; i < path.length; i++) {
  85. const p = path[i];
  86. h ^= p.t >>> 0;
  87. h = Math.imul(h, 16777619);
  88. const x = ((p.lat * 1e6) ^ (p.lng * 1e6)) | 0;
  89. h ^= x;
  90. h = Math.imul(h, 16777619);
  91. }
  92. let s = h || 0x9e3779b9;
  93. return function () {
  94. s = (s + 0x6d2b79f5) >>> 0;
  95. let t = Math.imul(s ^ (s >>> 15), 1 | s);
  96. t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
  97. return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  98. };
  99. }
  100. if (!Array.isArray(pathRows) || pathRows.length < 2) return [];
  101. const path = sortPath(pathRows);
  102. if (path.length < 2) return [];
  103. const rand = rngFactory(path);
  104. const t0 = path[0].t;
  105. const t1 = path[path.length - 1].t;
  106. const tSeg = segmentStartMs(t0, t1);
  107. const rngHalf = 35;
  108. const pA0 = ll(path, tSeg);
  109. const pA1 = ll(path, Math.min(t1, tSeg + Math.max(rngHalf, 1)));
  110. let prevU = bear(pA0.lat, pA0.lng, pA1.lat, pA1.lng);
  111. let prevV = lerpPath(path, tSeg, "speed");
  112. const out = [];
  113. let tCur = tSeg;
  114. let idx = 0;
  115. let lastT = tCur - gapMs(0);
  116. /** 贴近真机样例的加计零偏量级(持机姿态下重力分摊 + 跑动微扰,非 9.8 单列) */
  117. const baseAx = -1.08;
  118. const baseAy = 0.9;
  119. const baseAz = -0.06;
  120. const amp = 0.36;
  121. // 一阶低通,避免数值跳变过于“脚本化”
  122. const lp = 0.82; // 越大越平滑
  123. let lpAx = baseAx;
  124. let lpAy = baseAy;
  125. let lpAz = baseAz;
  126. let lpGx = 0.43;
  127. let lpGy = 0.42;
  128. let lpGz = -1.78;
  129. function clamp(x, lo, hi) {
  130. return x < lo ? lo : x > hi ? hi : x;
  131. }
  132. while (tCur <= t1) {
  133. const dtMs = tCur - lastT;
  134. const dtSec = Math.max(dtMs, 1) / 1000;
  135. const pb = ll(path, Math.max(t0, tCur - rngHalf));
  136. const pf = ll(path, Math.min(t1, tCur + rngHalf));
  137. const vPath = lerpPath(path, tCur, "speed");
  138. const dist = hav(pb.lat, pb.lng, pf.lat, pf.lng);
  139. const vTrack =
  140. (2 * rngHalf) > 0 ? dist / ((2 * rngHalf) / 1000) : vPath;
  141. const v = 0.62 * vPath + 0.38 * Math.max(0, vTrack);
  142. const br = bear(pb.lat, pb.lng, pf.lat, pf.lng);
  143. const uW = unwrap(prevU, br);
  144. const yaw = (uW - prevU) / dtSec;
  145. const aT = (v - prevV) / dtSec;
  146. const aL = v * yaw;
  147. prevU = uW;
  148. prevV = v;
  149. lastT = tCur;
  150. const co = Math.cos(0.31);
  151. const si = Math.sin(0.31);
  152. const jx = aL * co - aT * si;
  153. const jy = aL * si + aT * co;
  154. const runSec = (tCur - t0) / 1000;
  155. const ph = 2 * Math.PI * 1.82 * runSec;
  156. let ax =
  157. baseAx +
  158. amp * jx +
  159. 0.2 * Math.sin(ph) +
  160. 0.1 * (rand() - 0.5);
  161. let ay =
  162. baseAy +
  163. amp * jy +
  164. 0.17 * Math.cos(ph * 0.97) +
  165. 0.1 * (rand() - 0.5);
  166. let az =
  167. baseAz +
  168. 0.28 * Math.sin(2 * ph) +
  169. 0.18 * Math.cos(ph) +
  170. 0.08 * (rand() - 0.5);
  171. let gx = 0.43 + 0.12 * Math.cos(ph) + 0.07 * (rand() - 0.5);
  172. let gy = 0.42 + 0.26 * Math.sin(ph * 1.07) + 0.1 * (rand() - 0.5);
  173. let gz = -1.78 + 0.8 * yaw + 0.14 * (rand() - 0.5);
  174. // 去除极端尖峰注入,改为限幅 + 平滑,更贴近正常跑步统计特征
  175. gx = clamp(gx, -2.2, 2.2);
  176. gy = clamp(gy, -2.2, 2.2);
  177. gz = clamp(gz, -3.2, 1.2);
  178. lpAx = lp * lpAx + (1 - lp) * ax;
  179. lpAy = lp * lpAy + (1 - lp) * ay;
  180. lpAz = lp * lpAz + (1 - lp) * az;
  181. lpGx = lp * lpGx + (1 - lp) * gx;
  182. lpGy = lp * lpGy + (1 - lp) * gy;
  183. lpGz = lp * lpGz + (1 - lp) * gz;
  184. out.push({
  185. t: tCur,
  186. ax: f32(lpAx),
  187. ay: f32(lpAy),
  188. az: f32(lpAz),
  189. gx: f32(lpGx),
  190. gy: f32(lpGy),
  191. gz: f32(lpGz),
  192. });
  193. tCur += gapMs(idx++);
  194. }
  195. return out;
  196. };