This commit is contained in:
pythagodzilla 2026-03-19 17:57:03 +08:00
commit 07cb1304d2
49 changed files with 2324 additions and 0 deletions

10
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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噪声处理器
- **职责**:管理并应用各种噪声
- **已实现**GpsNoiseEngineGPS信号误差 + 漂移)
- **预留**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
View 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.

91
src/main/kotlin/Main.kt Normal file
View 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()
*/

View 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
}

View 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)
}
}

View 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: 实现参数设置逻辑
// 可考虑动态调整噪声引擎参数
}
}

View 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 // 转换为米
}
}

View 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噪声大小
)

View 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²- 预留扩展
)

View 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
}
}

View 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
)
}
}

View 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
View 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>