From 07cb1304d241874ee862cbdb2741e1fa8821f587 Mon Sep 17 00:00:00 2001 From: pythagodzilla Date: Thu, 19 Mar 2026 17:57:03 +0800 Subject: [PATCH] init --- .idea/.gitignore | 10 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/dictionaries/project.xml | 7 + .idea/kotlinc.xml | 6 + .idea/libraries/KotlinJavaRuntime.xml | 23 ++ .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/refactored-likeRunner.iml | 13 + .idea/vcs.xml | 6 + 00_START_HERE.txt | 152 ++++++++ BEFORE_AFTER.md | 361 ++++++++++++++++++ DATA_FLOW_GUIDE.md | 216 +++++++++++ QUICK_START.md | 172 +++++++++ README.md | 158 ++++++++ STRUCTURE.txt | 116 ++++++ .../refactored-likeRunner.kotlin_module | Bin 0 -> 69 bytes .../refactored-likeRunner/MainKt.class | Bin 0 -> 2471 bytes .../core/TrajectorySimulator.class | Bin 0 -> 2821 bytes ...HumanLikeDistanceConfig$WhenMappings.class | Bin 0 -> 861 bytes .../physics/HumanLikeDistanceConfig.class | Bin 0 -> 9303 bytes ...HumanLikeDistanceEngine$WhenMappings.class | Bin 0 -> 861 bytes .../physics/HumanLikeDistanceEngine.class | Bin 0 -> 9676 bytes .../core/physics/HumanMotionMode.class | Bin 0 -> 1885 bytes .../core/physics/RunnerPhysics.class | Bin 0 -> 2673 bytes .../core/physics/SpeedSegment.class | Bin 0 -> 3194 bytes .../core/trajectory/PathInterpolator.class | Bin 0 -> 4988 bytes .../model/Coordinate.class | Bin 0 -> 2601 bytes .../model/MetaCoordinate.class | Bin 0 -> 2301 bytes .../model/MotionState.class | Bin 0 -> 4899 bytes .../model/NoisyCoordinate.class | Bin 0 -> 2779 bytes .../model/PhysicsState.class | Bin 0 -> 2993 bytes .../model/RawCoordinate.class | Bin 0 -> 2295 bytes .../noise/AccelerationNoiseEngine.class | Bin 0 -> 1519 bytes .../noise/GpsNoiseEngine.class | Bin 0 -> 2453 bytes .../noise/NoiseEngine.class | Bin 0 -> 563 bytes .../noise/NoiseProcessor.class | Bin 0 -> 1800 bytes .../noise/VelocityNoiseEngine.class | Bin 0 -> 1510 bytes .../resources/data/SampleRouteKt.class | Bin 0 -> 1633 bytes src/main/kotlin/Main.kt | 91 +++++ .../kotlin/core/HumanLikeDistanceEngine.kt | 276 +++++++++++++ src/main/kotlin/core/RunnerPhysics.kt | 88 +++++ src/main/kotlin/core/TrajectorySimulator.kt | 134 +++++++ .../core/trajectory/PathInterpolator.kt | 93 +++++ src/main/kotlin/model/Coordinate.kt | 39 ++ src/main/kotlin/model/MotionState.kt | 32 ++ src/main/kotlin/noise/GpsNoiseEngine.kt | 126 ++++++ src/main/kotlin/noise/NoiseProcessor.kt | 81 ++++ src/main/kotlin/resources/data/SampleRoute.kt | 28 ++ test.kml | 76 ++++ 49 files changed, 2324 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/dictionaries/project.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/libraries/KotlinJavaRuntime.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/refactored-likeRunner.iml create mode 100644 .idea/vcs.xml create mode 100644 00_START_HERE.txt create mode 100644 BEFORE_AFTER.md create mode 100644 DATA_FLOW_GUIDE.md create mode 100644 QUICK_START.md create mode 100644 README.md create mode 100644 STRUCTURE.txt create mode 100644 out/production/refactored-likeRunner/META-INF/refactored-likeRunner.kotlin_module create mode 100644 out/production/refactored-likeRunner/MainKt.class create mode 100644 out/production/refactored-likeRunner/core/TrajectorySimulator.class create mode 100644 out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig$WhenMappings.class create mode 100644 out/production/refactored-likeRunner/core/physics/HumanLikeDistanceConfig.class create mode 100644 out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine$WhenMappings.class create mode 100644 out/production/refactored-likeRunner/core/physics/HumanLikeDistanceEngine.class create mode 100644 out/production/refactored-likeRunner/core/physics/HumanMotionMode.class create mode 100644 out/production/refactored-likeRunner/core/physics/RunnerPhysics.class create mode 100644 out/production/refactored-likeRunner/core/physics/SpeedSegment.class create mode 100644 out/production/refactored-likeRunner/core/trajectory/PathInterpolator.class create mode 100644 out/production/refactored-likeRunner/model/Coordinate.class create mode 100644 out/production/refactored-likeRunner/model/MetaCoordinate.class create mode 100644 out/production/refactored-likeRunner/model/MotionState.class create mode 100644 out/production/refactored-likeRunner/model/NoisyCoordinate.class create mode 100644 out/production/refactored-likeRunner/model/PhysicsState.class create mode 100644 out/production/refactored-likeRunner/model/RawCoordinate.class create mode 100644 out/production/refactored-likeRunner/noise/AccelerationNoiseEngine.class create mode 100644 out/production/refactored-likeRunner/noise/GpsNoiseEngine.class create mode 100644 out/production/refactored-likeRunner/noise/NoiseEngine.class create mode 100644 out/production/refactored-likeRunner/noise/NoiseProcessor.class create mode 100644 out/production/refactored-likeRunner/noise/VelocityNoiseEngine.class create mode 100644 out/production/refactored-likeRunner/resources/data/SampleRouteKt.class create mode 100644 src/main/kotlin/Main.kt create mode 100644 src/main/kotlin/core/HumanLikeDistanceEngine.kt create mode 100644 src/main/kotlin/core/RunnerPhysics.kt create mode 100644 src/main/kotlin/core/TrajectorySimulator.kt create mode 100644 src/main/kotlin/core/trajectory/PathInterpolator.kt create mode 100644 src/main/kotlin/model/Coordinate.kt create mode 100644 src/main/kotlin/model/MotionState.kt create mode 100644 src/main/kotlin/noise/GpsNoiseEngine.kt create mode 100644 src/main/kotlin/noise/NoiseProcessor.kt create mode 100644 src/main/kotlin/resources/data/SampleRoute.kt create mode 100644 test.kml 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 0000000000000000000000000000000000000000..01538e154a020340ae702e0e772b5c70c9782fa3 GIT binary patch literal 69 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'q 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 OBA)$(&uelsKm8rHV00q@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7b5898e31697b9c45f2fb2751e02a1b78df4358d GIT binary patch literal 861 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6998f0e161ad428dff47ed6a08153afd4a80cda9 GIT binary patch literal 2673 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 literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/model/Coordinate.class b/out/production/refactored-likeRunner/model/Coordinate.class new file mode 100644 index 0000000000000000000000000000000000000000..3a0401b9bee5dadd5f15955c6e4139c2d246aee0 GIT binary patch literal 2601 zcmaJ@-%}e^6h3$JV>cndCLxdlwbf!t0&Sqh9~G#!#Fk*76)M`|k1ojyTf#2xZaVtn z)R+DV&eTUA93MKP&RFW$83)@p|0c)rJ9oE)LZ&mBz27;U^L^*ubM`L({P*WyL^MUF zAbs0un9cH>I`W>_Gs}vCwpRsJqzX(vMR4T<)L6Jgn zHAOK}lN2QlcGIy1iAsu+)RiEGQtUJQ6!iplub`-JZJQ}d2aXIoHZ4z3rgpfLX((-& zevLVyR0&PP7o@^}13ZF~b!U6Wu}#~bJZPLdXq<;g-P!p@(15JGlhUc0<8GAinEtwJ zShiO-Y}@e--*SML9Dk|RY)mlw zXUDV~g3c6wN+CzOP7-L9$iKauIb6v2;{`g zuUbYEQ<+Fm!^8{As%no~ELtA=Z_9Xf|V!^2y(?^>P( z|E2b=0PYpvsBbMAJ7M(5;pppzYiygo>AsMbTya`%-MnJ4?6ELs^$0&r;zEdG4kDD{ z>l7%%*Qi`^dtyytpo@*R`)!^xSB$;Ub-XAdW5J0=hAC!ac8w&<8J)GV=&4*E8-xB(~7>;%0= zr+``Eeefr+p3SMzZy%7xEt}7&u@LJEIaLKtz@NPkD{&3G^msmy)#y>|{yUklnYlu%R5-Q5IK-PYnMysM^2O zr;^g!D<_Q0fzW;*uhJqdp|Ev$ReT9B2Ao`%&rhj=)r0pM4*&WZMXL0n{4LW;m2S{S zRa&Kw*GPEu3EfqM0d(`jIfX4yHJR0<9$fIG8j(c=PMlO0Z KE5T{`T>BrBMdfw? literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/model/MetaCoordinate.class b/out/production/refactored-likeRunner/model/MetaCoordinate.class new file mode 100644 index 0000000000000000000000000000000000000000..c58a6544457ce380e20b28643b08c32d9df4171f GIT binary patch literal 2301 zcmaJ>-%}e^6#njJlWdj%OZZu6ZELY4p=~JkhYD?NiAXTeS}ICW$0b=|A?)JrrlT)T zpZX^_Qy+ck_|O@3#!|=5IM}}VkN8hGj^Eu~fzq^)x%Zs2=X~F}=bU^0`uF!g0Gz|; z0zI2<-D;HPtiYUdU9WCCW?-cu1dMg_j#+A$&T8rU%DPnx(ntuTCT+(KE(;{{<#J(B zAdxRDW|2f%hYF2)!*v8iISU!Xu{mM8IYwp%J0)sNCm!Y1hy54GO-3nGb({}ul={Rm+2DVFi-VNrP zjmCsLNlItdLjqZeIb65CG@A{QPvu(!m&=vv!BS1UB#A5ra7e-*70?2AA@FQxl}*SO z_CZ8emZ5(SHnLr7+Ksy9rEx;Xuq<}>^L@*+_LnrAWXa5}Ez7A3oX)?9{32Nl%;V=# zkl00m!+DnSB^iW{r=;U)a-BNH{*t#OgQi{`bXEq9XK@^_OX(czux9#eQ=G!E-IeDC zuk1Eg8XU*B*kPtq$>IXu(eXCqka(6KHbyWeWqs8&8=R@${pXG5cA{`wVE9$dXXj1w z5Hu|(%=DN`INv0^hABzNF-*F_n&k=P_EjZj5a_CIxIx2q!kbh#1CwCN=A8uZnIaeP zPHaelH%s~2meLrdItTghox3M>WkAstW9P2UpJJqRMbo*rahG;2GIwy(p5D_2cJ7WV zW8zwWE@dc#%2CiE*8J&abfEW`q&%Too2CZF`1?3#&ZYkgFiZDnkGaOX4w4u@Xe=e~xV`xu*O!dg) z@~W~Dy&f-BEpR=ne!5|AST~!FBP-j!_XM5U;FVLOvFNPYjy2!hT(P`cvQM%FZp~~g znw~B7*f>O(z}~bf&K=vgX}`L6!HK;Pn6-^Lb1U{9KInbJ^vq2wu)OEO^aZ!+)vRe- z(jJOq7GJ=}Szcu8Y)`ke~*179S&19C?KHV&M^tUuaVB zG1puOla`MuGW_pA8lTX9jDZY87d_-(XGp_xQj$aoe(6R}Oj9D7{FW^KiuRuQA67jT9c^ z@XyqusHjT%Zk6=iD(P{RmgpNEGa5+q3esOo6OJ8`QM+ z4|>v2I>S~=cH-{#;3B5+KJ!?nHO*%bSHsv(!}TM4#(kaE88)D@j6?;q;qL=fE11K4 z1=n$78N$bhxVem8A2}ZjKJ;+y@R9V9^3m<1$CqFGeDwPm@Np${nD%iY6pn}Mt^WWN C4!_(0 literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/model/MotionState.class b/out/production/refactored-likeRunner/model/MotionState.class new file mode 100644 index 0000000000000000000000000000000000000000..1f174b6b9f18e5a36b393065d4761b7937e275b6 GIT binary patch literal 4899 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 literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/model/RawCoordinate.class b/out/production/refactored-likeRunner/model/RawCoordinate.class new file mode 100644 index 0000000000000000000000000000000000000000..ac740e560899da94192fbcdd7308300a4fb16d94 GIT binary patch literal 2295 zcmaJ>-%}e^6#njJlPpVsB|xD-ZELY4p=~JkhYD?NiAXTe3KgZOZ1>i56q}DmO6IE!S>C6#DBtZ{O;}wl%~yO?mhSHJ>Pfkch0?k{rmeL050HD zfv!!rX4Q-H=9iPM>(y+>46HPSfU$1gHH&rASuNgNS+}Y|8VP~agzebDRe@x#RLU<3 zBy#!143bFeP@!>McO3yy%0NeJ3lwCe&kQnXkJcRmYOrSe0zH)`+~ed}wStO7A$BQV znt^~`bvL(M$8v)62Nz=mth!rY3mla>y#Vx7#r0N;>sGMhnYQB>O~-KqGq7FSb8ax# zsMp8kIZ`@v9uUY#$f26`x!I_b`+BY^ZmCqR9M);Pc?ucy;iv>ZE}#YOLg3lXsz7%x ze*hu!QiI-o)W~sc>?U~# znwH&VddwwkZ4%zVq$FeuCf#7o@~HU%RbmE#&dP=x)NQ9YYXxS_3`~M4n|BkuVTv5U z>#!jWUMl4)TUw*EYHZ@acki9nl|DsRjNN-W|B8{)6;0>d%31og$lcCKdvagv+r2lY zjEWn**_5I5E29a{s+^yEpHj7?k(y~Sbh(~>dOtzCbhoe>6UWrqE#mNqmx|Ia0Mwiw!zA5-1( zxV)xRqQ~Qnss^rS)y~%K4QsyPI8xcR{nzK*2Jf31wM9q8cC5L^=8EOrmUWUPaI0p0 z(e!M&j-3O93G7X)?A*0|oBr$jx0~1tfmz*{HMip3Lx*?YGCgzC3M}uLFnz&ocvWl4 zmb6FXn8oMtah`WtWl8w09r6ekbZnb{|N>%44vE|e;pwW<)kHv68zGI?wF=XH2E!A_!VtG(TKoh z79g+$%tRYvz)tiu4K@x34>k=xvKM^nFtcbH(AzX1WNRM4caz9c!U>8gCDb27SsKni z!r;$bMNyGT`kqSqo=SSG(h~Pd8TtXE1;_9TZHao3m}$<(dy?um51>imrhAgf*fwSg z!}%SYh^X#xqY5R{%3!`^abi;}g48$n7(+{s@LG0w2O|<^2j{Y*k!{d62=w|6F7Brt z)gl+CeIP~RUBcx!#cRY@h~FzQf5XJYfN^|{wn6)|iGgi?ic~nlZ5sRH3a*9;^~S&g z*YI|n>=q9oJL+;_u<$eLk**8>W0*3XPNDc>6zI C8NM0- literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/noise/AccelerationNoiseEngine.class b/out/production/refactored-likeRunner/noise/AccelerationNoiseEngine.class new file mode 100644 index 0000000000000000000000000000000000000000..cf871cbf8ea60e409b6c6841302109617012a207 GIT binary patch literal 1519 zcmaJ=TTdHD6#iyy*6Xzcwz&k-LPC-T+ca4q>D5Un!6djcB!bpz#nZ5!n#Jo~YiH~> zQdFV-i}bNi?Nilopa`!zyEpp7l1pcFw6#_Cxx@- zy22L`S6&!wYt2T`@B)zlW5^uxE_Zw$G@Qr#hr(3}#2B*uz;n(c4CY-g@YH>Vc)nby zF~stPS{ex?ZNyR2=C zDHw9qUP(hvr8LGdp;Jr}v^wx4!{zG#_EuuJoiA6a%~1JX;2d^aju$8q1>ASa;v3%a z)p{666?I${Mq4~;il|hmnXA7%*PkDCSC&nP?=slD-S(gMX2EbVUu}hT;X6+bo=VS^ zyNWAODwG+nRl}&^9167`aW9Y#4}y@4G;`@}hibdy`&3oBJrrkQo?-57HC6%@Q4#`3 z;2OiEdm!BAAj=aT@s^-w7#8yVVGZ41X-KoHlQgKWG~UAv8`rTw^L?}a1PTme4WWja z(z{-|y+yolqllXf6K`u>PijU_YKdrbHt#K=c%3Uw4I8a)jQD8i1;mG@-VpJO?>xPu zD5Jj2@a*L1ifv3Az5mIPZDdT_uws3cb*7#^Ia)E6*n@1&vW$#z$ykb={$$3jcxL4D zOvcjZ6*H40X>U&LjP^agI{hI|UWR>gL@JYvi6PbL&t^*3u);7t;M5T9sR1f_G9t=o zqKNuU-)oAUP7vtvXCJlYkn^pkVt9v)%4>;A(DkIZ?~Aq9JRtV26M1OXReFEhoz}jH zzTo7eSs%LGukpy!=YjG9vCD04@%G?+{lB?iQQ;hjy&HBSS3LA|;+a8A?Tu=%Ot%)N z_DqsF{YvRRa{8%}ZjJUP$txta4?PE3AJFMb#OMo57LSoCF1)~4@lRyGK1S{ZrhX%X zfpuDQ6b2|-J4oVNN~%yM%@o-w7#A^3Qtvb{%V>z10mMB57$I9`Hvau{Bqnfav$$}K z*?#s7vc$mbb1){s*aR_#E+v`6WrESwu9DP%sm0vYKagMi9k+g^W4}h-powQgb){a_ zmBKt6vOgs61o5^v30-88_B!(oDv{U$t2i2G$5__Iv0kclUq47?;1O92e1=VukIA+| zk+=5{tKd%W_W?ev;3Iro!6*214@{zjyL(7WOiSF8dUIbQAz?|3OH4@2Nz6+)6083L D_~cSr literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/noise/GpsNoiseEngine.class b/out/production/refactored-likeRunner/noise/GpsNoiseEngine.class new file mode 100644 index 0000000000000000000000000000000000000000..98690d156818d998fe05d508f1897579a9bb6e03 GIT binary patch literal 2453 zcma)8&rcgy5dL;;ugzi{;{q{|pG{J*+r$K-G?bK-;QW9Zl3+rTk~UrJ#a?Xhy1Q#g zE)~k9IrUIgmC|ERmAJH0DX5Vulw+^`6MAWnm3ru@>i5=$*hyN&vfs>`H*aR%%r^tS z{PWY#0M4K)q06)kM^9a;Ia~PCqX%Hn1(wp zAuyOuCl)051``Wm1Q1l=M@T}XP<1ZZwq}l(cT6bVF~Sd+gNsQ=|-XG zh7qaD_w9PCxkfoPrL}BmDkIu>%|9-k#n-H=IUdKU7z!|Q7!9? z4oWv+sGP1tWmviJ$7AvNdhgUS!#G<%eVh1u z2HP3UEe0`4vsG)YfmoU!!+90&;9Vld`NVcf2Ga@3FW@~PpODbe$U?nfK}_!Av#At8 zT#^u6)yg&f+A@|w^y|(oFY0Xn$W372cx?Tobf%86rb2fSBE`(=vSIcXzM)1g$ zRUfB_EEcfMOM>CtknbCU8zPrKE#dc_^+8pR%c>mRSy%a$qlzkr{B>E#*FtyJ<2&oa z@{n|~KNbqfQMpeZ@_C$Ja8JKc{GmWpnI4L^_jSemF>%_o4uyx6Xot`wjWp;!(&${I zZtM2bhkja-83;w5(N~_InmQ^l}_>AVM(N7-)^?{8ot}+gb{N^ z&z~$CC4H`DnxbVoTj%bn5^L4ZL)WP2Q|7AS7;O59XT3|TPIABq7aY^GwaSui&ui2X z!_mrV2Xq7^s`6Vxukx|IaawAxhulQ9nhEDLR)fR z6CKIKCZfqFh~3%3Yn#~ry?58(+T&^V)4~C?;UIk;MlbrY!m}f^Lo0|=B0hayWULW6 z5t}b6y!JJ`X2}P6cCb6(|MW3}UFg5s9SGFfgVV`@#1@X!9ha!)hup40gwV&pL6<>> z&`ug-7(Jv1a0s0^ir1;jXGu4x5vArvZy@%@Hr`79fc-yWh@_)5s#x6k4RxiIhS#~itcNYY-_Hjt%ERBZ$ELBkR>tNo&2?sYF=>Gy7RVdK_ literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/noise/NoiseEngine.class b/out/production/refactored-likeRunner/noise/NoiseEngine.class new file mode 100644 index 0000000000000000000000000000000000000000..83000f38fa025ee38a4b75c8e6c8bdbc4eb1cd1d GIT binary patch literal 563 zcmZutO>Yx15Pf!%-Le$ArC({`214zjY>2q^(n^2`3L&BrmzX4 z;D91}<3}N87bGfyGD*Fyvzm_zA_VKWDLfTPQ^-OT9`@p zems?Vh|yq3#A2bZ>t2R~rZSA3P{wD%7)zhZ z5+B$wSZaMnoBJ~xv@+Q{8AKt1pc}w|T$ zE`xLzf88O6FYVIh^0m^E;qcGQ$KMmT8Y2>bW@Qpslc9x(jy|MUOr{|w!f{k%8xJX; sQ+0#d?u^jrA*tgoHhZ{-`#o$S9f5gl~EMXOoW&WeOL zDxZ?4JSJ5Mq~fX+#baLbM=AHL)>sfyB~ADA_MGWH-93N*`{!Q(X7DXTpLA^{vMYLB z^W2h9%JpJk45@v7z_SjQmF(*7z9{)IL>SCRwzU1n43W&}RswA>EkqGJ2U@JF0Bb>3 zY$;-BXPC+4^Mzv7^&MMg_YZ1WTl&J2+{xy}cf8^F^R86B*C_d}x6Hk&@N!M)4ht3% z49Us`P=yw0RkHiS-}SgHRhCQX(h-}IBYORnhU3t^ zaWA|gjzNYSSFuu%zDEFbWeh_M{iQuos)h(_+~YMt^fBDY6fajO*J9bwAeDg0>v+e) zHN4A^sJG-2$1p?h6^X}ilL}oCe$kadq4Npc!ia_U@j>{3`w84>nld_+eEw>(!u&Qp zyY2`oP#?y0QO6lNFMNt&l4??NYjsx&>EEM%%8a&T)9gS3$YRREU64adf37rYyMns* zK0`;@ZR|Q?i#rXGzylrP3_fhdo=)K7Cdk89^Zf*JP4gq-yvfR?BF8{|Z&I%LW`91l z@C36|Q>mLFAdBVCx7;U{I(NbITq;-pl`TejUPT;3_nU!P7CtX?pHuG`wSx%Ft)Uk% zu&P#QQjH&Mtxb_uCSsh^qY=wU8A`5l!_xjVVJ{k=~YO(Kk8yF!U7N z5_h(^XX|}v9J!2jK0Oyetqr%~mBce!0}X{|w*GTF?$OH<4VdrI4-KXKl6EG^0g?tu zdJSparrk5NlLqvSoghB;3f6WqX|fY^{*LZn1Me?sO+wdgoKPpwOLB*HiRL-;P+w>ouIL5@v{QM9!5$3B-BmP zKMnQxD?HgwN7H6HeuBAQ2-1KKXG}l0)Y2(DrE6!A#vF!pi(?53C}I)IC}4$lOF^iQ zDcw=r#ys+r!UK9@7DzM6KS>1SDO?IROhjvggrG@T#W0@JovrkJMfOh3q2}@pqhFJ5 z;0t_1a-FONV)(@lA_Xi3zao}(JgWt);jj!Fcp#tT?%~)pDPS1 MEGi5uJXhFy4I&n?ZvX%Q literal 0 HcmV?d00001 diff --git a/out/production/refactored-likeRunner/noise/VelocityNoiseEngine.class b/out/production/refactored-likeRunner/noise/VelocityNoiseEngine.class new file mode 100644 index 0000000000000000000000000000000000000000..75608c6dcfb7ee9e7db51d1c92eea4f995273d9b GIT binary patch literal 1510 zcmZ`&TW=dh6#i!I?5@{tvT-g=3Jnb{jqT8Gl9sF6hBi(cBPXpAus|uGi9I+QuXnAT zu|*|>BK}2rn&dj-e=bZWGufKo)6TnTB8D_)Cm%^)x zAgcN5$(BB?hYddz78rwjz`NWFc-ZhB?j4AlvXEdH?+2Z69$_$V`Ju1wFeG!Oe3c=Q z%U3h7kaCcO!!XeupsdGn6z`~dNpu-lDT55orC?z66tJWGz}x0wJ!)BS9gOR6GwvxE zvXx#{Lr%pECNQZ}OcA=;_a(z?mH%z6$Z#W9Dp#733jEMJ=(apRR3Z*};FZMJyc4Lk zD3mJh)KnC2^0+DDV!mpw{`ORVe$Y+X4xzoy;P7@k=+BMee6G@p>LTzS?LU!zP3|bJ zL@{4txLk?ihIb&;Ud;VadOQpxGWrDLZAEIU69iOMracsAW1eB|RW+7F6;l!duyC1S zs4s3JB(Y<+nsR?E2E*3&h=jIY6vkBjPZORp{w$by|BO{)Cf{ zW_wiQL6yh8J`a@Vh*`DfCT|bUSN@y(1r5%CI6F}%u8I4;PCPq^slHSVmg&}#)SgK) zt6wMGM^-;E(%q%KN%9g&?L$w2)_ZjN6bbqQQ-vd>3k%OMR`>(spC2Ln4AZ}m!N3}= zSqcLl)A|Kc_==J~Mu{}jWT#-9#|%lm)4`pYF~O=g!>}ERCDV{O|ua z=iIq_?zwaM@6Q(j+y{%HHmf^!E}PIDHL1CpI;>@$GW8)l=jwfKAsB-^u1#vHsaa`t zU~F7Z5cM&Xq;+>)EJInWG44g?T*Fl3hU2y}G;M%w@r<3+P4$rOYMr*7O&XTw>aBYz zcw5}grqyxX9m{Hl<*1rv*{pLR|Asm8)B8FgF!Za+y z-OJ#MHI6VSw|jRXN(1f3gBN>V{N}Addslt@<HI78E8RcQfsNlhi zgP}s12fzJ6bWr2uyZ4*Uyu-=m-yXSmnn>u*nU!6OoLu_#rS=P)46U3_k8|>-d3`m( z$w1u7Omm|4EdTyACl4R1J^4D3y4Cqu-3pQ5wO3}JJIKkO=cc#UaI!je<^@`FSKVKk z>zB@Pa^>ug+5(@aVZL|vYcDH3*nNYOfyom;e8kE3SHFED&Pn^bf9&7R$;_9bgEZ)> zkA9f@c+MmHe0?Q+aK3d*WQsq^?JkzOy+_@tuYTravhq~4gQ0%HcB#5*!ZuB63U8M> z*T%eghN+4sHNrrOp&>RZwrX7?wQlH`2p+{q7>Dr~L+qBV{h!W>#`BBeUB%m&`b5f1 z`l!hzeggxWAoQ%kT{Df6Eet!Crb@y~Q(;N24@;F&SPJ^?sF0VYs)J?zGO1Z=X3J*- zL5isM2Ys@C`Fx;&XYOQtzVh4mF~jDpp3)Mooz;^&Ok+YH%2}44r316>wA?%4GK7YW zH1&Qit25M4lxt-4UTe~Dj4@O1SnGWTeHWFPn4UeXQ4B+P*gLuh3~s9O zbBt`50WEYM{9YNV`SBq4BsmF3VUPys?*u(xg#vOujFYP+%76Y(5t$=pn4mk41&Z?d zh5yM9q8KIgoU+**&B*IiDa&6#?rZWbKt8G=GcKa=(HH1PF;a9#YUMKv;l13ib(5 zBtVCPP5~kU>{rk&K(PS5H$jO2eF_eVh*ALt6byRc0*-u!$R{@|`YN`16$K)wqzc|e z8lyVyCVdd4I6&G%-<=WCBZwkJYM>0ykUo!1c#ZTF6ud_|O&_Tl(pi*afpifSxI%iB zX8wuv7gXUI=?zrlA4Hgh8YYuQX-6fPvyINYZG1?}jNQUCw| literal 0 HcmV?d00001 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