init
This commit is contained in:
commit
07cb1304d2
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Ask2AgentMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/dictionaries/project.xml
generated
Normal file
7
.idea/dictionaries/project.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>Coord</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
</project>
|
||||
23
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal file
23
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal file
@ -0,0 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/refactored-likeRunner.iml" filepath="$PROJECT_DIR$/.idea/refactored-likeRunner.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
13
.idea/refactored-likeRunner.iml
generated
Normal file
13
.idea/refactored-likeRunner.iml
generated
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
152
00_START_HERE.txt
Normal file
152
00_START_HERE.txt
Normal file
@ -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️⃣ 按照预留位置标记添加新功能 ║
|
||||
║ ║
|
||||
║ 祝你使用愉快! 🚀 ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════════════════════════════════╝
|
||||
361
BEFORE_AFTER.md
Normal file
361
BEFORE_AFTER.md
Normal file
@ -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<MetaCoordinate>) {
|
||||
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<MetaCoordinate>) {
|
||||
// 一次初始化,重复使用
|
||||
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<MetaCoordinate>) {
|
||||
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<MetaCoordinate>) {
|
||||
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<Double, Double, Double> { // ✅ 更清晰的返回类型
|
||||
|
||||
// 第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(预留位置已标记)
|
||||
216
DATA_FLOW_GUIDE.md
Normal file
216
DATA_FLOW_GUIDE.md
Normal file
@ -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)独立放置。
|
||||
172
QUICK_START.md
Normal file
172
QUICK_START.md
Normal file
@ -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 深入理解数据流
|
||||
- [ ] 根据需要修改或添加功能
|
||||
|
||||
祝你使用愉快!有问题随时查看相关文档。
|
||||
158
README.md
Normal file
158
README.md
Normal file
@ -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 导出功能
|
||||
- [ ] 不同运动模式支持
|
||||
116
STRUCTURE.txt
Normal file
116
STRUCTURE.txt
Normal file
@ -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
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════
|
||||
Binary file not shown.
BIN
out/production/refactored-likeRunner/MainKt.class
Normal file
BIN
out/production/refactored-likeRunner/MainKt.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
out/production/refactored-likeRunner/model/Coordinate.class
Normal file
BIN
out/production/refactored-likeRunner/model/Coordinate.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/model/MetaCoordinate.class
Normal file
BIN
out/production/refactored-likeRunner/model/MetaCoordinate.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/model/MotionState.class
Normal file
BIN
out/production/refactored-likeRunner/model/MotionState.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/model/NoisyCoordinate.class
Normal file
BIN
out/production/refactored-likeRunner/model/NoisyCoordinate.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/model/PhysicsState.class
Normal file
BIN
out/production/refactored-likeRunner/model/PhysicsState.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/model/RawCoordinate.class
Normal file
BIN
out/production/refactored-likeRunner/model/RawCoordinate.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
out/production/refactored-likeRunner/noise/GpsNoiseEngine.class
Normal file
BIN
out/production/refactored-likeRunner/noise/GpsNoiseEngine.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/noise/NoiseEngine.class
Normal file
BIN
out/production/refactored-likeRunner/noise/NoiseEngine.class
Normal file
Binary file not shown.
BIN
out/production/refactored-likeRunner/noise/NoiseProcessor.class
Normal file
BIN
out/production/refactored-likeRunner/noise/NoiseProcessor.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
91
src/main/kotlin/Main.kt
Normal file
91
src/main/kotlin/Main.kt
Normal file
@ -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<MotionState>()
|
||||
* 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()
|
||||
*/
|
||||
276
src/main/kotlin/core/HumanLikeDistanceEngine.kt
Normal file
276
src/main/kotlin/core/HumanLikeDistanceEngine.kt
Normal file
@ -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<SpeedSegment> {
|
||||
val segments = mutableListOf<SpeedSegment>()
|
||||
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
|
||||
}
|
||||
88
src/main/kotlin/core/RunnerPhysics.kt
Normal file
88
src/main/kotlin/core/RunnerPhysics.kt
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
134
src/main/kotlin/core/TrajectorySimulator.kt
Normal file
134
src/main/kotlin/core/TrajectorySimulator.kt
Normal file
@ -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<MetaCoordinate>) {
|
||||
|
||||
// 核心引擎(一次性创建,重复使用)
|
||||
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: 实现参数设置逻辑
|
||||
// 可考虑动态调整噪声引擎参数
|
||||
}
|
||||
}
|
||||
93
src/main/kotlin/core/trajectory/PathInterpolator.kt
Normal file
93
src/main/kotlin/core/trajectory/PathInterpolator.kt
Normal file
@ -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<MetaCoordinate>) {
|
||||
// 预计算:每个路段的起点、终点、以及该路段的距离
|
||||
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 // 转换为米
|
||||
}
|
||||
}
|
||||
39
src/main/kotlin/model/Coordinate.kt
Normal file
39
src/main/kotlin/model/Coordinate.kt
Normal file
@ -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噪声大小
|
||||
)
|
||||
32
src/main/kotlin/model/MotionState.kt
Normal file
32
src/main/kotlin/model/MotionState.kt
Normal file
@ -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²)- 预留扩展
|
||||
)
|
||||
126
src/main/kotlin/noise/GpsNoiseEngine.kt
Normal file
126
src/main/kotlin/noise/GpsNoiseEngine.kt
Normal file
@ -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<Double, Double, Double> {
|
||||
|
||||
// 第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
|
||||
}
|
||||
}
|
||||
81
src/main/kotlin/noise/NoiseProcessor.kt
Normal file
81
src/main/kotlin/noise/NoiseProcessor.kt
Normal file
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
28
src/main/kotlin/resources/data/SampleRoute.kt
Normal file
28
src/main/kotlin/resources/data/SampleRoute.kt
Normal file
@ -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)
|
||||
)
|
||||
76
test.kml
Normal file
76
test.kml
Normal file
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
|
||||
<Document>
|
||||
<name>真实的步行轨迹 (北京市昌平区)</name>
|
||||
<description>
|
||||
<![CDATA[
|
||||
此轨迹由原始传感器数据经卡尔曼滤波和平滑处理生成。
|
||||
运动方式:步行
|
||||
总距离:~180米
|
||||
总时长:3分钟
|
||||
平均速度:3.6 km/h
|
||||
]]>
|
||||
</description>
|
||||
|
||||
<Style id="multiTrack_style">
|
||||
<IconStyle>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
<LineStyle>
|
||||
<color>9900ffff</color> <width>6</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
|
||||
<Placemark>
|
||||
<name>步行路径 (带时间轴)</name>
|
||||
<styleUrl>#multiTrack_style</styleUrl>
|
||||
|
||||
<gx:Track>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<gx:interpolate>1</gx:interpolate>
|
||||
|
||||
<when>2026-03-19T00:00:00Z</when>
|
||||
<when>2026-03-19T00:00:10Z</when>
|
||||
<when>2026-03-19T00:00:20Z</when>
|
||||
<when>2026-03-19T00:00:30Z</when>
|
||||
<when>2026-03-19T00:00:40Z</when>
|
||||
<when>2026-03-19T00:00:50Z</when>
|
||||
<when>2026-03-19T00:01:00Z</when>
|
||||
<when>2026-03-19T00:01:10Z</when>
|
||||
<when>2026-03-19T00:01:20Z</when>
|
||||
<when>2026-03-19T00:01:30Z</when>
|
||||
<when>2026-03-19T00:01:40Z</when>
|
||||
<when>2026-03-19T00:01:50Z</when>
|
||||
<when>2026-03-19T00:02:00Z</when>
|
||||
<when>2026-03-19T00:02:10Z</when>
|
||||
<when>2026-03-19T00:02:20Z</when>
|
||||
<when>2026-03-19T00:02:30Z</when>
|
||||
<when>2026-03-19T00:02:40Z</when>
|
||||
<when>2026-03-19T00:02:50Z</when>
|
||||
<when>2026-03-19T00:03:00Z</when>
|
||||
|
||||
<gx:coord>116.136647 40.252336 35</gx:coord>
|
||||
<gx:coord>116.136686 40.252420 35</gx:coord>
|
||||
<gx:coord>116.136690 40.252510 35</gx:coord>
|
||||
<gx:coord>116.136650 40.252580 35</gx:coord>
|
||||
<gx:coord>116.136668 40.252696 35</gx:coord>
|
||||
<gx:coord>116.136658 40.252794 35</gx:coord>
|
||||
<gx:coord>116.136653 40.252915 35</gx:coord>
|
||||
<gx:coord>116.136650 40.252943 35</gx:coord>
|
||||
<gx:coord>116.136651 40.253087 35</gx:coord>
|
||||
<gx:coord>116.136635 40.253133 35</gx:coord>
|
||||
<gx:coord>116.136623 40.253246 35</gx:coord>
|
||||
<gx:coord>116.136624 40.253352 35</gx:coord>
|
||||
<gx:coord>116.136641 40.253407 35</gx:coord>
|
||||
<gx:coord>116.136617 40.253523 35</gx:coord>
|
||||
<gx:coord>116.136643 40.253594 35</gx:coord>
|
||||
<gx:coord>116.136625 40.253699 35</gx:coord>
|
||||
<gx:coord>116.136649 40.253785 35</gx:coord>
|
||||
<gx:coord>116.136660 40.253868 35</gx:coord>
|
||||
<gx:coord>116.136645 40.253948 35</gx:coord>
|
||||
</gx:Track>
|
||||
</Placemark>
|
||||
</Document>
|
||||
</kml>
|
||||
Loading…
x
Reference in New Issue
Block a user