commit 07cb1304d241874ee862cbdb2741e1fa8821f587 Author: pythagodzilla Date: Thu Mar 19 17:57:03 2026 +0800 init diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b6b1ecf --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..69eb76a --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + Coord + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..0dd4b35 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 0000000..fcb9cff --- /dev/null +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6f29fee --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..783182d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/refactored-likeRunner.iml b/.idea/refactored-likeRunner.iml new file mode 100644 index 0000000..8aacdb5 --- /dev/null +++ b/.idea/refactored-likeRunner.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/00_START_HERE.txt b/00_START_HERE.txt new file mode 100644 index 0000000..74fe6a6 --- /dev/null +++ b/00_START_HERE.txt @@ -0,0 +1,152 @@ +╔════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ ✅ 轨迹生成程序整理完成! ║ +║ ║ +╚════════════════════════════════════════════════════════════════════════════╝ + +📍 新项目位置 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + /Users/pythagodzilla/Projects/Kotlin/refactored-likeRunner/ + +📦 项目内容 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✓ 4份文档(指南 + 对比 + 快速开始) + ✓ 9个代码文件(规范命名,充分注释) + ✓ 完整的4步数据流架构 + ✓ 为未来扩展预留的接口和位置 + +📄 核心文档(按阅读顺序) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 1️⃣ QUICK_START.md - 5分钟快速入门(👈 从这里开始!) + 2️⃣ README.md - 完整项目说明 + 3️⃣ DATA_FLOW_GUIDE.md - 数据流详细参考(最重要!) + 4️⃣ BEFORE_AFTER.md - 改进前后对比 + 5️⃣ STRUCTURE.txt - 文件结构导航 + +💻 核心代码文件 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 【第1步:物理计算】 + → core/physics/RunnerPhysics.kt + + 【第2步:路径插值】 + → core/trajectory/PathInterpolator.kt (Haversine逻辑完全保留) + + 【第3步:噪声处理】 + → noise/NoiseProcessor.kt (协调器) + → noise/GpsNoiseEngine.kt (GPS噪声 + 预留其他引擎) + + 【第4步:结果组装】 + → core/TrajectorySimulator.kt (主协调器 - 从这里理解流程!) + + 【数据模型】 + → model/Coordinate.kt + → model/MotionState.kt + + 【程序入口】 + → Main.kt (完整使用示例) + +🎯 改进亮点 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✅ 清晰的4步数据流 + 时间 → 物理 → 路径 → 噪声 → 结果 + + ✅ 对象复用(提高效率) + 一次初始化,多次调用,状态保留 + + ✅ 命名规范化 + pathLinearlizer → PathInterpolator + haversine → calculateHaversineDistance + getCoordinate → simulate + + ✅ 数据类型完整 + MetaCoordinate → RawCoordinate → NoisyCoordinate → MotionState + + ✅ 逻辑完全保留 + Haversine算法、GPS噪声模型 - 代码逻辑100%保持 + + ✅ 充分的注释 + 伪代码、数据流说明、预留位置清晰标记 + + ✅ 易于扩展 + 预留位置标记:VelocityNoiseEngine, AccelerationNoiseEngine + 支持添加高程、多模式、传感器噪声等 + +📚 使用方式速查 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 我想... → 看... + ───────────────────────────────────────── + 快速理解项目 QUICK_START.md + 理解4步数据流 DATA_FLOW_GUIDE.md + 看代码怎么用 Main.kt + 理解主协调流程 core/TrajectorySimulator.kt + 改物理计算 core/physics/RunnerPhysics.kt + 改路径算法 core/trajectory/PathInterpolator.kt + 加新噪声 noise/GpsNoiseEngine.kt + 改输出数据结构 model/MotionState.kt + 知道预留在哪 GpsNoiseEngine.kt & NoiseProcessor.kt + +🔍 核心概念 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 时间输入 + ↓ + [步骤1] RunnerPhysics.calculate(time) + → PhysicsState(distance, velocity, acceleration) + ↓ + [步骤2] PathInterpolator.interpolate(distance) + → RawCoordinate(lon, lat) + ↓ + [步骤3] NoiseProcessor.applyNoise(rawCoord, physicsState) + → NoisyCoordinate(lon±noise, lat±noise, error) + ↓ + [步骤4] 组装 + → MotionState (完整运动状态) + +✨ 预留扩展点 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + [ ] VelocityNoiseEngine (速度噪声) + [ ] AccelerationNoiseEngine (加速度噪声) + [ ] ElevationInterpolator (高程信息) + [ ] 多运动模式支持 (跑步/散步切换) + [ ] GPX/KML导出 (轨迹文件输出) + [ ] 传感器噪声模拟 (加速度计、陀螺仪) + +⚠️ 保留说明 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 原项目(/Users/pythagodzilla/Projects/Kotlin/likeRunner/) + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✅ 完全保留,不做任何改动 + ✅ 新项目和原项目完全独立 + ✅ 可同时运行两个项目 + +🎓 推荐学习流程 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ⏱️ 5分钟 → QUICK_START.md 快速了解 + ⏱️ 10分钟 → README.md 深入了解 + ⏱️ 15分钟 → Main.kt 看使用方式 + ⏱️ 20分钟 → DATA_FLOW_GUIDE.md 理解数据流 + ⏱️ 30分钟 → core/TrajectorySimulator.kt 看完整流程 + ⏱️ 60分钟 → 浏览所有代码,理解全貌 + +✅ 完成检查清单 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✓ 创建独立项目(不影响原项目) + ✓ 规范了命名(pathLinearlizer → PathInterpolator等) + ✓ 清晰了流程(4步数据流,每步职责明确) + ✓ 保留了逻辑(Haversine、GPS噪声 - 代码100%保留) + ✓ 加入注释(伪代码、说明、预留标记) + ✓ 预留了位置(VelocityNoise、AccelerationNoise等) + ✓ 提供文档(快速开始、流程指南、对比说明) + ✓ 展示示例(Main.kt 完整使用示例) + +╔════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ 🎉 项目已准备好!现在你可以: ║ +║ ║ +║ 1️⃣ 阅读 QUICK_START.md 快速上手 ║ +║ 2️⃣ 根据 DATA_FLOW_GUIDE.md 理解数据流 ║ +║ 3️⃣ 在需要修改时参考相应文件 ║ +║ 4️⃣ 按照预留位置标记添加新功能 ║ +║ ║ +║ 祝你使用愉快! 🚀 ║ +║ ║ +╚════════════════════════════════════════════════════════════════════════════╝ diff --git a/BEFORE_AFTER.md b/BEFORE_AFTER.md new file mode 100644 index 0000000..0cbbe11 --- /dev/null +++ b/BEFORE_AFTER.md @@ -0,0 +1,361 @@ +# 代码重构前后对比 + +## 整体改进 + +### 原项目问题 +``` +❌ 每次调用都重新创建对象(浪费资源,状态丢失) +❌ 流程不清晰,难以扩展 +❌ 职责混乱,不知道在哪里插入新功能 +❌ 数据模型不完整(缺少中间状态的定义) +❌ 无法直观看出数据在各步骤间如何流动 +``` + +### 改进后的特点 +``` +✅ 对象复用(一次初始化,重复使用) +✅ 清晰的4步数据流(每步职责明确) +✅ 易于扩展(预留位置清楚标记) +✅ 完整的数据模型(每步都有输出定义) +✅ 充分的注释和伪代码(易于理解和修改) +``` + +## 具体对比 + +### 对比1:主程序入口 + +#### 原代码 (Main.kt) +```kotlin +fun main() { +} // 空的,什么都没有 +``` + +#### 新代码 (Main.kt) +```kotlin +fun main() { + // 1. 加载样本轨迹 + val route = sampleRoute + + // 2. 创建模拟器(只创建一次) + val simulator = TrajectorySimulator(route) + + // 3. 配置参数 + simulator.setPhysicsParams(...) + simulator.setNoiseParams(...) + + // 4. 模拟循环(逐时间步调用) + for (time in 0.0..duration step 0.1) { + val motionState = simulator.simulate(time) + println(motionState) + } +} +``` + +**改进**:清晰展示了如何使用,每个步骤有注释说明 + +--- + +### 对比2:主处理流程 + +#### 原代码 (Runner.getCoordinate) +```kotlin +class Runner(val route: List) { + fun getCoordinate(time: Double): Coordinate { + val path = PathLinearize(route) // ❌ 每次都创建 + val physicEngine = RunnerPhysic() // ❌ 每次都创建 + val noiseEngine = PathNoiseEngine() // ❌ 每次都创建 + + val distance = physicEngine.getDistance(time) + val coordinate = path.getPointDistance(distance) + val finalCoordinate = noiseEngine.applyNoise(coordinate.lat, coordinate.lon, physicEngine) + + return finalCoordinate + } +} +``` + +**问题**: +- 每次都重新创建对象,浪费资源 +- NoiseEngine 中的漂移状态会丢失 +- 流程混乱,无法清晰看出数据如何流转 +- 返回值类型混乱(Coordinate vs 其他) + +#### 新代码 (TrajectorySimulator.simulate) +```kotlin +class TrajectorySimulator(route: List) { + // 一次初始化,重复使用 + private val pathInterpolator: PathInterpolator = PathInterpolator(route) + private val physicsEngine: RunnerPhysics = RunnerPhysics() + private val noiseProcessor: NoiseProcessor = NoiseProcessor() + + fun simulate(time: Double): MotionState { + // ════════════════════════════════════════════ + // 步骤1:计算物理状态 + // ════════════════════════════════════════════ + val physicsState = physicsEngine.calculate(time) // ✅ 返回 PhysicsState + + // ════════════════════════════════════════════ + // 步骤2:路径插值 + // ════════════════════════════════════════════ + val rawCoordinate = pathInterpolator.interpolate(physicsState.distance) // ✅ 返回 RawCoordinate + + // ════════════════════════════════════════════ + // 步骤3:添加噪声 + // ════════════════════════════════════════════ + val noisyCoordinate = noiseProcessor.applyNoise(rawCoordinate, physicsState) // ✅ 返回 NoisyCoordinate + + // ════════════════════════════════════════════ + // 步骤4:组装最终状态 + // ════════════════════════════════════════════ + return MotionState(...) // ✅ 返回完整的 MotionState + } +} +``` + +**改进**: +- 对象复用,提高效率 +- 4步流程清晰,每步职责明确 +- 每步都有明确的输入输出类型 +- 易于理解和调试 + +--- + +### 对比3:坐标数据模型 + +#### 原代码 +```kotlin +// 只有两个坐标类,中间状态混乱 +data class Coordinate(val lon: Double, val lat: Double, val time: Double) +data class MetaCoordinate(val lon: Double, val lat: Double) +``` + +#### 新代码 +```kotlin +// 完整的数据流定义 +data class MetaCoordinate(val lon: Double, val lat: Double) // 原始轨迹点 + +data class RawCoordinate(val lon: Double, val lat: Double) // 无噪声插值点 + +data class NoisyCoordinate( + val lon: Double, + val lat: Double, + val gpsError: Double // 记录噪声大小 +) + +data class Coordinate(val lon: Double, val lat: Double, val time: Double) // 带时间的坐标 +``` + +**改进**: +- 每个数据流阶段都有明确的数据类型 +- 便于调试和理解数据的变换过程 +- 预留了噪声信息的记录 + +--- + +### 对比4:物理引擎 + +#### 原代码 +```kotlin +class RunnerPhysic { + fun getDistance(time: Double): Double {} // ❌ 未实现,空的 +} +``` + +#### 新代码 +```kotlin +class RunnerPhysics( + private val totalDistance: Double = 0.0, + private val duration: Double = 1000.0, + private val maxVelocity: Double = 4.0 +) { + /** + * 计算指定时间的物理状态 + * + * @param time 当前时间(秒) + * @return 包含距离、速度、加速度的物理状态 + */ + fun calculate(time: Double): PhysicsState { + // 伪代码说明: + // 1. 计算距离 + // 2. 计算速度 + // 3. 计算加速度 + + val distance = getDistance(time) + val velocity = 0.0 // TODO: 实现 + val acceleration = 0.0 // TODO: 实现 + + return PhysicsState(time, distance, velocity, acceleration) + } + + fun getDistance(time: Double): Double { + // 伪代码说明 + return 0.0 // TODO: 实现 + } +} +``` + +**改进**: +- 返回类型改为 PhysicsState(包含完整的物理信息) +- 添加了伪代码说明实现思路 +- 添加了TODO标记,清楚地标出需要实现的地方 +- 参数化了初始值(便于配置) + +--- + +### 对比5:路径插值器 + +#### 原代码 (pathLinearlizer.kt) +```kotlin +class PathLinearize(val nodes: List) { + private val segments = nodes.windowed(2).map { (a, b) -> + val distance = haversine(a, b) + Triple(a, b, distance) + } + + fun haversine(node1: MetaCoordinate, node2: MetaCoordinate): Double { + // ... 完整的Haversine算法(逻辑保留) + } + + fun getPointDistance(totalDistance: Double): MetaCoordinate { + // ... 插值逻辑 + } +} +``` + +#### 新代码 (PathInterpolator.kt) +```kotlin +class PathInterpolator(val nodes: List) { + private val segments = nodes.windowed(2).map { (a, b) -> + val distance = calculateHaversineDistance(a, b) // ✅ 改名:更清晰 + Triple(a, b, distance) + } + + fun interpolate(totalDistance: Double): RawCoordinate { // ✅ 改名:更直观 + // ... 同样的插值逻辑,完全保留 + } + + private fun calculateHaversineDistance(node1: MetaCoordinate, node2: MetaCoordinate): Double { + // ✅ Haversine逻辑完全保留,只改了方法名 + // 和原代码一模一样 + } +} +``` + +**改进**: +- 类名改为 PathInterpolator(更准确) +- 方法名改为 `interpolate` 和 `calculateHaversineDistance`(更清晰) +- 返回类型改为 RawCoordinate(更明确) +- **核心逻辑完全保留**,只改了命名 + +--- + +### 对比6:噪声处理 + +#### 原代码 +```kotlin +class PathNoiseEngine( + private val gpsErrorStdDev: Double = 0.00002, + private val driftWeight: Double = 0.3 +) { + private val random = Random() + private var driftLat = 0.0 + private var driftLon = 0.0 + + fun applyNoise(rawLat: Double, rawLon: Double, physic: RunnerPhysic): Coordinate { + // ... 白噪声和漂移逻辑(完全保留) + return Coordinate(rawLon+whiteNoiseLon+driftLon, rawLat+whiteNoiseLat+driftLat, time = Double) + // ❌ time = Double 是个错误,应该是实际时间值 + } +} +``` + +#### 新代码 (GpsNoiseEngine.kt) +```kotlin +class GpsNoiseEngine( + private val gpsErrorStdDev: Double = 0.00002, + private val driftWeight: Double = 0.3 +) { + /** + * 应用GPS噪声到坐标 + * + * 伪代码: + * 1. 生成白噪声 + * 2. 生成漂移 + * 3. 合并噪声 + */ + fun applyNoise( + rawLon: Double, + rawLat: Double, + physicsState: PhysicsState + ): Triple { // ✅ 更清晰的返回类型 + + // 第1步:白噪声(逻辑完全保留) + val whiteNoiseLat = random.nextGaussian() * gpsErrorStdDev + val whiteNoiseLon = random.nextGaussian() * gpsErrorStdDev + + // 第2步:漂移(逻辑完全保留) + driftLat = driftLat * 0.8 + random.nextGaussian() * gpsErrorStdDev * driftWeight + driftLon = driftLon * 0.8 + random.nextGaussian() * gpsErrorStdDev * driftWeight + + // 第3步:合并(逻辑完全保留) + val finalLon = rawLon + whiteNoiseLon + driftLon + val finalLat = rawLat + whiteNoiseLat + driftLat + + val totalError = sqrt((whiteNoiseLon + driftLon).pow(2) + (whiteNoiseLat + driftLat).pow(2)) + + return Triple(finalLon, finalLat, totalError) // ✅ 返回噪声大小便于记录 + } +} +``` + +新增 NoiseProcessor(协调多个噪声引擎): +```kotlin +class NoiseProcessor { + private val gpsNoiseEngine = GpsNoiseEngine() + // 预留位置: + // private val velocityNoiseEngine = VelocityNoiseEngine() + // private val accelerationNoiseEngine = AccelerationNoiseEngine() + + fun applyNoise(rawCoord: RawCoordinate, physicsState: PhysicsState): NoisyCoordinate { + val (noisyLon, noisyLat, gpsError) = gpsNoiseEngine.applyNoise( + rawCoord.lon, rawCoord.lat, physicsState + ) + + // ✅ 预留位置: + // val velocityNoise = velocityNoiseEngine.applyNoise(physicsState.velocity) + // val accelNoise = accelerationNoiseEngine.applyNoise(physicsState.acceleration) + + return NoisyCoordinate(noisyLon, noisyLat, gpsError) + } +} +``` + +**改进**: +- 返回值更明确(Triple 包含噪声大小) +- 创建 NoiseProcessor 协调多个噪声引擎 +- **GPS噪声算法逻辑完全保留** +- 清晰标记了预留位置供未来扩展 + +--- + +## 总结 + +| 方面 | 原代码 | 新代码 | +|------|--------|--------| +| **项目独立性** | 修改原项目 | 新建独立项目,原项目不动 | +| **对象管理** | 每次重建 | 一次初始化,重复使用 | +| **流程清晰度** | 混乱 | 4步清晰流程 | +| **数据模型** | 不完整 | 完整的中间状态定义 | +| **命名规范** | 不规范 | 规范统一 | +| **注释文档** | 无 | 充分的伪代码和说明 | +| **扩展预留** | 无 | 清晰标记的预留位置 | +| **核心算法** | 保留 | **完全保留** | + +--- + +## 如何使用新项目 + +1. **快速上手**:看 Main.kt(完整使用示例) +2. **理解流程**:看 DATA_FLOW_GUIDE.md(数据流参考) +3. **要修改代码**:看 README.md(各文件职责说明) +4. **要加新功能**:看 GpsNoiseEngine.kt(预留位置已标记) diff --git a/DATA_FLOW_GUIDE.md b/DATA_FLOW_GUIDE.md new file mode 100644 index 0000000..772450b --- /dev/null +++ b/DATA_FLOW_GUIDE.md @@ -0,0 +1,216 @@ +# 数据流快速参考指南 + +## 一句话理解:4步处理流程 + +``` +时间 → 物理 → 路径 → 噪声 → 结果 +``` + +## 详细数据流 + +### 第1步:物理计算 (Physics) + +``` +输入:double time(秒) + +过程: + RunnerPhysics.calculate(time) + ↓ + 计算距离、速度、加速度 + +输出:PhysicsState + { + time: Double, // 时间 + distance: Double, // 行进距离(米) + velocity: Double, // 速度(m/s) + acceleration: Double // 加速度(m/s²) + } + +文件位置:src/main/kotlin/core/physics/RunnerPhysics.kt +``` + +### 第2步:路径插值 (Trajectory) + +``` +输入:double distance(米)来自第1步 + +过程: + PathInterpolator.interpolate(distance) + ↓ + 在轨迹上查找对应坐标 + (使用Haversine算法计算路段距离) + (线性插值得到准确坐标) + +输出:RawCoordinate(无噪声) + { + lon: Double, // 经度 + lat: Double // 纬度 + } + +文件位置:src/main/kotlin/core/trajectory/PathInterpolator.kt +``` + +### 第3步:噪声处理 (Noise) + +``` +输入: + - RawCoordinate(第2步的输出) + - PhysicsState(第1步的输出) + +过程: + NoiseProcessor.applyNoise() + ↓ + ├─ GpsNoiseEngine.applyNoise(lon, lat) + │ - 白噪声:每个时刻的随机偏差 + │ - 漂移:GPS偏向某侧的持续特性 + │ - 返回:(lon±noise, lat±noise, error_magnitude) + │ + ├─ [预留] VelocityNoiseEngine.applyNoise(velocity) + │ - 速度波动噪声 + │ + └─ [预留] AccelerationNoiseEngine.applyNoise(acceleration) + - 加速度波动噪声 + +输出:NoisyCoordinate + { + lon: Double, // 经度(带噪声) + lat: Double, // 纬度(带噪声) + gpsError: Double // 噪声大小(米) + } + +文件位置: + - 协调器:src/main/kotlin/noise/NoiseProcessor.kt + - GPS噪声:src/main/kotlin/noise/GpsNoiseEngine.kt +``` + +### 第4步:结果组装 (Assembly) + +``` +输入: + - PhysicsState(第1步) + - NoisyCoordinate(第3步) + +过程: + 组装所有数据到单一对象 + +输出:MotionState(最终结果) + { + time: Double, // 时间(秒) + distance: Double, // 距离(米) + velocity: Double, // 速度(m/s) + acceleration: Double, // 加速度(m/s²) + latitude: Double, // 纬度(带噪声) + longitude: Double, // 经度(带噪声) + gpsError: Double, // GPS噪声(米) + velocityNoise: Double, // 速度噪声(预留) + accelNoise: Double // 加速度噪声(预留) + } + +文件位置:src/main/kotlin/model/MotionState.kt +``` + +## 在哪里插入新功能? + +### 想添加速度噪声? +``` +位置:第3步 - NoiseProcessor.applyNoise() +方法: + 1. 在 GpsNoiseEngine.kt 中实现 VelocityNoiseEngine + 2. 在 NoiseProcessor 中调用: + val velNoise = velocityNoiseEngine.applyNoise(physicsState.velocity) + 3. 在 MotionState 中记录结果:velocityNoise = velNoise +``` + +### 想添加高程信息? +``` +位置:第2步和第3步之间 +方法: + 1. 创建 src/main/kotlin/core/trajectory/ElevationInterpolator.kt + 2. 在 TrajectorySimulator.simulate() 中: + 步骤2.5: val elevation = elevationInterpolator.interpolate(distance) + 3. 扩展 MotionState 添加 elevation 字段 +``` + +### 想改变运动模式(跑步/散步)? +``` +位置:第1步 - RunnerPhysics.calculate() +方法: + 1. 在 RunnerPhysics 中添加运动模式参数 + 2. 根据速度判断当前模式 + 3. 不同模式使用不同的速度曲线 +``` + +### 想添加传感器噪声(加速度计)? +``` +位置:第3步 - NoiseProcessor.applyNoise() +方法: + 1. 在 GpsNoiseEngine.kt 中实现 AccelerationNoiseEngine + 2. 在 NoiseProcessor 中调用 + 3. 在 MotionState 中记录结果 +``` + +## 重要的类和文件 + +| 类名 | 文件位置 | 职责 | +|------|---------|------| +| TrajectorySimulator | core/TrajectorySimulator.kt | 主协调器,管理4步流程 | +| RunnerPhysics | core/physics/RunnerPhysics.kt | 第1步:物理计算 | +| PathInterpolator | core/trajectory/PathInterpolator.kt | 第2步:路径插值 | +| NoiseProcessor | noise/NoiseProcessor.kt | 第3步:噪声处理 | +| GpsNoiseEngine | noise/GpsNoiseEngine.kt | GPS噪声实现 | +| MotionState | model/MotionState.kt | 最终输出数据结构 | + +## 调用顺序(不要改) + +``` +main() + ↓ +TrajectorySimulator(route) // 初始化 + ↓ +for (time in 0..duration) + ↓ + simulator.simulate(time) + ↓ + [第1步] physicsEngine.calculate(time) → PhysicsState + ↓ + [第2步] pathInterpolator.interpolate(distance) → RawCoordinate + ↓ + [第3步] noiseProcessor.applyNoise(rawCoord, physicsState) → NoisyCoordinate + ↓ + [第4步] 组装 MotionState + ↓ + return MotionState +``` + +## 核心算法(逻辑保持原样) + +### Haversine 公式(计算球面距离) +位置:PathInterpolator.calculateHaversineDistance() +- 输入:两个坐标点 +- 输出:球面距离(米) +- **逻辑完全保留** + +### GPS 噪声模型 +位置:GpsNoiseEngine.applyNoise() +- 白噪声:高斯分布 +- 漂移:0.8 衰减 + 随机累积 +- **逻辑完全保留** + +## 常见问题 + +**Q: 我该改哪个文件?** +A: 看你要做什么: +- 改物理计算 → RunnerPhysics.kt +- 改路径算法 → PathInterpolator.kt(但不要改Haversine) +- 加新噪声 → GpsNoiseEngine.kt 或新建文件 +- 改输出格式 → MotionState.kt + +**Q: 怎么知道在哪一步插入新功能?** +A: 问自己"我的新功能需要什么输入": +- 需要时间 → 第1步前 +- 需要距离 → 第2步前 +- 需要坐标 → 第3步前 +- 需要物理参数 → 第3步中 + +**Q: 原有代码会不会被破坏?** +A: 不会。原始项目(likeRunner)完全保留,新项目(refactored-likeRunner)独立放置。 diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..d133c4b --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,172 @@ +# 快速开始指南 + +## 项目已准备好! + +新项目位置:`/Users/pythagodzilla/Projects/Kotlin/refactored-likeRunner/` + +## 文件清单 + +### 📌 必读文档(3份) +1. **README.md** - 项目完整说明 +2. **DATA_FLOW_GUIDE.md** - 数据流快速参考(**最重要**) +3. **BEFORE_AFTER.md** - 改进前后对比 + +### 📌 项目代码(9个文件) + +**数据模型层** +- `model/Coordinate.kt` - 4种坐标类型 +- `model/MotionState.kt` - 2种状态类型 + +**核心模拟层** +- `core/TrajectorySimulator.kt` - 主协调器(**从这里开始理解**) +- `core/physics/RunnerPhysics.kt` - 物理计算 +- `core/trajectory/PathInterpolator.kt` - 路径插值(Haversine完全保留) + +**噪声处理层** +- `noise/NoiseProcessor.kt` - 噪声协调器 +- `noise/GpsNoiseEngine.kt` - GPS噪声 + 预留其他引擎 + +**程序入口** +- `Main.kt` - 使用示例 + +**数据资源** +- `resources/data/SampleRoute.kt` - 样本轨迹 + +## 5分钟快速理解 + +### 问题1:程序是干什么的? +✅ 模拟真实的跑步轨迹,包含: +- 物理计算(距离、速度、加速度) +- 路径插值(根据距离找坐标) +- GPS噪声模拟(白噪声 + 漂移) + +### 问题2:怎么用? +✅ 看 `Main.kt` 就行: +```kotlin +val simulator = TrajectorySimulator(sampleRoute) +for (time in 0.0..duration step 0.1) { + val motionState = simulator.simulate(time) + println(motionState) +} +``` + +### 问题3:数据流是什么? +✅ 4步简单流程: +``` +时间 → [物理] → 距离 → [路径] → 坐标 → [噪声] → 最终坐标 → [组装] → 输出 +``` + +详见 `DATA_FLOW_GUIDE.md` + +### 问题4:我想加新功能怎么办? +✅ 取决于是什么功能: +- 改物理:改 `RunnerPhysics.kt` +- 加噪声:改 `GpsNoiseEngine.kt` +- 改坐标:改 `Coordinate.kt` +- 改路径:改 `PathInterpolator.kt`(只改命名,算法完全保留) + +详见 `DATA_FLOW_GUIDE.md` 的"在哪里插入新功能" + +### 问题5:原有代码呢? +✅ 完全保留在 `/Users/pythagodzilla/Projects/Kotlin/likeRunner/` + +新项目不会影响原项目。 + +## 文件阅读顺序 + +**第一次接触项目(推荐)** +``` +1. 本文件(QUICK_START.md) ← 你在这里 +2. README.md ← 了解项目概况 +3. Main.kt ← 看代码怎么用 +4. DATA_FLOW_GUIDE.md ← 深入理解数据流 +5. core/TrajectorySimulator.kt ← 看主协调器 +``` + +**要修改代码时** +``` +1. DATA_FLOW_GUIDE.md ← 找到目标步骤 +2. 对应的代码文件 ← 修改代码 +3. BEFORE_AFTER.md ← 参考改进示例 +``` + +**要添加新功能时** +``` +1. DATA_FLOW_GUIDE.md ← "在哪里插入新功能"部分 +2. GpsNoiseEngine.kt ← 参考预留位置的代码 +3. 创建新文件或修改现有文件 +``` + +## 核心概念速查 + +### 4步数据流 +``` +步骤1 [物理] : time → PhysicsState +步骤2 [路径] : distance → RawCoordinate +步骤3 [噪声] : RawCoordinate → NoisyCoordinate +步骤4 [组装] : 所有 → MotionState +``` + +### 关键类 +``` +TrajectorySimulator ← 主协调器,管理4步 +RunnerPhysics ← 步骤1 +PathInterpolator ← 步骤2 +NoiseProcessor ← 步骤3 +GpsNoiseEngine ← 步骤3的实现 +``` + +### 关键方法 +``` +TrajectorySimulator.simulate(time) ← 主入口 +RunnerPhysics.calculate(time) ← 物理计算 +PathInterpolator.interpolate(distance) ← 路径查询 +NoiseProcessor.applyNoise(...) ← 添加噪声 +``` + +## 代码规范 + +✅ **保留原样** +- Haversine算法逻辑 +- GPS噪声模型逻辑 +- 所有数学计算 + +✅ **改进** +- 方法命名(`haversine` → `calculateHaversineDistance`) +- 类名规范(`PathLinearize` → `PathInterpolator`) +- 数据类型清晰(每步都有明确的输出类型) +- 充分注释(伪代码、说明、预留标记) + +✅ **新增** +- NoiseProcessor 协调器 +- 中间数据类型(RawCoordinate, PhysicsState等) +- 详细注释和文档 + +## 常见问题 + +**Q: Haversine算法被改了吗?** +A: 没有。只改了方法名(`haversine` → `calculateHaversineDistance`),逻辑完全一样。 + +**Q: GPS噪声算法被改了吗?** +A: 没有。白噪声和漂移的计算逻辑完全保留,返回值改为Triple以包含噪声大小。 + +**Q: 我能直接跑这个代码吗?** +A: 大部分可以(Main.kt展示了完整流程),但物理计算部分还需要实现(已标记TODO)。 + +**Q: 怎么才能在项目中看到数据流?** +A: 打开 `core/TrajectorySimulator.kt`,`simulate()` 方法就是完整的4步流程。 + +**Q: 预留位置在哪里?** +A: 在 `GpsNoiseEngine.kt` 和 `NoiseProcessor.kt` 中,都用注释清晰标记了。 + +**Q: 能同时运行原项目和新项目吗?** +A: 可以,它们完全独立。新项目在 `refactored-likeRunner/` 文件夹里。 + +## 下一步 + +- [ ] 阅读 README.md 了解完整项目 +- [ ] 在 Main.kt 中看程序怎么用 +- [ ] 阅读 DATA_FLOW_GUIDE.md 深入理解数据流 +- [ ] 根据需要修改或添加功能 + +祝你使用愉快!有问题随时查看相关文档。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c0fda1 --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ +# TrajectoryRunner - 轨迹生成器 + +## 概述 + +这是一个模拟真实跑步轨迹的 Kotlin 程序,用于生成带有物理特性和GPS噪声的运动轨迹数据。 + +## 核心架构 + +整个程序遵循清晰的 4 步数据流: + +``` +时间 t + ↓ +[步骤1: 物理计算] → 距离、速度、加速度 + ↓ +[步骤2: 路径插值] → 原始坐标(lon, lat) + ↓ +[步骤3: 噪声处理] → 添加GPS/速度/加速度噪声 + ↓ +[步骤4: 结果组装] → MotionState(完整运动状态) + ↓ +输出: 模拟的轨迹点 +``` + +## 目录结构 + +``` +src/main/kotlin/ +├── Main.kt # 程序入口 +├── model/ # 数据模型 +│ ├── Coordinate.kt # 坐标类型定义 +│ └── MotionState.kt # 运动状态定义 +├── core/ # 核心模拟逻辑 +│ ├── TrajectorySimulator.kt # 主协调器 +│ ├── physics/ # 物理引擎 +│ │ └── RunnerPhysics.kt # 物理计算 +│ └── trajectory/ # 轨迹处理 +│ └── PathInterpolator.kt # 路径插值(含Haversine算法) +├── noise/ # 噪声引擎 +│ ├── NoiseProcessor.kt # 噪声协调器 +│ ├── GpsNoiseEngine.kt # GPS噪声(已实现) +│ │ # + VelocityNoiseEngine(预留) +│ │ # + AccelerationNoiseEngine(预留) +└── resources/data/ # 样本数据 + └── SampleRoute.kt # 样本轨迹数据 + +``` + +## 关键类说明 + +### TrajectorySimulator(轨迹模拟器) +- **职责**:管理整个数据流,协调各个引擎 +- **初始化**:创建 PathInterpolator, RunnerPhysics, NoiseProcessor +- **核心方法**:`simulate(time)` - 执行完整的4步处理 + +### PathInterpolator(路径插值器) +- **职责**:根据距离在轨迹上查找坐标 +- **算法**:Haversine(计算球面距离) +- **核心方法**:`interpolate(distance)` - 线性插值 + +### RunnerPhysics(物理引擎) +- **职责**:计算物理参数(距离、速度、加速度) +- **输入**:时间 +- **输出**:PhysicsState + +### NoiseProcessor(噪声处理器) +- **职责**:管理并应用各种噪声 +- **已实现**:GpsNoiseEngine(GPS信号误差 + 漂移) +- **预留**:VelocityNoiseEngine, AccelerationNoiseEngine + +## 数据模型 + +### MetaCoordinate(原始坐标) +```kotlin +data class MetaCoordinate(val lon: Double, val lat: Double) +``` +轨迹的基础路径点。 + +### PhysicsState(物理状态) +```kotlin +data class PhysicsState( + val time: Double, + val distance: Double, + val velocity: Double, + val acceleration: Double +) +``` +某一时刻的物理参数。 + +### MotionState(运动状态) +```kotlin +data class MotionState( + val time: Double, + val distance: Double, + val velocity: Double, + val acceleration: Double, + val latitude: Double, // 带GPS噪声 + val longitude: Double, // 带GPS噪声 + val gpsError: Double, + val velocityNoise: Double, // 预留 + val accelNoise: Double // 预留 +) +``` +最终的综合运动状态。 + +## 添加新功能的方法 + +### 例1:添加速度噪声 +在 `GpsNoiseEngine.kt` 中,有预留的 `VelocityNoiseEngine` 类框架,只需在 `NoiseProcessor.applyNoise()` 中调用: +```kotlin +val velocityNoise = velocityNoiseEngine.applyNoise(physicsState.velocity) +``` + +### 例2:添加高程信息 +在 `PathInterpolator.interpolate()` 返回后,创建 `ElevationInterpolator`,在步骤2和步骤3之间插入: +```kotlin +// 步骤2.5:插值高程 +val elevation = elevationInterpolator.interpolate(physicsState.distance) +``` + +### 例3:支持不同运动模式 +在 `RunnerPhysics.calculate()` 中增加运动模式判断,不同模式使用不同的速度曲线。 + +## 代码设计原则 + +1. **单一职责**:每个类只做一件事 +2. **清晰的数据流**:4步处理,输入输出明确 +3. **易于扩展**:预留位置和接口,支持未来添加新功能 +4. **逻辑完整**:核心算法逻辑保持原有 +5. **充分注释**:伪代码和数据流说明帮助理解 + +## 使用示例 + +```kotlin +fun main() { + // 创建模拟器 + val simulator = TrajectorySimulator(sampleRoute) + + // 配置参数 + simulator.setPhysicsParams(5000.0, 1800.0, 4.0) + simulator.setNoiseParams(0.00002, 0.3) + + // 模拟运动 + for (time in 0.0..1800.0 step 0.1) { + val motionState = simulator.simulate(time) + println(motionState) + } +} +``` + +## 预留扩展点 + +- [ ] 完整的 RunnerPhysics 物理模型实现 +- [ ] VelocityNoiseEngine 速度噪声引擎 +- [ ] AccelerationNoiseEngine 加速度噪声引擎 +- [ ] 高程插值 (ElevationInterpolator) +- [ ] GPX/KML 导出功能 +- [ ] 不同运动模式支持 diff --git a/STRUCTURE.txt b/STRUCTURE.txt new file mode 100644 index 0000000..160fbf7 --- /dev/null +++ b/STRUCTURE.txt @@ -0,0 +1,116 @@ +═══════════════════════════════════════════════════════════════════════════ + 重构项目文件结构说明 +═══════════════════════════════════════════════════════════════════════════ + +📁 refactored-likeRunner/ +│ +├─ 📄 README.md 【必读】完整项目说明 +├─ 📄 DATA_FLOW_GUIDE.md 【必读】数据流快速参考 +├─ 📄 STRUCTURE.txt 本文件 +│ +└─ src/main/kotlin/ + │ + ├─ 📄 Main.kt 【程序入口】展示如何使用 + │ + ├─ model/ 【数据模型层】 + │ ├─ Coordinate.kt - MetaCoordinate, Coordinate, RawCoordinate, NoisyCoordinate + │ └─ MotionState.kt - PhysicsState, MotionState + │ + ├─ core/ 【核心模拟层】 + │ │ + │ ├─ 📄 TrajectorySimulator.kt 【主协调器】4步数据流的协调器 + │ │ - 初始化各个引擎 + │ │ - simulate() 执行4步处理 + │ │ + │ ├─ physics/ 【第1步:物理计算】 + │ │ └─ RunnerPhysics.kt - 计算距离、速度、加速度 + │ │ + │ └─ trajectory/ 【第2步:路径插值】 + │ └─ PathInterpolator.kt - 线性插值 (Haversine算法完全保留) + │ + ├─ noise/ 【第3步:噪声处理】 + │ │ + │ ├─ NoiseProcessor.kt - 噪声协调器,管理多个引擎 + │ │ + │ └─ GpsNoiseEngine.kt - GPS噪声引擎(白噪声+漂移) + │ - 预留: VelocityNoiseEngine + │ - 预留: AccelerationNoiseEngine + │ + └─ resources/data/ 【数据资源】 + └─ SampleRoute.kt - 样本轨迹数据 + +═══════════════════════════════════════════════════════════════════════════ + 4步数据流位置 +═══════════════════════════════════════════════════════════════════════════ + +【第1步】物理计算 → RunnerPhysics.kt +┌──────────────────────────────────────┐ +│ 输入: time (秒) │ +│ 处理: 计算物理参数 │ +│ 输出: PhysicsState │ +│ - distance (距离) │ +│ - velocity (速度) │ +│ - acceleration (加速度) │ +└──────────────────────────────────────┘ + +【第2步】路径插值 → PathInterpolator.kt +┌──────────────────────────────────────┐ +│ 输入: distance (来自第1步) │ +│ 处理: 在轨迹上查找坐标 │ +│ 输出: RawCoordinate │ +│ - lon, lat (无噪声) │ +└──────────────────────────────────────┘ + +【第3步】噪声处理 → NoiseProcessor.kt + GpsNoiseEngine.kt +┌──────────────────────────────────────┐ +│ 输入: RawCoordinate + PhysicsState │ +│ 处理: 添加各种噪声 │ +│ 输出: NoisyCoordinate │ +│ - lon, lat (带噪声) │ +│ - gpsError (噪声大小) │ +└──────────────────────────────────────┘ + +【第4步】结果组装 → TrajectorySimulator.kt +┌──────────────────────────────────────┐ +│ 输入: 所有前置步骤的输出 │ +│ 处理: 组装到单一对象 │ +│ 输出: MotionState │ +│ (完整的运动状态,可直接使用) │ +└──────────────────────────────────────┘ + +═══════════════════════════════════════════════════════════════════════════ + 核心特性 +═══════════════════════════════════════════════════════════════════════════ + +✅ 清晰的数据流 + 每个步骤职责明确,输入输出清晰 + +✅ 易于扩展 + 预留了位置供添加新的噪声引擎、物理模型等 + +✅ 逻辑保留 + 原有算法(Haversine、GPS噪声模型)逻辑完全保持 + +✅ 充分注释 + 每个关键类都有详细的伪代码和说明 + +✅ 独立项目 + 新项目完全独立,不影响原有的 likeRunner 项目 + +═══════════════════════════════════════════════════════════════════════════ + 快速导航 +═══════════════════════════════════════════════════════════════════════════ + +我想... 去看... +───────────────────────────────────────────────────────────── +了解整个项目 → README.md +理解数据流 → DATA_FLOW_GUIDE.md +看代码怎么用 → Main.kt +改物理计算 → core/physics/RunnerPhysics.kt +改路径算法 → core/trajectory/PathInterpolator.kt +加新噪声 → noise/GpsNoiseEngine.kt +改坐标数据结构 → model/Coordinate.kt +看最终输出数据 → model/MotionState.kt +怎么协调各步骤 → core/TrajectorySimulator.kt + +═══════════════════════════════════════════════════════════════════════════ diff --git a/out/production/refactored-likeRunner/META-INF/refactored-likeRunner.kotlin_module b/out/production/refactored-likeRunner/META-INF/refactored-likeRunner.kotlin_module new file mode 100644 index 0000000..01538e1 Binary files /dev/null and b/out/production/refactored-likeRunner/META-INF/refactored-likeRunner.kotlin_module differ diff --git a/out/production/refactored-likeRunner/MainKt.class b/out/production/refactored-likeRunner/MainKt.class new file mode 100644 index 0000000..6f481bc Binary files /dev/null and b/out/production/refactored-likeRunner/MainKt.class differ diff --git a/out/production/refactored-likeRunner/core/TrajectorySimulator.class b/out/production/refactored-likeRunner/core/TrajectorySimulator.class new file mode 100644 index 0000000..47fd0bd Binary files /dev/null and b/out/production/refactored-likeRunner/core/TrajectorySimulator.class differ diff --git a/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig$WhenMappings.class b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig$WhenMappings.class new file mode 100644 index 0000000..42c642f Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig$WhenMappings.class differ diff --git a/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig.class b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig.class new file mode 100644 index 0000000..2fd4c33 Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig.class differ diff --git a/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine$WhenMappings.class b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine$WhenMappings.class new file mode 100644 index 0000000..7b5898e Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine$WhenMappings.class differ diff --git a/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine.class b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine.class new file mode 100644 index 0000000..782b5f4 Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine.class differ diff --git a/out/production/refactored-likeRunner/core/physics/HumanMotionMode.class b/out/production/refactored-likeRunner/core/physics/HumanMotionMode.class new file mode 100644 index 0000000..e28dc3f Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/HumanMotionMode.class differ diff --git a/out/production/refactored-likeRunner/core/physics/RunnerPhysics.class b/out/production/refactored-likeRunner/core/physics/RunnerPhysics.class new file mode 100644 index 0000000..6998f0e Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/RunnerPhysics.class differ diff --git a/out/production/refactored-likeRunner/core/physics/SpeedSegment.class b/out/production/refactored-likeRunner/core/physics/SpeedSegment.class new file mode 100644 index 0000000..16c09fb Binary files /dev/null and b/out/production/refactored-likeRunner/core/physics/SpeedSegment.class differ diff --git a/out/production/refactored-likeRunner/core/trajectory/PathInterpolator.class b/out/production/refactored-likeRunner/core/trajectory/PathInterpolator.class new file mode 100644 index 0000000..0d22951 Binary files /dev/null and b/out/production/refactored-likeRunner/core/trajectory/PathInterpolator.class differ diff --git a/out/production/refactored-likeRunner/model/Coordinate.class b/out/production/refactored-likeRunner/model/Coordinate.class new file mode 100644 index 0000000..3a0401b Binary files /dev/null and b/out/production/refactored-likeRunner/model/Coordinate.class differ diff --git a/out/production/refactored-likeRunner/model/MetaCoordinate.class b/out/production/refactored-likeRunner/model/MetaCoordinate.class new file mode 100644 index 0000000..c58a654 Binary files /dev/null and b/out/production/refactored-likeRunner/model/MetaCoordinate.class differ diff --git a/out/production/refactored-likeRunner/model/MotionState.class b/out/production/refactored-likeRunner/model/MotionState.class new file mode 100644 index 0000000..1f174b6 Binary files /dev/null and b/out/production/refactored-likeRunner/model/MotionState.class differ diff --git a/out/production/refactored-likeRunner/model/NoisyCoordinate.class b/out/production/refactored-likeRunner/model/NoisyCoordinate.class new file mode 100644 index 0000000..cdc5222 Binary files /dev/null and b/out/production/refactored-likeRunner/model/NoisyCoordinate.class differ diff --git a/out/production/refactored-likeRunner/model/PhysicsState.class b/out/production/refactored-likeRunner/model/PhysicsState.class new file mode 100644 index 0000000..79b3e9f Binary files /dev/null and b/out/production/refactored-likeRunner/model/PhysicsState.class differ diff --git a/out/production/refactored-likeRunner/model/RawCoordinate.class b/out/production/refactored-likeRunner/model/RawCoordinate.class new file mode 100644 index 0000000..ac740e5 Binary files /dev/null and b/out/production/refactored-likeRunner/model/RawCoordinate.class differ diff --git a/out/production/refactored-likeRunner/noise/AccelerationNoiseEngine.class b/out/production/refactored-likeRunner/noise/AccelerationNoiseEngine.class new file mode 100644 index 0000000..cf871cb Binary files /dev/null and b/out/production/refactored-likeRunner/noise/AccelerationNoiseEngine.class differ diff --git a/out/production/refactored-likeRunner/noise/GpsNoiseEngine.class b/out/production/refactored-likeRunner/noise/GpsNoiseEngine.class new file mode 100644 index 0000000..98690d1 Binary files /dev/null and b/out/production/refactored-likeRunner/noise/GpsNoiseEngine.class differ diff --git a/out/production/refactored-likeRunner/noise/NoiseEngine.class b/out/production/refactored-likeRunner/noise/NoiseEngine.class new file mode 100644 index 0000000..83000f3 Binary files /dev/null and b/out/production/refactored-likeRunner/noise/NoiseEngine.class differ diff --git a/out/production/refactored-likeRunner/noise/NoiseProcessor.class b/out/production/refactored-likeRunner/noise/NoiseProcessor.class new file mode 100644 index 0000000..518fe81 Binary files /dev/null and b/out/production/refactored-likeRunner/noise/NoiseProcessor.class differ diff --git a/out/production/refactored-likeRunner/noise/VelocityNoiseEngine.class b/out/production/refactored-likeRunner/noise/VelocityNoiseEngine.class new file mode 100644 index 0000000..75608c6 Binary files /dev/null and b/out/production/refactored-likeRunner/noise/VelocityNoiseEngine.class differ diff --git a/out/production/refactored-likeRunner/resources/data/SampleRouteKt.class b/out/production/refactored-likeRunner/resources/data/SampleRouteKt.class new file mode 100644 index 0000000..f57aafd Binary files /dev/null and b/out/production/refactored-likeRunner/resources/data/SampleRouteKt.class differ diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..47c019c --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,91 @@ +import core.TrajectorySimulator +import resources.data.sampleRoute + +/** + * 主程序入口 + * + * 演示如何使用 TrajectorySimulator 生成轨迹数据 + * + * 完整流程: + * 1. 加载样本轨迹 + * 2. 创建模拟器实例 + * 3. 配置参数 + * 4. 模拟运动 + * 5. 输出结果 + */ +fun main() { + // ════════════════════════════════════════════ + // 第1步:准备数据 + // ════════════════════════════════════════════ + val route = sampleRoute + + // ════════════════════════════════════════════ + // 第2步:创建模拟器 + // ════════════════════════════════════════════ + val simulator = TrajectorySimulator(route) + + // ════════════════════════════════════════════ + // 第3步:配置参数(可选) + // ════════════════════════════════════════════ + simulator.setPhysicsParams( + totalDistance = 2000.0, // 5公里 + duration = 600.0, // + maxVelocity = 4.0 // 4 m/s (14.4 km/h) + ) + + simulator.setNoiseParams( + gpsErrorStdDev = 0.00002, // GPS误差标准差 + driftWeight = 0.1 // 漂移权重 + ) + + // ════════════════════════════════════════════ + // 第4步:模拟运动(逐个时间步) + // ════════════════════════════════════════════ + val timeStep = 3.0 + val duration = 600.0 // 总模拟时长10分钟 + + println("时间(秒)\t距离(米)\t速度(m/s)\t加速度(m/s²)\t纬度\t\t经度\t\tGPS误差(m)") + println("─".repeat(100)) + + var time = 0.0 + while (time <= duration) { + // 调用模拟器的核心方法,执行完整的4步数据流 + val motionState = simulator.simulate(time) + + // 输出结果 + println( + "%.1f\t%.1f\t%.3f\t%.3f\t%.6f\t%.6f\t%.6f".format( + motionState.time, + motionState.distance, + motionState.velocity, + motionState.acceleration, + motionState.latitude, + motionState.longitude, + motionState.gpsError + ) + ) + + time += timeStep + } + + println("\n模拟完成!") +} + +/** + * 伪代码:未来扩展示例 + * + * // 例1:支持多个样本轨迹 + * val simulator2 = TrajectorySimulator(customRoute) + * + * // 例2:生成多个轨迹并导出GPX/KML + * val motionStates = mutableListOf() + * for (time in 0.0..duration step timeStep) { + * motionStates.add(simulator.simulate(time)) + * } + * GpxExporter.export(motionStates, "output.gpx") + * KmlExporter.export(motionStates, "output.kml") + * + * // 例3:统计分析 + * val avgVelocity = motionStates.map { it.velocity }.average() + * val maxError = motionStates.map { it.gpsError }.maxOrNull() + */ diff --git a/src/main/kotlin/core/HumanLikeDistanceEngine.kt b/src/main/kotlin/core/HumanLikeDistanceEngine.kt new file mode 100644 index 0000000..b1add88 --- /dev/null +++ b/src/main/kotlin/core/HumanLikeDistanceEngine.kt @@ -0,0 +1,276 @@ +package core + +import kotlin.math.floor +import kotlin.math.ln +import kotlin.math.sqrt +import kotlin.random.Random + +/** + * 人类跑步风格的距离-时间引擎(与路线解耦)。 + * + * 输入:time(秒) + * 输出:distance(米) + * + * 说明: + * - 该引擎只负责“时间 -> 距离”,不关心坐标路线。 + * - 内部使用状态机(跑步/慢跑/步行/过渡)来生成更像人的速度曲线。 + * - 初始化时预计算整段时间线,之后 getDistance(t) 为确定性查询。 + */ +class HumanLikeDistanceEngine( + private val config: HumanLikeDistanceConfig +) { + + private val rng = config.seed?.let { Random(it) } ?: Random.Default + private val timeline = buildTimeline() + + /** + * 根据给定时间返回累计距离。 + */ + fun getDistance(timeSeconds: Double): Double { + if (timeSeconds <= 0.0) return 0.0 + if (timeSeconds >= config.totalDurationSeconds) return config.totalDistanceMeters + + val index = timeSeconds / config.sampleStepSeconds + val left = floor(index).toInt().coerceAtLeast(0) + val right = (left + 1).coerceAtMost(timeline.lastIndex) + if (left == right) return timeline[left] + + val local = index - left + return timeline[left] + (timeline[right] - timeline[left]) * local + } + + /** + * 预生成整段轨迹的“累计距离时间线”。 + * 逻辑: + * 1) 按状态生成分段目标速度; + * 2) 叠加低频抖动; + * 3) 施加加速度约束,避免速度突变; + * 4) 积分得到距离; + * 5) 最后按总距离做比例闭合。 + */ + private fun buildTimeline(): DoubleArray { + config.validate() + + val requiredAvgSpeed = config.totalDistanceMeters / config.totalDurationSeconds + require(requiredAvgSpeed in config.minSpeedMps..config.maxSpeedMps) { + "Unreachable target. Required average speed=$requiredAvgSpeed m/s is outside " + + "[${config.minSpeedMps}, ${config.maxSpeedMps}]" + } + + val segments = buildSegments(config.totalDurationSeconds) + + val count = (config.totalDurationSeconds / config.sampleStepSeconds).toInt() + 1 + val speeds = DoubleArray(count) + + var segIndex = 0 + var segElapsed = 0.0 + + var currentSpeed = requiredAvgSpeed.coerceIn(config.minSpeedMps, config.maxSpeedMps) + var jitterState = 0.0 + + for (i in speeds.indices) { + val t = i * config.sampleStepSeconds + + while (segIndex < segments.lastIndex && segElapsed >= segments[segIndex].durationSeconds) { + segElapsed -= segments[segIndex].durationSeconds + segIndex++ + } + + val seg = segments[segIndex] + val next = segments.getOrElse(segIndex + 1) { seg } + + val target = when (seg.mode) { + HumanMotionMode.TRANSITION -> { + val ratio = (segElapsed / seg.durationSeconds).coerceIn(0.0, 1.0) + lerp(seg.targetSpeedMps, next.targetSpeedMps, ratio) + } + else -> seg.targetSpeedMps + } + + jitterState = jitterState * config.jitterPersistence + gaussian() * config.jitterSigma(seg.mode) + val desired = (target + jitterState).coerceIn(config.minSpeedMps, config.maxSpeedMps) + + val maxDelta = config.maxAccelerationMps2 * config.sampleStepSeconds + val delta = (desired - currentSpeed).coerceIn(-maxDelta, maxDelta) + currentSpeed = (currentSpeed + delta).coerceIn(config.minSpeedMps, config.maxSpeedMps) + + speeds[i] = currentSpeed + segElapsed += if (t < config.totalDurationSeconds) config.sampleStepSeconds else 0.0 + } + + val distances = DoubleArray(count) + for (i in 1 until count) { + distances[i] = distances[i - 1] + speeds[i - 1] * config.sampleStepSeconds + } + + val produced = distances.last().coerceAtLeast(1e-9) + val scale = config.totalDistanceMeters / produced + + for (i in distances.indices) { + distances[i] *= scale + } + distances[distances.lastIndex] = config.totalDistanceMeters + + return distances + } + + /** + * 构建状态分段序列(每段有:状态、时长、目标速度)。 + */ + private fun buildSegments(totalDuration: Double): List { + val segments = mutableListOf() + var remaining = totalDuration + + var mode = HumanMotionMode.RUN + while (remaining > 0.0) { + val duration = nextDuration(mode).coerceAtMost(remaining) + val speed = nextTargetSpeed(mode) + segments.add(SpeedSegment(mode, duration, speed)) + remaining -= duration + mode = nextMode(mode) + } + + // 保证至少出现一次步行段,避免整段过于“机器化” + if (segments.none { it.mode == HumanMotionMode.WALK }) { + val idx = (segments.size / 2).coerceAtMost(segments.lastIndex) + val s = segments[idx] + segments[idx] = SpeedSegment( + mode = HumanMotionMode.WALK, + durationSeconds = s.durationSeconds, + targetSpeedMps = nextTargetSpeed(HumanMotionMode.WALK) + ) + } + + return segments + } + + /** + * 状态转移:通过概率控制人类运动状态的切换。 + */ + private fun nextMode(current: HumanMotionMode): HumanMotionMode { + val r = rng.nextDouble() + return when (current) { + HumanMotionMode.RUN -> when { + r < 0.58 -> HumanMotionMode.RUN + r < 0.78 -> HumanMotionMode.JOG + r < 0.90 -> HumanMotionMode.TRANSITION + else -> HumanMotionMode.WALK + } + HumanMotionMode.JOG -> when { + r < 0.45 -> HumanMotionMode.JOG + r < 0.70 -> HumanMotionMode.RUN + r < 0.88 -> HumanMotionMode.TRANSITION + else -> HumanMotionMode.WALK + } + HumanMotionMode.WALK -> when { + r < 0.35 -> HumanMotionMode.WALK + r < 0.75 -> HumanMotionMode.JOG + else -> HumanMotionMode.TRANSITION + } + HumanMotionMode.TRANSITION -> when { + r < 0.45 -> HumanMotionMode.JOG + r < 0.80 -> HumanMotionMode.RUN + else -> HumanMotionMode.WALK + } + } + } + + /** + * 为不同状态生成持续时长。 + */ + private fun nextDuration(mode: HumanMotionMode): Double { + return when (mode) { + HumanMotionMode.RUN -> rng.nextDouble(18.0, 65.0) + HumanMotionMode.JOG -> rng.nextDouble(15.0, 55.0) + HumanMotionMode.WALK -> rng.nextDouble(5.0, 35.0) + HumanMotionMode.TRANSITION -> rng.nextDouble(3.0, 15.0) + } + } + + /** + * 为当前状态抽样目标速度(m/s)。 + */ + private fun nextTargetSpeed(mode: HumanMotionMode): Double { + val low = config.minSpeedMps + val high = config.maxSpeedMps + val span = (high - low).coerceAtLeast(1e-6) + + val candidate = when (mode) { + HumanMotionMode.WALK -> low + span * rng.nextDouble(0.05, 0.30) + HumanMotionMode.JOG -> low + span * rng.nextDouble(0.35, 0.65) + HumanMotionMode.RUN -> low + span * rng.nextDouble(0.60, 0.98) + HumanMotionMode.TRANSITION -> low + span * rng.nextDouble(0.25, 0.75) + } + return candidate.coerceIn(low, high) + } + + /** + * 高斯随机数(Box-Muller)。 + */ + private fun gaussian(): Double { + val u1 = (1.0 - rng.nextDouble()).coerceAtLeast(1e-12) + val u2 = rng.nextDouble() + return sqrt(-2.0 * ln(u1)) * kotlin.math.cos(2.0 * Math.PI * u2) + } + + private fun lerp(a: Double, b: Double, t: Double): Double = a + (b - a) * t +} + +/** + * 距离-时间引擎的配置参数。 + */ +data class HumanLikeDistanceConfig( + val totalDistanceMeters: Double, + val totalDurationSeconds: Double, + val minSpeedMps: Double, + val maxSpeedMps: Double, + val sampleStepSeconds: Double = 1.0, + val maxAccelerationMps2: Double = 1.2, + val jitterPersistence: Double = 0.93, + val walkJitterSigma: Double = 0.06, + val jogJitterSigma: Double = 0.10, + val runJitterSigma: Double = 0.14, + val transitionJitterSigma: Double = 0.08, + val seed: Long? = null +) { + /** + * 基础参数合法性检查。 + */ + fun validate() { + require(totalDistanceMeters > 0.0) { "totalDistanceMeters must be > 0" } + require(totalDurationSeconds > 0.0) { "totalDurationSeconds must be > 0" } + require(minSpeedMps > 0.0) { "minSpeedMps must be > 0" } + require(maxSpeedMps > minSpeedMps) { "maxSpeedMps must be > minSpeedMps" } + require(sampleStepSeconds > 0.0) { "sampleStepSeconds must be > 0" } + require(maxAccelerationMps2 > 0.0) { "maxAccelerationMps2 must be > 0" } + require(jitterPersistence in 0.0..0.9999) { "jitterPersistence must be in [0, 0.9999]" } + } + + /** + * 不同状态下的速度抖动强度。 + */ + fun jitterSigma(mode: HumanMotionMode): Double { + return when (mode) { + HumanMotionMode.WALK -> walkJitterSigma + HumanMotionMode.JOG -> jogJitterSigma + HumanMotionMode.RUN -> runJitterSigma + HumanMotionMode.TRANSITION -> transitionJitterSigma + } + } +} + +private data class SpeedSegment( + val mode: HumanMotionMode, + val durationSeconds: Double, + val targetSpeedMps: Double +) + +/** + * 运动状态枚举。 + */ +enum class HumanMotionMode { + WALK, + JOG, + RUN, + TRANSITION +} diff --git a/src/main/kotlin/core/RunnerPhysics.kt b/src/main/kotlin/core/RunnerPhysics.kt new file mode 100644 index 0000000..4179bb0 --- /dev/null +++ b/src/main/kotlin/core/RunnerPhysics.kt @@ -0,0 +1,88 @@ +package core + +import model.PhysicsState + +/** + * 物理引擎 - 模拟真实的运动物理过程 + * + * 职责:根据时间计算运动状态(距离、速度、加速度) + * + * 数据流: + * 时间 t + * ↓ + * [PhysicsEngine.calculate(t)] + * ↓ + * PhysicsState(distance, velocity, acceleration) + * + * 预留扩展: + * - 支持不同的运动模式(跑步/散步) + * - 支持加速度衰减曲线 + * - 支持不同的速度曲线 + */ +class RunnerPhysics( + private val totalDistance: Double = 0.0, // 总路线距离(米) + private val duration: Double = 1000.0, // 总运动时间(秒) + private val maxVelocity: Double = 4.0 // 最大速度(m/s) +) { + private val derivativeStepSeconds = 1.0 + + // 接入人类风格距离引擎(仅用于 time -> distance) + private val distanceEngine: HumanLikeDistanceEngine? = + if (totalDistance > 0.0 && duration > 0.0 && maxVelocity > 0.0) { + HumanLikeDistanceEngine( + HumanLikeDistanceConfig( + totalDistanceMeters = totalDistance, + totalDurationSeconds = duration, + minSpeedMps = (maxVelocity * 0.45).coerceAtLeast(0.5), + maxSpeedMps = maxVelocity + ) + ) + } else { + null + } + + /** + * 计算指定时间的物理状态 + * + * @param time 当前时间(秒) + * @return 包含距离、速度、加速度的物理状态 + */ + fun calculate(time: Double): PhysicsState { + // 1) 距离:由 distanceEngine 生成 + // 2) 速度:对距离做一阶差分 + // 3) 加速度:对速度做二阶差分 + val t = time.coerceAtLeast(0.0) + val dt = derivativeStepSeconds + + val distance = getDistance(t) + val t1 = (t - dt).coerceAtLeast(0.0) + val t2 = (t - 2 * dt).coerceAtLeast(0.0) + + val d1 = getDistance(t1) + val d2 = getDistance(t2) + + val vNow = if (t > t1) (distance - d1) / (t - t1) else 0.0 + val vPrev = if (t1 > t2) (d1 - d2) / (t1 - t2) else vNow + + val velocity = vNow.coerceAtLeast(0.0) + val acceleration = if (t > t1) ((vNow - vPrev) / (t - t1)) else 0.0 + + return PhysicsState( + time = t, + distance = distance, + velocity = velocity, + acceleration = acceleration + ) + } + + /** + * 获取指定时间的行进距离 + * + * @param time 时间(秒) + * @return 行进距离(米) + */ + fun getDistance(time: Double): Double { + // 优先使用新引擎;参数无效时回退为简单线性进度 + return distanceEngine?.getDistance(time) ?: time.coerceAtLeast(0.0) + } +} diff --git a/src/main/kotlin/core/TrajectorySimulator.kt b/src/main/kotlin/core/TrajectorySimulator.kt new file mode 100644 index 0000000..44ecd2b --- /dev/null +++ b/src/main/kotlin/core/TrajectorySimulator.kt @@ -0,0 +1,134 @@ +package core + +import core.trajectory.PathInterpolator +import model.MetaCoordinate +import model.MotionState +import noise.NoiseProcessor + +/** + * 轨迹模拟器 - 核心协调器,管理整个数据流 + * + * 完整的数据流: + * + * ┌────────────────────────────────────────────────────────┐ + * │ TrajectorySimulator (轨迹模拟器) │ + * │ │ + * │ 初始化 (一次性执行): │ + * │ ├─ PathInterpolator(route) → 预计算距离表 │ + * │ ├─ RunnerPhysics(...) → 初始化物理参数 │ + * │ └─ NoiseProcessor() → 初始化噪声引擎 │ + * │ │ + * │ 模拟循环 (for each time t): │ + * │ ├─ 步骤1: 计算物理状态 │ + * │ │ t → [PhysicsEngine] → PhysicsState │ + * │ │ 输出: distance, velocity, acceleration │ + * │ │ │ + * │ ├─ 步骤2: 路径插值 │ + * │ │ distance → [PathInterpolator] → RawCoordinate│ + * │ │ 输出: 未加噪的 (lon, lat) │ + * │ │ │ + * │ ├─ 步骤3: 添加噪声 │ + * │ │ RawCoord + PhysicsState → [NoiseProcessor] │ + * │ │ ├─ GpsNoiseEngine (已实现) │ + * │ │ ├─ VelocityNoiseEngine (预留) │ + * │ │ └─ AccelerationNoiseEngine (预留) │ + * │ │ 输出: 加噪后的 (lon, lat) │ + * │ │ │ + * │ └─ 步骤4: 组装最终状态 │ + * │ 所有数据 → [MotionState] → 完整运动状态 │ + * │ 输出: time, distance, velocity, acceleration, │ + * │ latitude, longitude, errors │ + * │ │ + * └─────────────────────────────────────────────────────────┘ + */ +class TrajectorySimulator(route: List) { + + // 核心引擎(一次性创建,重复使用) + private val pathInterpolator: PathInterpolator = PathInterpolator(route) + private val physicsEngine: RunnerPhysics = RunnerPhysics() + private val noiseProcessor: NoiseProcessor = NoiseProcessor() + + /** + * 模拟特定时刻的运动状态 + * + * 执行完整的4步处理流程: + * 1. 物理计算 → PhysicsState + * 2. 路径插值 → RawCoordinate + * 3. 噪声处理 → NoisyCoordinate + * 4. 组装结果 → MotionState + * + * @param time 模拟时间(秒) + * @return 该时刻的完整运动状态 + */ + fun simulate(time: Double): MotionState { + + // ════════════════════════════════════════════ + // 步骤1:计算物理状态 + // ════════════════════════════════════════════ + // 输入: 时间 + // 输出: 距离、速度、加速度 + val physicsState = physicsEngine.calculate(time) + + // ════════════════════════════════════════════ + // 步骤2:路径插值 + // ════════════════════════════════════════════ + // 输入: 距离 + // 输出: 原始坐标(无噪声) + val rawCoordinate = pathInterpolator.interpolate(physicsState.distance) + + // ════════════════════════════════════════════ + // 步骤3:添加噪声 + // ════════════════════════════════════════════ + // 输入: 原始坐标 + 物理状态 + // 输出: 加噪坐标 + 噪声信息 + val noisyCoordinate = noiseProcessor.applyNoise(rawCoordinate, physicsState) + + // ════════════════════════════════════════════ + // 步骤4:组装最终状态 + // ════════════════════════════════════════════ + // 合并所有信息到 MotionState + return MotionState( + time = time, + distance = physicsState.distance, + velocity = physicsState.velocity, + acceleration = physicsState.acceleration, + latitude = noisyCoordinate.lat, + longitude = noisyCoordinate.lon, + gpsError = noisyCoordinate.gpsError + ) + } + + /** + * 设置物理引擎参数 + * + * 用于配置运动的物理特性(速度、加速度等) + * + * @param totalDistance 总路线距离(米) + * @param duration 总运动时间(秒) + * @param maxVelocity 最大速度(m/s) + */ + fun setPhysicsParams( + totalDistance: Double, + duration: Double, + maxVelocity: Double + ) { + // TODO: 实现参数设置逻辑 + // 可考虑重新初始化物理引擎 + } + + /** + * 设置噪声参数 + * + * 用于配置各种噪声的强度 + * + * @param gpsErrorStdDev GPS误差标准差(米) + * @param driftWeight 漂移权重(0.0-1.0) + */ + fun setNoiseParams( + gpsErrorStdDev: Double, + driftWeight: Double + ) { + // TODO: 实现参数设置逻辑 + // 可考虑动态调整噪声引擎参数 + } +} diff --git a/src/main/kotlin/core/trajectory/PathInterpolator.kt b/src/main/kotlin/core/trajectory/PathInterpolator.kt new file mode 100644 index 0000000..050be74 --- /dev/null +++ b/src/main/kotlin/core/trajectory/PathInterpolator.kt @@ -0,0 +1,93 @@ +package core.trajectory + +import model.MetaCoordinate +import model.RawCoordinate +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt + +/** + * 路径插值器 - 根据行进距离在轨迹上找到对应的坐标 + * + * 职责: + * 1. 初始化时预计算所有路段距离 + * 2. 给定距离,线性插值返回坐标 + * + * 数据流: + * MetaCoordinate[] (原始轨迹点) + * ↓ + * [PathInterpolator.initialize()] + * ↓ + * distance (从物理引擎获得) + * ↓ + * [PathInterpolator.interpolate(distance)] + * ↓ + * RawCoordinate (未加噪的坐标) + * + * 核心算法:Haversine(已有逻辑完全保留) + */ +class PathInterpolator(val nodes: List) { + // 预计算:每个路段的起点、终点、以及该路段的距离 + private val segments = nodes.windowed(2).map { (a, b) -> + val distance = calculateHaversineDistance(a, b) + Triple(a, b, distance) + } + + /** + * 根据行进距离获取插值坐标 + * + * 伪代码: + * 1. 遍历所有路段 + * 2. 累减距离,找到目标所在的路段 + * 3. 在该路段内线性插值 + * 4. 返回插值坐标 + * + * @param totalDistance 总行进距离(米) + * @return 该距离对应的坐标 + */ + fun interpolate(totalDistance: Double): RawCoordinate { + var remaining = totalDistance + + for ((a, b, distance) in segments) { + if (remaining <= distance) { + val ratio = remaining / distance + return RawCoordinate( + lon = a.lon + (b.lon - a.lon) * ratio, + lat = a.lat + (b.lat - a.lat) * ratio + ) + } + remaining -= distance + } + // 超出路线长度,返回终点 + return RawCoordinate(nodes.last().lon, nodes.last().lat) + } + + /** + * 计算两点间的球面距离 (Haversine公式) + * + * 原始逻辑完全保留 + * + * @param node1 起点坐标 + * @param node2 终点坐标 + * @return 距离(米) + */ + private fun calculateHaversineDistance(node1: MetaCoordinate, node2: MetaCoordinate): Double { + val earthRadius = 6371.0 + + val radLat1 = Math.toRadians(node1.lat) + val radLat2 = Math.toRadians(node2.lat) + val radLon1 = Math.toRadians(node1.lon) + val radLon2 = Math.toRadians(node2.lon) + + val dLat = radLat2 - radLat1 + val dLon = radLon1 - radLon2 + + // Haversine 公式(完全保持原有逻辑) + val a = sin(dLat / 2).pow(2) + cos(radLat1) * cos(radLat2) * sin(dLon / 2).pow(2) + val c = 2 * atan2(sqrt(a), sqrt(1 - a)) + + return earthRadius * c * 1000 // 转换为米 + } +} diff --git a/src/main/kotlin/model/Coordinate.kt b/src/main/kotlin/model/Coordinate.kt new file mode 100644 index 0000000..12c68d7 --- /dev/null +++ b/src/main/kotlin/model/Coordinate.kt @@ -0,0 +1,39 @@ +package model + +/** + * 原始坐标点 - 轨迹中的基础路径点,不包含时间信息 + * 用于定义跑步路线的静态坐标 + */ +data class MetaCoordinate( + val lon: Double, // 经度 + val lat: Double // 纬度 +) + +/** + * 完整坐标点 - 包含时间戳的坐标,表示运动过程中的某一时刻 + * 输出最终结果时使用 + */ +data class Coordinate( + val lon: Double, // 经度 + val lat: Double, // 纬度 + val time: Double // 时间戳 +) + +/** + * 原始插值坐标 - 路径插值后的坐标,还未添加噪声 + * 用于内部数据流处理 + */ +data class RawCoordinate( + val lon: Double, + val lat: Double +) + +/** + * 加噪后坐标 - 添加了GPS噪声的最终坐标 + * 包含噪声信息便于调试和分析 + */ +data class NoisyCoordinate( + val lon: Double, + val lat: Double, + val gpsError: Double = 0.0 // GPS噪声大小 +) diff --git a/src/main/kotlin/model/MotionState.kt b/src/main/kotlin/model/MotionState.kt new file mode 100644 index 0000000..45d10b8 --- /dev/null +++ b/src/main/kotlin/model/MotionState.kt @@ -0,0 +1,32 @@ +package model + +/** + * 物理状态 - 某一时刻的完整物理参数 + * 由物理引擎计算得出,用于路径和噪声处理 + * + * 数据流:PhysicsEngine.calculate() → PhysicsState → NoiseProcessor + */ +data class PhysicsState( + val time: Double, // 当前时间(秒) + val distance: Double, // 总行进距离(米) + val velocity: Double, // 当前速度(m/s) + val acceleration: Double // 当前加速度(m/s²) +) + +/** + * 运动状态 - 综合了物理、路径、噪声的最终输出 + * 包含完整的运动信息,可用于生成GPX/KML等输出格式 + * + * 数据流:各引擎计算 → MotionState(最终状态) + */ +data class MotionState( + val time: Double, // 当前时间(秒) + val distance: Double, // 总行进距离(米) + val velocity: Double, // 当前速度(m/s) + val acceleration: Double, // 当前加速度(m/s²) + val latitude: Double, // 纬度(带GPS噪声) + val longitude: Double, // 经度(带GPS噪声) + val gpsError: Double = 0.0, // GPS噪声大小(米) + val velocityNoise: Double = 0.0, // 速度噪声大小(m/s)- 预留扩展 + val accelNoise: Double = 0.0 // 加速度噪声大小(m/s²)- 预留扩展 +) diff --git a/src/main/kotlin/noise/GpsNoiseEngine.kt b/src/main/kotlin/noise/GpsNoiseEngine.kt new file mode 100644 index 0000000..7ea401d --- /dev/null +++ b/src/main/kotlin/noise/GpsNoiseEngine.kt @@ -0,0 +1,126 @@ +package noise + +import model.PhysicsState +import java.util.Random +import kotlin.math.pow +import kotlin.math.sqrt + +/** + * GPS噪声引擎 - 模拟真实GPS信号的误差特性 + * + * 噪声模型: + * 1. 白噪声 (White Noise): 每个时刻的随机偏差 + * 2. 漂移 (Drift/Brownian Motion): GPS信号偏向某一侧的持续性特性 + * + * 数据流: + * 原始坐标 (lon, lat) + PhysicsState + * ↓ + * [GpsNoiseEngine.applyNoise()] + * ├─ 生成白噪声 + * ├─ 累积漂移 + * └─ 合并两种噪声 + * ↓ + * (lon + noise, lat + noise, error_magnitude) + * + * 核心算法逻辑完全保留 + */ +class GpsNoiseEngine( + private val gpsErrorStdDev: Double = 0.00002, // 经纬度标准差,约 2 米 + private val driftWeight: Double = 0.3 // 漂移权重 +) { + private val random = Random() + private var driftLat = 0.0 // 纬度漂移累积值 + private var driftLon = 0.0 // 经度漂移累积值 + + /** + * 应用GPS噪声到坐标 + * + * 伪代码: + * 1. 生成白噪声 (高斯分布) + * 2. 生成漂移 (模拟GPS偏向某侧的特性) + * 3. 合并两种噪声 + * 4. 返回加噪后的坐标和噪声大小 + * + * @param rawLat 原始纬度 + * @param rawLon 原始经度 + * @param physicsState 物理状态(预留用于未来扩展) + * @return Triple(带噪声的经度, 带噪声的纬度, 噪声大小) + */ + fun applyNoise( + rawLon: Double, + rawLat: Double, + physicsState: PhysicsState + ): Triple { + + // 第1步:生成瞬时高斯噪声 (White Noise) + val whiteNoiseLat = random.nextGaussian() * gpsErrorStdDev + val whiteNoiseLon = random.nextGaussian() * gpsErrorStdDev + + // 第2步:生成累积漂移 (Drift/Brownian Motion 变体) + // 模拟 GPS 信号在一段时间内偏向某一侧的特性 + // 原有逻辑完全保留 + driftLat = driftLat * 0.8 + random.nextGaussian() * gpsErrorStdDev * driftWeight + driftLon = driftLon * 0.8 + random.nextGaussian() * gpsErrorStdDev * driftWeight + + // 第3步:合并两种噪声 + val finalLon = rawLon + whiteNoiseLon + driftLon + val finalLat = rawLat + whiteNoiseLat + driftLat + + // 计算总噪声大小(用于记录和调试) + val totalError = sqrt( + (whiteNoiseLon + driftLon).pow(2) + (whiteNoiseLat + driftLat).pow(2) + ) + + return Triple(finalLon, finalLat, totalError) + } +} + +// ============ 预留位置:未来扩展的噪声引擎 ============ + +/** + * 速度噪声引擎 (预留) + * + * 用途:模拟GPS设备在计算速度时的噪声 + * + * 伪代码: + * fun applyNoise(velocity: Double, physicsState: PhysicsState): Double { + * // 生成速度波动 + * // 考虑GPS刷新率、滤波算法等因素 + * return velocity + noise + * } + */ +class VelocityNoiseEngine( + private val velocityErrorStdDev: Double = 0.1 // 速度误差标准差(m/s) +) : NoiseEngine { + private val random = Random() + + override fun applyNoise(physicsState: PhysicsState): Double { + // TODO: 实现速度噪声逻辑 + // 可参考:运动速度越快,相对误差越小 + return physicsState.velocity + } +} + +/** + * 加速度噪声引擎 (预留) + * + * 用途:模拟GPS设备计算加速度时的噪声 + * + * 伪代码: + * fun applyNoise(acceleration: Double, physicsState: PhysicsState): Double { + * // 加速度是速度的二阶导数,噪声会更大 + * // 可能需要应用滤波器 + * return acceleration + noise + * } + */ +class AccelerationNoiseEngine( + private val accelErrorStdDev: Double = 0.05 // 加速度误差标准差(m/s²) +) : NoiseEngine { + private val random = Random() + + override fun applyNoise(physicsState: PhysicsState): Double { + // TODO: 实现加速度噪声逻辑 + // 可参考:加速度通常更噪声,需要低通滤波 + return physicsState.acceleration + } +} diff --git a/src/main/kotlin/noise/NoiseProcessor.kt b/src/main/kotlin/noise/NoiseProcessor.kt new file mode 100644 index 0000000..6315629 --- /dev/null +++ b/src/main/kotlin/noise/NoiseProcessor.kt @@ -0,0 +1,81 @@ +package noise + +import model.PhysicsState +import model.RawCoordinate +import model.NoisyCoordinate + +/** + * 噪声引擎接口 - 为未来扩展预留的标准接口 + * + * 支持多种噪声类型: + * - GpsNoiseEngine: GPS信号误差(已实现) + * - VelocityNoiseEngine: 速度波动噪声(预留) + * - AccelerationNoiseEngine: 加速度波动噪声(预留) + */ +interface NoiseEngine { + /** + * 应用噪声 + * + * @param physicsState 物理状态 + * @return 添加噪声后的值 + */ + fun applyNoise(physicsState: PhysicsState): Double +} + +/** + * 噪声处理器 - 管理所有噪声引擎的协调器 + * + * 职责: + * 1. 管理多个噪声引擎实例 + * 2. 按顺序应用各种噪声 + * 3. 组装最终的带噪声坐标 + * + * 数据流: + * RawCoordinate (无噪声) + PhysicsState + * ↓ + * [NoiseProcessor.applyNoise()] + * ├─ GpsNoiseEngine.applyNoise(x, y) + * ├─ VelocityNoiseEngine.applyNoise(v) [预留] + * └─ AccelerationNoiseEngine.applyNoise(a) [预留] + * ↓ + * NoisyCoordinate (添加了噪声) + */ +class NoiseProcessor { + private val gpsNoiseEngine = GpsNoiseEngine() + // 预留其他噪声引擎 + // private val velocityNoiseEngine = VelocityNoiseEngine() + // private val accelerationNoiseEngine = AccelerationNoiseEngine() + + /** + * 应用所有噪声到坐标 + * + * 伪代码: + * 1. 获取原始坐标 (lon, lat) + * 2. 应用GPS噪声 → (lon + noise_lon, lat + noise_lat) + * 3. 返回带噪声的坐标和噪声信息 + * + * @param rawCoord 原始插值坐标 + * @param physicsState 物理状态(用于速度相关的噪声) + * @return 加噪后的坐标 + */ + fun applyNoise(rawCoord: RawCoordinate, physicsState: PhysicsState): NoisyCoordinate { + // 第一步:应用GPS噪声 + val (noisyLon, noisyLat, gpsError) = gpsNoiseEngine.applyNoise( + rawCoord.lon, + rawCoord.lat, + physicsState + ) + + // 第二步:预留位置 - 速度噪声 + // val (finalLon, finalLat) = velocityNoiseEngine.applyNoise(noisyLon, noisyLat, physicsState.velocity) + + // 第三步:预留位置 - 加速度噪声 + // val (finalLon, finalLat) = accelerationNoiseEngine.applyNoise(...) + + return NoisyCoordinate( + lon = noisyLon, + lat = noisyLat, + gpsError = gpsError + ) + } +} diff --git a/src/main/kotlin/resources/data/SampleRoute.kt b/src/main/kotlin/resources/data/SampleRoute.kt new file mode 100644 index 0000000..092c40a --- /dev/null +++ b/src/main/kotlin/resources/data/SampleRoute.kt @@ -0,0 +1,28 @@ +package resources.data + +import model.MetaCoordinate + +/** + * 样本轨迹数据 - 真实的跑步路线坐标 + * 北京地区的一条跑步路线 + * + * 使用场景:主程序可直接调用此数据进行模拟 + */ +val sampleRoute = listOf( + MetaCoordinate(116.136675, 40.252335), + MetaCoordinate(116.136616, 40.254506), + MetaCoordinate(116.137857, 40.256192), + MetaCoordinate(116.142863, 40.256100), + MetaCoordinate(116.145682, 40.254377), + MetaCoordinate(116.145465, 40.251443), + MetaCoordinate(116.144187, 40.249442), + MetaCoordinate(116.139558, 40.249806), + MetaCoordinate(116.139496, 40.249830), + MetaCoordinate(116.137490, 40.251151), + MetaCoordinate(116.139551, 40.252425), + MetaCoordinate(116.137989, 40.251971), + MetaCoordinate(116.135422, 40.250539), + MetaCoordinate(116.135391, 40.250539), + MetaCoordinate(116.135268, 40.253577), + MetaCoordinate(116.135393, 40.253604) +) diff --git a/test.kml b/test.kml new file mode 100644 index 0000000..42b248b --- /dev/null +++ b/test.kml @@ -0,0 +1,76 @@ + + + + 真实的步行轨迹 (北京市昌平区) + + + + + + + + 步行路径 (带时间轴) + #multiTrack_style + + + relativeToGround + 1 + + 2026-03-19T00:00:00Z + 2026-03-19T00:00:10Z + 2026-03-19T00:00:20Z + 2026-03-19T00:00:30Z + 2026-03-19T00:00:40Z + 2026-03-19T00:00:50Z + 2026-03-19T00:01:00Z + 2026-03-19T00:01:10Z + 2026-03-19T00:01:20Z + 2026-03-19T00:01:30Z + 2026-03-19T00:01:40Z + 2026-03-19T00:01:50Z + 2026-03-19T00:02:00Z + 2026-03-19T00:02:10Z + 2026-03-19T00:02:20Z + 2026-03-19T00:02:30Z + 2026-03-19T00:02:40Z + 2026-03-19T00:02:50Z + 2026-03-19T00:03:00Z + + 116.136647 40.252336 35 + 116.136686 40.252420 35 + 116.136690 40.252510 35 + 116.136650 40.252580 35 + 116.136668 40.252696 35 + 116.136658 40.252794 35 + 116.136653 40.252915 35 + 116.136650 40.252943 35 + 116.136651 40.253087 35 + 116.136635 40.253133 35 + 116.136623 40.253246 35 + 116.136624 40.253352 35 + 116.136641 40.253407 35 + 116.136617 40.253523 35 + 116.136643 40.253594 35 + 116.136625 40.253699 35 + 116.136649 40.253785 35 + 116.136660 40.253868 35 + 116.136645 40.253948 35 + + + + \ No newline at end of file