Path.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // 随机扰动函数
  2. function randomPerturbation(scale = 1e-7) {
  3. return (Math.random() * 2 - 1) * scale
  4. }
  5. // Haversine 公式计算两点距离(米)
  6. function haversine(lat1, lon1, lat2, lon2) {
  7. const R = 6371000
  8. const toRad = (deg) => deg * Math.PI / 180
  9. const phi1 = toRad(lat1)
  10. const phi2 = toRad(lat2)
  11. const dphi = toRad(lat2 - lat1)
  12. const dlambda = toRad(lon2 - lon1)
  13. const a = Math.sin(dphi / 2) ** 2 + Math.cos(phi1) * Math.cos(phi2) * Math.sin(dlambda / 2) ** 2
  14. return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  15. }
  16. /**
  17. * 根据原路径时间,重新生成新的时间路径数据
  18. */
  19. function getPathData(pathlist, runEndTime, useTime) {
  20. const startTime = (runEndTime - useTime) * 1000
  21. const oldStartTime = parseInt(pathlist[0].d.split(' ')[0])
  22. const newPathlist = pathlist.map((item, i) => {
  23. const newItem = { ...item }
  24. const [oldTimeStr, suffix] = item.d.split(' ', 2)
  25. const oldTime = parseInt(oldTimeStr)
  26. if (i === 0) {
  27. newItem.d = `${startTime} ${suffix}`
  28. } else {
  29. const newTime = startTime + oldTime - oldStartTime
  30. newItem.d = `${newTime} ${suffix}`
  31. newItem.a += randomPerturbation()
  32. newItem.o += randomPerturbation()
  33. }
  34. return newItem
  35. })
  36. return newPathlist
  37. }
  38. /**
  39. * 选择打卡点
  40. */
  41. function selectCheckpoints(path, checkpoints, runLogNum, pointUpdateDistance, logMaxDistance, runEndTime, pathTime) {
  42. const results = []
  43. let totalDistance = 0
  44. let lastCheckpointDistance = 0
  45. // 筛选有效打卡点
  46. const filteredCheckpoints = checkpoints
  47. .filter(cp => cp.is_del === "0" && cp.is_online === 1 && cp.ctrl_status === "1")
  48. // 打乱顺序
  49. for (let i = filteredCheckpoints.length - 1; i > 0; i--) {
  50. const j = Math.floor(Math.random() * (i + 1))
  51. // 避免解构交换在部分运行环境下触发 TDZ 相关异常
  52. const temp = filteredCheckpoints[i]
  53. filteredCheckpoints[i] = filteredCheckpoints[j]
  54. filteredCheckpoints[j] = temp
  55. }
  56. const usedCheckpoints = new Set()
  57. for (const p of path) {
  58. const parts = p.d.split(' ')
  59. const tsMs = parseInt(parts[0])
  60. const stepDistance = parseFloat(parts[1].split('_')[0]) || 0
  61. totalDistance += stepDistance
  62. for (const cp of filteredCheckpoints.slice(0, -1)) {
  63. if (usedCheckpoints.has(cp.id)) continue
  64. const [lat, lon] = cp.jingwei.split(',').map(Number)
  65. const dist = haversine(p.a, p.o, lat, lon)
  66. if (dist < logMaxDistance && (totalDistance - lastCheckpointDistance) > 200) {
  67. console.log(`选中打卡点 ${cp.id} ${cp.address},距离:${dist}`)
  68. results.push({
  69. point_id: cp.id,
  70. distance: +(totalDistance / 1000).toFixed(2),
  71. longitude: p.o,
  72. longtitude: p.o, // 保留原字段
  73. latitude: p.a,
  74. address: cp.address,
  75. jingwei: cp.jingwei.split(','),
  76. time: Math.floor(tsMs / 1000)
  77. })
  78. usedCheckpoints.add(cp.id)
  79. lastCheckpointDistance = totalDistance
  80. break
  81. }
  82. }
  83. }
  84. if (results.length >= runLogNum) {
  85. // 随机抽取 runLogNum 个打卡点
  86. const indices = []
  87. while (indices.length < runLogNum) {
  88. const idx = Math.floor(Math.random() * results.length)
  89. if (!indices.includes(idx)) indices.push(idx)
  90. }
  91. indices.sort((a, b) => a - b)
  92. const selectedResults = indices.map(i => results[i])
  93. const n = selectedResults.length
  94. let d = 0
  95. for (let i = 0; i < n; i++) {
  96. if (selectedResults[i].time >= runEndTime - 5) {
  97. console.log("打卡点时间异常,重新分配时间")
  98. for (let j = 0; j < n; j++) {
  99. selectedResults[j].time = Math.floor(runEndTime - (pathTime / n) * (n - j))
  100. selectedResults[j].distance = d + 0.3 * (j + 1)
  101. }
  102. break
  103. }
  104. }
  105. console.log(`选中打卡点:${JSON.stringify(selectedResults)}`)
  106. return selectedResults
  107. } else {
  108. console.log(`选中打卡点数量不足:${JSON.stringify(results)}`)
  109. return false
  110. }
  111. }
  112. /**
  113. * 根据距离和用时生成步频数据
  114. */
  115. function generateCadence(distanceKm, usedTime) {
  116. const paceSec = usedTime / distanceKm
  117. const paceMin = paceSec / 60
  118. let spmRange
  119. if (paceMin >= 8) spmRange = [85, 110]
  120. else if (paceMin >= 5) spmRange = [150, 170]
  121. else spmRange = [170, 190]
  122. const minutes = Math.ceil(usedTime / 60)
  123. const cadenceList = []
  124. for (let i = 0; i < minutes; i++) {
  125. let spm = Math.floor(Math.random() * (spmRange[1] - spmRange[0] + 1) + spmRange[0])
  126. if (i === minutes - 1) {
  127. const lastDuration = usedTime - (minutes - 1) * 60
  128. spm = Math.round(spm * (lastDuration / 60))
  129. }
  130. cadenceList.push(spm)
  131. }
  132. const totalSteps = cadenceList.reduce((a, b) => a + b, 0)
  133. return {
  134. cadence_list: cadenceList,
  135. total_steps: totalSteps
  136. }
  137. }
  138. module.exports = {
  139. getPathData,
  140. selectCheckpoints,
  141. generateCadence
  142. }