// 随机扰动函数 function randomPerturbation(scale = 1e-7) { return (Math.random() * 2 - 1) * scale } // Haversine 公式计算两点距离(米) function haversine(lat1, lon1, lat2, lon2) { const R = 6371000 const toRad = (deg) => deg * Math.PI / 180 const phi1 = toRad(lat1) const phi2 = toRad(lat2) const dphi = toRad(lat2 - lat1) const dlambda = toRad(lon2 - lon1) const a = Math.sin(dphi / 2) ** 2 + Math.cos(phi1) * Math.cos(phi2) * Math.sin(dlambda / 2) ** 2 return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) } /** * 根据原路径时间,重新生成新的时间路径数据 */ function getPathData(pathlist, runEndTime, useTime) { const startTime = (runEndTime - useTime) * 1000 const oldStartTime = parseInt(pathlist[0].d.split(' ')[0]) const newPathlist = pathlist.map((item, i) => { const newItem = { ...item } const [oldTimeStr, suffix] = item.d.split(' ', 2) const oldTime = parseInt(oldTimeStr) if (i === 0) { newItem.d = `${startTime} ${suffix}` } else { const newTime = startTime + oldTime - oldStartTime newItem.d = `${newTime} ${suffix}` newItem.a += randomPerturbation() newItem.o += randomPerturbation() } return newItem }) console.log(newPathlist) return newPathlist } /** * 选择打卡点 */ function selectCheckpoints(path, checkpoints, runLogNum, pointUpdateDistance, logMaxDistance, runEndTime, pathTime) { const results = [] let totalDistance = 0 let lastCheckpointDistance = 0 // 筛选有效打卡点 const filteredCheckpoints = checkpoints .filter(cp => cp.is_del === "0" && cp.is_online === 1 && cp.ctrl_status === "1") // 打乱顺序 for (let i = filteredCheckpoints.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) [filteredCheckpoints[i], filteredCheckpoints[j]] = [filteredCheckpoints[j], filteredCheckpoints[i]] } const usedCheckpoints = new Set() for (const p of path) { const parts = p.d.split(' ') const tsMs = parseInt(parts[0]) const stepDistance = parseFloat(parts[1].split('_')[0]) || 0 totalDistance += stepDistance for (const cp of filteredCheckpoints.slice(0, -1)) { if (usedCheckpoints.has(cp.id)) continue const [lat, lon] = cp.jingwei.split(',').map(Number) const dist = haversine(p.a, p.o, lat, lon) if (dist < logMaxDistance && (totalDistance - lastCheckpointDistance) > 200) { console.log(`选中打卡点 ${cp.id} ${cp.address},距离:${dist}`) results.push({ point_id: cp.id, distance: +(totalDistance / 1000).toFixed(2), longitude: p.o, longtitude: p.o, // 保留原字段 latitude: p.a, address: cp.address, jingwei: cp.jingwei.split(','), time: Math.floor(tsMs / 1000) }) usedCheckpoints.add(cp.id) lastCheckpointDistance = totalDistance break } } } if (results.length >= runLogNum) { // 随机抽取 runLogNum 个打卡点 const indices = [] while (indices.length < runLogNum) { const idx = Math.floor(Math.random() * results.length) if (!indices.includes(idx)) indices.push(idx) } indices.sort((a, b) => a - b) const selectedResults = indices.map(i => results[i]) const n = selectedResults.length let d = 0 for (let i = 0; i < n; i++) { if (selectedResults[i].time >= runEndTime - 5) { console.log("打卡点时间异常,重新分配时间") for (let j = 0; j < n; j++) { selectedResults[j].time = Math.floor(runEndTime - (pathTime / n) * (n - j)) selectedResults[j].distance = d + 0.3 * (j + 1) } break } } console.log(`选中打卡点:${JSON.stringify(selectedResults)}`) return selectedResults } else { console.log(`选中打卡点数量不足:${JSON.stringify(results)}`) return false } } /** * 根据距离和用时生成步频数据 */ function generateCadence(distanceKm, usedTime) { const paceSec = usedTime / distanceKm const paceMin = paceSec / 60 let spmRange if (paceMin >= 8) spmRange = [85, 110] else if (paceMin >= 5) spmRange = [150, 170] else spmRange = [170, 190] const minutes = Math.ceil(usedTime / 60) const cadenceList = [] for (let i = 0; i < minutes; i++) { let spm = Math.floor(Math.random() * (spmRange[1] - spmRange[0] + 1) + spmRange[0]) if (i === minutes - 1) { const lastDuration = usedTime - (minutes - 1) * 60 spm = Math.round(spm * (lastDuration / 60)) } cadenceList.push(spm) } const totalSteps = cadenceList.reduce((a, b) => a + b, 0) return { cadence_list: cadenceList, total_steps: totalSteps } } module.exports = { getPathData, selectCheckpoints, generateCadence }