trackswap 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +328 -281
- package/dist/FIT/base.d.ts +0 -30
- package/dist/FIT/decoder.d.ts +0 -48
- package/dist/FIT/encoder.d.ts +0 -97
- package/dist/FIT/mesg.d.ts +0 -3
- package/dist/FIT/middleware.d.ts +0 -15
- package/dist/FIT/plugins.d.ts +0 -9
- package/dist/FIT/processor.d.ts +0 -24
- package/dist/FIT/types/mesg_num.d.ts +102 -102
- package/dist/FIT/types/sport.d.ts +44 -44
- package/dist/FIT/types.d.ts +0 -35
- package/dist/GPX/base.d.ts +6 -50
- package/dist/GPX/converters.d.ts +0 -30
- package/dist/GPX/decoder.d.ts +0 -66
- package/dist/GPX/encoder.d.ts +0 -109
- package/dist/GPX/middleware.d.ts +0 -9
- package/dist/GPX/processor.d.ts +0 -18
- package/dist/GPX/types.d.ts +0 -108
- package/dist/TCX/base.d.ts +0 -33
- package/dist/TCX/converters.d.ts +0 -24
- package/dist/TCX/decoder.d.ts +0 -54
- package/dist/TCX/encoder.d.ts +0 -109
- package/dist/TCX/index.d.ts +4 -2
- package/dist/TCX/middleware.d.ts +11 -13
- package/dist/TCX/processor.d.ts +13 -21
- package/dist/TCX/types.d.ts +1 -13
- package/dist/TrackSwap.d.ts +37 -33
- package/dist/core/base.d.ts +0 -46
- package/dist/core/xml-utils.d.ts +0 -24
- package/dist/index.cjs +26 -0
- package/dist/index.js +26 -5
- package/dist/sport/base.d.ts +64 -0
- package/dist/sport/converters.d.ts +33 -0
- package/dist/sport/encoders.d.ts +34 -0
- package/dist/sport/index.d.ts +17 -0
- package/dist/sport/processor.d.ts +19 -0
- package/dist/types.d.ts +91 -13
- package/package.json +23 -5
- package/dist/FIT/base.js +0 -101
- package/dist/FIT/crc.js +0 -16
- package/dist/FIT/decoder.js +0 -323
- package/dist/FIT/encoder.js +0 -445
- package/dist/FIT/index.js +0 -13
- package/dist/FIT/mesg.js +0 -208
- package/dist/FIT/middleware.js +0 -206
- package/dist/FIT/plugins.js +0 -104
- package/dist/FIT/processor.js +0 -170
- package/dist/FIT/types/course_point.js +0 -58
- package/dist/FIT/types/event.js +0 -50
- package/dist/FIT/types/event_type.js +0 -19
- package/dist/FIT/types/file.js +0 -24
- package/dist/FIT/types/index.js +0 -297
- package/dist/FIT/types/mesg_num.js +0 -106
- package/dist/FIT/types/sport.js +0 -48
- package/dist/FIT/types.js +0 -1
- package/dist/FIT/util.js +0 -7
- package/dist/GPX/base.js +0 -56
- package/dist/GPX/converters.js +0 -352
- package/dist/GPX/decoder.js +0 -409
- package/dist/GPX/encoder.js +0 -513
- package/dist/GPX/extensions/index.js +0 -14
- package/dist/GPX/index.js +0 -11
- package/dist/GPX/middleware.js +0 -93
- package/dist/GPX/processor.js +0 -188
- package/dist/GPX/types.js +0 -1
- package/dist/GPX/util.js +0 -18
- package/dist/TCX/base.js +0 -62
- package/dist/TCX/converters.js +0 -333
- package/dist/TCX/decoder.js +0 -350
- package/dist/TCX/encoder.js +0 -513
- package/dist/TCX/index.js +0 -9
- package/dist/TCX/middleware.js +0 -93
- package/dist/TCX/processor.js +0 -197
- package/dist/TCX/types.js +0 -1
- package/dist/TrackSwap.js +0 -84
- package/dist/core/base.js +0 -138
- package/dist/core/decoder.d.ts +0 -66
- package/dist/core/decoder.js +0 -90
- package/dist/core/index.js +0 -4
- package/dist/core/processor.d.ts +0 -69
- package/dist/core/processor.js +0 -265
- package/dist/core/xml-utils.js +0 -140
- package/dist/types.js +0 -1
package/README.md
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
# TrackSwap -
|
|
1
|
+
# TrackSwap - 多格式轨迹处理库
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个强大的多格式轨迹文件处理库,支持 GPX、FIT、TCX 文件的解析、编码和格式转换。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**🌐 Language / 语言**: [English](README_EN.md) | [中文](README.md)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🚀 特性
|
|
12
|
+
|
|
13
|
+
- 🔄 **多格式支持**: GPX 1.1、FIT、TCX 格式
|
|
14
|
+
- 🎯 **统一转换**: 任意格式之间相互转换
|
|
15
|
+
- 🤖 **自动检测**: 智能识别文件格式
|
|
16
|
+
- 🔧 **插件架构**: 基于插件的可扩展设计
|
|
17
|
+
- 🛡️ **类型安全**: 完整的 TypeScript 支持
|
|
18
|
+
- ⚡ **高性能**: 优化的解析和编码算法
|
|
19
|
+
|
|
20
|
+
## 📦 安装
|
|
8
21
|
|
|
9
22
|
```bash
|
|
10
23
|
npm install trackswap
|
|
11
24
|
```
|
|
12
25
|
|
|
13
|
-
##
|
|
26
|
+
## 🎯 快速开始
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
### 基本使用
|
|
16
29
|
|
|
17
30
|
```typescript
|
|
18
31
|
import { TrackSwap } from 'trackswap';
|
|
@@ -20,364 +33,398 @@ import fs from 'fs';
|
|
|
20
33
|
|
|
21
34
|
const trackSwap = new TrackSwap();
|
|
22
35
|
|
|
23
|
-
// 解析GPX文件
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const gpxData = await trackSwap.parseGPX(buffer);
|
|
27
|
-
|
|
28
|
-
console.log('GPX版本:', gpxData?.version);
|
|
29
|
-
console.log('轨迹数量:', gpxData?.trk?.length);
|
|
30
|
-
}
|
|
36
|
+
// 解析 GPX 文件
|
|
37
|
+
const gpxBuffer = fs.readFileSync('track.gpx');
|
|
38
|
+
const gpxData = await trackSwap.parseGPX(gpxBuffer);
|
|
31
39
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<gpx version="1.1" creator="TrackSwap">
|
|
36
|
-
<wpt lat="39.9042" lon="116.4074">
|
|
37
|
-
<name>北京</name>
|
|
38
|
-
</wpt>
|
|
39
|
-
</gpx>`;
|
|
40
|
-
|
|
41
|
-
const gpxData = await trackSwap.parseGPXString(xmlString);
|
|
42
|
-
console.log('航路点:', gpxData?.wpt?.[0]?.name);
|
|
43
|
-
}
|
|
44
|
-
```
|
|
40
|
+
// 解析 FIT 文件
|
|
41
|
+
const fitBuffer = fs.readFileSync('activity.fit');
|
|
42
|
+
const fitData = await trackSwap.parseFIT(fitBuffer);
|
|
45
43
|
|
|
44
|
+
// 解析 TCX 文件
|
|
45
|
+
const tcxBuffer = fs.readFileSync('workout.tcx');
|
|
46
|
+
const tcxData = await trackSwap.parseTCX(tcxBuffer);
|
|
47
|
+
```
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
### 格式转换
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
```typescript
|
|
52
|
+
// 🔄 自动检测格式并转换
|
|
53
|
+
const gpxBuffer = await trackSwap.convertFile(anyBuffer, 'gpx');
|
|
54
|
+
const fitBuffer = await trackSwap.convertFile(anyBuffer, 'fit');
|
|
55
|
+
const tcxBuffer = await trackSwap.convertFile(anyBuffer, 'tcx');
|
|
56
|
+
|
|
57
|
+
// 🎯 指定源格式转换(提升性能)
|
|
58
|
+
const convertedBuffer = await trackSwap.convertFile(
|
|
59
|
+
sourceBuffer,
|
|
60
|
+
'fit', // 目标格式
|
|
61
|
+
'gpx' // 源格式(可选)
|
|
62
|
+
);
|
|
63
|
+
```
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|------|------|------|--------|
|
|
53
|
-
| `parseGPX(buffer)` | 解析GPX Buffer | `Buffer` | `Promise<GPX11Type \| undefined>` |
|
|
54
|
-
| `parseGPXString(xml)` | 解析GPX字符串 | `string` | `Promise<GPX11Type \| undefined>` |
|
|
55
|
-
| `encodeGPX(data)` | 编码为Buffer | `GPX11Type` | `Promise<Buffer>` |
|
|
56
|
-
| `encodeGPXString(data)` | 编码为字符串 | `GPX11Type` | `string` |
|
|
57
|
-
| `getDecoder()` | 获取解码器实例 | - | `GPXDecoder` |
|
|
58
|
-
| `getEncoder()` | 获取编码器实例 | - | `GPXEncoder` |
|
|
59
|
-
| `destroy()` | 清理资源 | - | `Promise<void>` |
|
|
65
|
+
### 自动解析
|
|
60
66
|
|
|
67
|
+
```typescript
|
|
68
|
+
// 🤖 自动检测格式并解析
|
|
69
|
+
const parsedData = await trackSwap.parseFile(buffer);
|
|
61
70
|
|
|
71
|
+
// 🏃♂️ 解析并转换为统一的运动数据格式
|
|
72
|
+
const sportData = await trackSwap.parseToSport(buffer);
|
|
73
|
+
```
|
|
62
74
|
|
|
63
|
-
|
|
75
|
+
## 📖 详细使用指南
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
### 1. 文件解析
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
70
|
-
│ GPX解码器架构图 │
|
|
71
|
-
├─────────────────────────────────────────────────────────────┤
|
|
72
|
-
│ │
|
|
73
|
-
│ 🔧 固定核心处理器 (不可修改) │
|
|
74
|
-
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
75
|
-
│ │ 1. TokenizeProcessor - XML → Tokens │ │
|
|
76
|
-
│ │ 2. AstGenerateProcessor - Tokens → AST │ │
|
|
77
|
-
│ │ 3. ConvertProcessor - AST → GPX对象 │ │
|
|
78
|
-
│ │ 4. CompleteProcessor - 最终化处理 │ │
|
|
79
|
-
│ └─────────────────────────────────────────────────────────┘ │
|
|
80
|
-
│ │
|
|
81
|
-
│ 🔌 可扩展中间件 (真正的扩展点) │
|
|
82
|
-
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
83
|
-
│ │ • onTokenize() - Token增强、过滤 │ │
|
|
84
|
-
│ │ • onAstGenerate() - AST结构增强 │ │
|
|
85
|
-
│ │ • onConvert() - 转换结果增强 │ │
|
|
86
|
-
│ │ • onComplete() - 最终结果处理 │ │
|
|
87
|
-
│ │ • onError() - 错误处理 │ │
|
|
88
|
-
│ └─────────────────────────────────────────────────────────┘ │
|
|
89
|
-
│ │
|
|
90
|
-
│ 📦 转换器插件 (支持优先级) │
|
|
91
|
-
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
92
|
-
│ │ • 默认转换器:wpt, rte, trk, metadata... │ │
|
|
93
|
-
│ │ • 自定义转换器:扩展标签、覆盖默认行为 │ │
|
|
94
|
-
│ │ • 优先级系统:多转换器处理同一标签 │ │
|
|
95
|
-
│ └─────────────────────────────────────────────────────────┘ │
|
|
96
|
-
│ │
|
|
97
|
-
└─────────────────────────────────────────────────────────────┘
|
|
98
|
-
|
|
99
|
-
## 📋 快速开始
|
|
79
|
+
#### GPX 文件处理
|
|
100
80
|
|
|
101
81
|
```typescript
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
// 从 Buffer 解析
|
|
83
|
+
const gpxData = await trackSwap.parseGPX(buffer);
|
|
104
84
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await registerDefaultMiddlewares(decoder);
|
|
85
|
+
// 从字符串解析
|
|
86
|
+
const gpxData = await trackSwap.parseGPXString(xmlString);
|
|
108
87
|
|
|
109
|
-
//
|
|
110
|
-
const buffer =
|
|
111
|
-
const gpxData = await decoder.parserByBuffer(buffer);
|
|
88
|
+
// 编码为 Buffer
|
|
89
|
+
const buffer = await trackSwap.encodeGPX(gpxData);
|
|
112
90
|
|
|
113
|
-
//
|
|
114
|
-
|
|
91
|
+
// 编码为字符串
|
|
92
|
+
const xmlString = trackSwap.encodeGPXString(gpxData);
|
|
115
93
|
```
|
|
116
94
|
|
|
95
|
+
#### FIT 文件处理
|
|
117
96
|
|
|
118
|
-
|
|
97
|
+
```typescript
|
|
98
|
+
// 解析 FIT 文件
|
|
99
|
+
const fitData = await trackSwap.parseFIT(buffer);
|
|
119
100
|
|
|
120
|
-
|
|
101
|
+
// 编码为 Activity 文件
|
|
102
|
+
const activityBuffer = await trackSwap.encodeFIT(fitData);
|
|
121
103
|
|
|
122
|
-
|
|
104
|
+
// 编码为 Course 文件
|
|
105
|
+
const courseBuffer = await trackSwap.encodeFITCourse(fitData);
|
|
106
|
+
```
|
|
123
107
|
|
|
124
|
-
####
|
|
125
|
-
- `TokenizeProcessor`: XML → Token数组
|
|
126
|
-
- `AstGenerateProcessor`: Token数组 → AST
|
|
127
|
-
- `ConvertProcessor`: AST → GPX对象
|
|
128
|
-
- `CompleteProcessor`: 性能统计和清理
|
|
108
|
+
#### TCX 文件处理
|
|
129
109
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- `TrkConverter`: 处理 `<trk>` 轨迹标签
|
|
134
|
-
- `TrksegConverter`: 处理 `<trkseg>` 轨迹段标签
|
|
135
|
-
- `MetadataConverter`: 处理 `<metadata>` 元数据标签
|
|
136
|
-
- `LinkConverter`: 处理 `<link>` 链接标签
|
|
137
|
-
- `PersonConverter`: 处理 `<author>` 作者标签
|
|
138
|
-
- `EmailConverter`: 处理 `<email>` 邮箱标签
|
|
139
|
-
- `CopyrightConverter`: 处理 `<copyright>` 版权标签
|
|
140
|
-
- `BoundsConverter`: 处理 `<bounds>` 边界标签
|
|
110
|
+
```typescript
|
|
111
|
+
// 从 Buffer 解析
|
|
112
|
+
const tcxData = await trackSwap.parseTCX(buffer);
|
|
141
113
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- **中间件**: 在流水线各阶段插入自定义逻辑
|
|
114
|
+
// 从字符串解析
|
|
115
|
+
const tcxData = await trackSwap.parseTCXString(xmlString);
|
|
145
116
|
|
|
146
|
-
|
|
117
|
+
// 编码为 Buffer
|
|
118
|
+
const buffer = await trackSwap.encodeTCX(tcxData);
|
|
147
119
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
↓ ↓ ↓ ↓ ↓
|
|
151
|
-
中间件钩子 中间件钩子 中间件钩子 中间件钩子 中间件钩子
|
|
120
|
+
// 编码为字符串
|
|
121
|
+
const xmlString = await trackSwap.encodeTCXString(tcxData);
|
|
152
122
|
```
|
|
153
123
|
|
|
154
|
-
|
|
124
|
+
### 2. 格式转换
|
|
155
125
|
|
|
156
|
-
|
|
126
|
+
#### 统一转换方法
|
|
157
127
|
|
|
158
128
|
```typescript
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class MyConverter extends BaseConverter {
|
|
162
|
-
name = 'my-converter';
|
|
163
|
-
supportedTags = ['custom-tag'];
|
|
164
|
-
priority = 50; // 优先级,数字越小优先级越高
|
|
165
|
-
|
|
166
|
-
convert(ast: TokenAST, context: DecoderContext): MyDataType | undefined {
|
|
167
|
-
// 验证必要属性
|
|
168
|
-
if (!this.hasRequiredAttributes(ast, ['required-attr'])) {
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const result: MyDataType = {
|
|
173
|
-
value: this.parseFloat(ast.value),
|
|
174
|
-
name: this.parseString(ast.attributes?.name)
|
|
175
|
-
};
|
|
129
|
+
// 🔄 自动检测并转换
|
|
130
|
+
const result = await trackSwap.convertFile(sourceBuffer, 'gpx');
|
|
176
131
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
'child-tag': (child, target) => {
|
|
180
|
-
target.childValue = this.parseString(child.value);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
132
|
+
// 🎯 指定格式转换
|
|
133
|
+
const result = await trackSwap.convertFile(sourceBuffer, 'fit', 'gpx');
|
|
183
134
|
|
|
184
|
-
|
|
185
|
-
|
|
135
|
+
// 📊 支持的转换组合
|
|
136
|
+
// GPX ↔ FIT ↔ TCX (任意格式之间相互转换)
|
|
137
|
+
```
|
|
186
138
|
|
|
187
|
-
|
|
188
|
-
async initialize(context: DecoderContext): Promise<void> {
|
|
189
|
-
console.log(`${this.name} 转换器初始化`);
|
|
190
|
-
}
|
|
139
|
+
#### SportFileType 统一格式
|
|
191
140
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
141
|
+
```typescript
|
|
142
|
+
// 转换为统一的运动数据格式
|
|
143
|
+
const gpxSport = await trackSwap.convertGPXToSport(gpxData);
|
|
144
|
+
const fitSport = await trackSwap.convertFITToSport(fitData);
|
|
145
|
+
const tcxSport = await trackSwap.convertTCXToSport(tcxData);
|
|
146
|
+
|
|
147
|
+
// 从统一格式转换回具体格式
|
|
148
|
+
const gpxData = await trackSwap.convertSportToGPX(sportData);
|
|
149
|
+
const fitData = await trackSwap.convertSportToFIT(sportData);
|
|
150
|
+
const tcxData = await trackSwap.convertSportToTCX(sportData);
|
|
196
151
|
```
|
|
197
152
|
|
|
198
|
-
###
|
|
153
|
+
### 3. 智能解析
|
|
199
154
|
|
|
200
155
|
```typescript
|
|
201
|
-
|
|
202
|
-
|
|
156
|
+
// 🤖 自动检测格式
|
|
157
|
+
const format = trackSwap.detectFormat(buffer);
|
|
158
|
+
console.log(`检测到格式: ${format}`); // 'gpx' | 'fit' | 'tcx' | 'unknown'
|
|
203
159
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
160
|
+
// 📂 统一解析方法
|
|
161
|
+
const parsedData = await trackSwap.parseFile(buffer);
|
|
162
|
+
const parsedData = await trackSwap.parseFile(buffer, 'gpx'); // 指定格式
|
|
207
163
|
|
|
208
|
-
|
|
164
|
+
// 🏃♂️ 解析为统一格式
|
|
165
|
+
const sportData = await trackSwap.parseToSport(buffer);
|
|
166
|
+
const sportData = await trackSwap.parseToSport(buffer, 'fit'); // 指定格式
|
|
167
|
+
```
|
|
209
168
|
|
|
210
|
-
|
|
169
|
+
## 🔧 API 参考
|
|
211
170
|
|
|
212
|
-
|
|
213
|
-
import { BaseMiddleware } from './decoder';
|
|
171
|
+
### TrackSwap 类
|
|
214
172
|
|
|
215
|
-
|
|
216
|
-
name = 'my-middleware';
|
|
217
|
-
description = '我的自定义中间件';
|
|
173
|
+
#### 解析方法
|
|
218
174
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
175
|
+
| 方法 | 说明 | 参数 | 返回值 |
|
|
176
|
+
|------|------|------|--------|
|
|
177
|
+
| `parseGPX(buffer)` | 解析 GPX Buffer | `Buffer` | `Promise<GPX11Type \| undefined>` |
|
|
178
|
+
| `parseGPXString(xml)` | 解析 GPX 字符串 | `string` | `Promise<GPX11Type \| undefined>` |
|
|
179
|
+
| `parseFIT(buffer)` | 解析 FIT Buffer | `Buffer` | `Promise<FITFileType>` |
|
|
180
|
+
| `parseTCX(buffer)` | 解析 TCX Buffer | `Buffer` | `Promise<TCXFileType>` |
|
|
181
|
+
| `parseTCXString(xml)` | 解析 TCX 字符串 | `string` | `Promise<TCXFileType>` |
|
|
182
|
+
| `parseFile(buffer, type?)` | 统一解析方法 | `Buffer, string?` | `Promise<GPX11Type \| FITFileType \| TCXFileType>` |
|
|
183
|
+
| `parseToSport(buffer, type?)` | 解析为统一格式 | `Buffer, string?` | `Promise<SportFileType>` |
|
|
225
184
|
|
|
226
|
-
|
|
227
|
-
async onAstGenerate(ast: TokenAST, context: DecoderContext): Promise<TokenAST> {
|
|
228
|
-
// 可以修改AST结构
|
|
229
|
-
this.addCustomProperties(ast);
|
|
230
|
-
return ast;
|
|
231
|
-
}
|
|
185
|
+
#### 编码方法
|
|
232
186
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
187
|
+
| 方法 | 说明 | 参数 | 返回值 |
|
|
188
|
+
|------|------|------|--------|
|
|
189
|
+
| `encodeGPX(data)` | 编码 GPX 为 Buffer | `GPX11Type` | `Promise<Buffer>` |
|
|
190
|
+
| `encodeGPXString(data)` | 编码 GPX 为字符串 | `GPX11Type` | `string` |
|
|
191
|
+
| `encodeFIT(data)` | 编码 FIT 为 Buffer | `FITFileType` | `Promise<Buffer>` |
|
|
192
|
+
| `encodeFITCourse(data)` | 编码 FIT Course | `FITFileType` | `Promise<Buffer>` |
|
|
193
|
+
| `encodeTCX(data)` | 编码 TCX 为 Buffer | `TCXFileType` | `Promise<Buffer>` |
|
|
194
|
+
| `encodeTCXString(data)` | 编码 TCX 为字符串 | `TCXFileType` | `Promise<string>` |
|
|
239
195
|
|
|
240
|
-
|
|
241
|
-
async onComplete(result: GPX11Type, context: DecoderContext): Promise<GPX11Type> {
|
|
242
|
-
// 最终处理
|
|
243
|
-
const performance = context.metadata.get('performance');
|
|
244
|
-
console.log('处理完成,性能指标:', performance);
|
|
245
|
-
return result;
|
|
246
|
-
}
|
|
196
|
+
#### 转换方法
|
|
247
197
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
198
|
+
| 方法 | 说明 | 参数 | 返回值 |
|
|
199
|
+
|------|------|------|--------|
|
|
200
|
+
| `convertFile(buffer, target, source?)` | 统一转换方法 | `Buffer, string, string?` | `Promise<Buffer>` |
|
|
201
|
+
| `convertGPXToSport(data)` | GPX → Sport | `GPX11Type` | `Promise<SportFileType>` |
|
|
202
|
+
| `convertFITToSport(data)` | FIT → Sport | `FITFileType` | `Promise<SportFileType>` |
|
|
203
|
+
| `convertTCXToSport(data)` | TCX → Sport | `TCXFileType` | `Promise<SportFileType>` |
|
|
204
|
+
| `convertSportToGPX(data)` | Sport → GPX | `SportFileType` | `Promise<GPX11Type>` |
|
|
205
|
+
| `convertSportToFIT(data)` | Sport → FIT | `SportFileType` | `Promise<FITFileType>` |
|
|
206
|
+
| `convertSportToTCX(data)` | Sport → TCX | `SportFileType` | `Promise<TCXFileType>` |
|
|
253
207
|
|
|
254
|
-
|
|
255
|
-
// 添加自定义属性逻辑
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
```
|
|
208
|
+
#### 工具方法
|
|
259
209
|
|
|
260
|
-
|
|
210
|
+
| 方法 | 说明 | 参数 | 返回值 |
|
|
211
|
+
|------|------|------|--------|
|
|
212
|
+
| `detectFormat(buffer)` | 检测文件格式 | `Buffer` | `'gpx' \| 'fit' \| 'tcx' \| 'unknown'` |
|
|
213
|
+
| `getSportProcessor()` | 获取运动处理器 | - | `SportProcessor` |
|
|
214
|
+
| `destroy()` | 清理资源 | - | `Promise<void>` |
|
|
261
215
|
|
|
262
|
-
|
|
263
|
-
const decoder = new GPXDecoder();
|
|
264
|
-
await decoder.registerMiddleware(new MyMiddleware());
|
|
216
|
+
## 🌟 实际应用示例
|
|
265
217
|
|
|
266
|
-
|
|
267
|
-
```
|
|
218
|
+
### 批量格式转换
|
|
268
219
|
|
|
269
|
-
|
|
220
|
+
```typescript
|
|
221
|
+
import { TrackSwap } from 'trackswap';
|
|
222
|
+
import fs from 'fs';
|
|
223
|
+
import path from 'path';
|
|
270
224
|
|
|
271
|
-
|
|
225
|
+
async function batchConvert(inputDir: string, outputFormat: 'gpx' | 'fit' | 'tcx') {
|
|
226
|
+
const trackSwap = new TrackSwap();
|
|
227
|
+
const files = fs.readdirSync(inputDir);
|
|
228
|
+
|
|
229
|
+
for (const file of files) {
|
|
230
|
+
try {
|
|
231
|
+
const inputPath = path.join(inputDir, file);
|
|
232
|
+
const buffer = fs.readFileSync(inputPath);
|
|
233
|
+
|
|
234
|
+
// 🔄 自动检测并转换
|
|
235
|
+
const converted = await trackSwap.convertFile(buffer, outputFormat);
|
|
236
|
+
|
|
237
|
+
const outputPath = path.join(inputDir,
|
|
238
|
+
`${path.parse(file).name}.${outputFormat}`);
|
|
239
|
+
fs.writeFileSync(outputPath, converted);
|
|
240
|
+
|
|
241
|
+
console.log(`✅ 转换完成: ${file} → ${outputFormat}`);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`❌ 转换失败: ${file}`, error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await trackSwap.destroy();
|
|
248
|
+
}
|
|
272
249
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const decoder = new GPXDecoder();
|
|
276
|
-
const result = await decoder.parserByBuffer(buffer);
|
|
250
|
+
// 使用示例
|
|
251
|
+
await batchConvert('./tracks', 'gpx');
|
|
277
252
|
```
|
|
278
253
|
|
|
279
|
-
###
|
|
254
|
+
### 轨迹数据分析
|
|
280
255
|
|
|
281
256
|
```typescript
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
257
|
+
async function analyzeTrack(buffer: Buffer) {
|
|
258
|
+
const trackSwap = new TrackSwap();
|
|
259
|
+
|
|
260
|
+
// 🔍 检测格式
|
|
261
|
+
const format = trackSwap.detectFormat(buffer);
|
|
262
|
+
console.log(`文件格式: ${format}`);
|
|
263
|
+
|
|
264
|
+
// 📊 转换为统一格式进行分析
|
|
265
|
+
const sportData = await trackSwap.parseToSport(buffer);
|
|
266
|
+
|
|
267
|
+
// 分析数据
|
|
268
|
+
const analysis = {
|
|
269
|
+
totalDistance: sportData.summary?.totalDistance || 0,
|
|
270
|
+
totalTime: sportData.summary?.totalTime || 0,
|
|
271
|
+
avgSpeed: sportData.summary?.avgSpeed || 0,
|
|
272
|
+
maxSpeed: sportData.summary?.maxSpeed || 0,
|
|
273
|
+
trackPoints: sportData.tracks?.[0]?.segments?.[0]?.points?.length || 0
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
console.log('轨迹分析结果:', analysis);
|
|
277
|
+
|
|
278
|
+
await trackSwap.destroy();
|
|
279
|
+
return analysis;
|
|
280
|
+
}
|
|
287
281
|
```
|
|
288
282
|
|
|
289
|
-
###
|
|
283
|
+
### 多格式数据融合
|
|
290
284
|
|
|
291
285
|
```typescript
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return extension;
|
|
286
|
+
async function mergeMultiFormatTracks(files: { buffer: Buffer, name: string }[]) {
|
|
287
|
+
const trackSwap = new TrackSwap();
|
|
288
|
+
const allSportData: SportFileType[] = [];
|
|
289
|
+
|
|
290
|
+
// 🔄 将所有格式转换为统一格式
|
|
291
|
+
for (const file of files) {
|
|
292
|
+
try {
|
|
293
|
+
const sportData = await trackSwap.parseToSport(file.buffer);
|
|
294
|
+
sportData.metadata = { ...sportData.metadata, originalFile: file.name };
|
|
295
|
+
allSportData.push(sportData);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error(`解析失败: ${file.name}`, error);
|
|
298
|
+
}
|
|
307
299
|
}
|
|
300
|
+
|
|
301
|
+
// 融合数据逻辑
|
|
302
|
+
const mergedData = mergeSportData(allSportData);
|
|
303
|
+
|
|
304
|
+
// 输出为不同格式
|
|
305
|
+
const results = {
|
|
306
|
+
gpx: await trackSwap.convertSportToGPX(mergedData),
|
|
307
|
+
fit: await trackSwap.convertSportToFIT(mergedData),
|
|
308
|
+
tcx: await trackSwap.convertSportToTCX(mergedData)
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
await trackSwap.destroy();
|
|
312
|
+
return results;
|
|
308
313
|
}
|
|
309
|
-
|
|
310
|
-
// 注册到已有解码器
|
|
311
|
-
const decoder = new GPXDecoder();
|
|
312
|
-
await decoder.registerConverter(new GarminExtensionConverter());
|
|
313
314
|
```
|
|
314
315
|
|
|
315
|
-
##
|
|
316
|
-
|
|
317
|
-
```typescript
|
|
318
|
-
const decoder = new GPXDecoder();
|
|
316
|
+
## 🏗️ 架构设计
|
|
319
317
|
|
|
320
|
-
|
|
321
|
-
console.log('默认转换器:', decoder.listConverters());
|
|
322
|
-
// 输出: ['wpt', 'trkpt', 'rtept', 'rte', 'trk', 'trkseg', 'metadata', 'link', 'author', 'email', 'copyright', 'bounds']
|
|
318
|
+
TrackSwap 采用模块化架构,核心组件包括:
|
|
323
319
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
320
|
+
```
|
|
321
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
322
|
+
│ TrackSwap 架构图 │
|
|
323
|
+
├─────────────────────────────────────────────────────────────┤
|
|
324
|
+
│ │
|
|
325
|
+
│ 🔧 核心处理模块 │
|
|
326
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
327
|
+
│ │ • GPXDecoder/Encoder - GPX 文件处理 │ │
|
|
328
|
+
│ │ • FITDecoder/Encoder - FIT 文件处理 │ │
|
|
329
|
+
│ │ • TCXDecoder/Encoder - TCX 文件处理 │ │
|
|
330
|
+
│ │ • SportProcessor - 统一格式转换处理 │ │
|
|
331
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
332
|
+
│ │
|
|
333
|
+
│ 🔄 转换引擎 │
|
|
334
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
335
|
+
│ │ • GPXToSportConverter - GPX → SportFileType │ │
|
|
336
|
+
│ │ • FITToSportConverter - FIT → SportFileType │ │
|
|
337
|
+
│ │ • TCXToSportConverter - TCX → SportFileType │ │
|
|
338
|
+
│ │ • SportToGPXEncoder - SportFileType → GPX │ │
|
|
339
|
+
│ │ • SportToFITEncoder - SportFileType → FIT │ │
|
|
340
|
+
│ │ • SportToTCXEncoder - SportFileType → TCX │ │
|
|
341
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
342
|
+
│ │
|
|
343
|
+
│ 🎯 统一接口 │
|
|
344
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
345
|
+
│ │ • 自动格式检测 │ │
|
|
346
|
+
│ │ • 统一解析方法 │ │
|
|
347
|
+
│ │ • 统一转换方法 │ │
|
|
348
|
+
│ │ • 生命周期管理 │ │
|
|
349
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
350
|
+
│ │
|
|
351
|
+
└─────────────────────────────────────────────────────────────┘
|
|
352
|
+
```
|
|
327
353
|
|
|
328
|
-
|
|
329
|
-
console.log('所有转换器:', decoder.listConverters());
|
|
330
|
-
console.log('所有中间件:', decoder.listMiddlewares());
|
|
354
|
+
## 🔍 支持的格式
|
|
331
355
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
356
|
+
### GPX (GPS Exchange Format)
|
|
357
|
+
- ✅ GPX 1.1 标准
|
|
358
|
+
- ✅ 航路点 (Waypoints)
|
|
359
|
+
- ✅ 路线 (Routes)
|
|
360
|
+
- ✅ 轨迹 (Tracks)
|
|
361
|
+
- ✅ 元数据 (Metadata)
|
|
362
|
+
- ✅ 扩展属性
|
|
335
363
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
364
|
+
### FIT (Flexible and Interoperable Data Transfer)
|
|
365
|
+
- ✅ Activity 文件
|
|
366
|
+
- ✅ Course 文件
|
|
367
|
+
- ✅ 轨迹点数据
|
|
368
|
+
- ✅ 心率、速度、海拔等传感器数据
|
|
369
|
+
- ✅ Garmin 设备兼容
|
|
339
370
|
|
|
340
|
-
|
|
371
|
+
### TCX (Training Center XML)
|
|
372
|
+
- ✅ 训练数据
|
|
373
|
+
- ✅ 运动轨迹
|
|
374
|
+
- ✅ 心率区间
|
|
375
|
+
- ✅ 圈速数据
|
|
376
|
+
- ✅ Garmin Connect 兼容
|
|
341
377
|
|
|
342
|
-
|
|
378
|
+
## 🛠️ 开发指南
|
|
343
379
|
|
|
344
|
-
|
|
345
|
-
- **错误处理**: 统一的错误处理机制
|
|
346
|
-
- **插件状态**: 跟踪插件的生命周期状态
|
|
347
|
-
- **上下文传递**: 丰富的上下文信息便于调试
|
|
348
|
-
- **开箱即用**: 创建即可工作,无需配置
|
|
380
|
+
### 环境要求
|
|
349
381
|
|
|
350
|
-
|
|
382
|
+
- Node.js >= 16
|
|
383
|
+
- TypeScript >= 4.5
|
|
351
384
|
|
|
352
|
-
###
|
|
385
|
+
### 项目结构
|
|
353
386
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
387
|
+
```
|
|
388
|
+
src/
|
|
389
|
+
├── TrackSwap.ts # 主入口类
|
|
390
|
+
├── GPX/ # GPX 模块
|
|
391
|
+
│ ├── decoder.ts
|
|
392
|
+
│ ├── encoder.ts
|
|
393
|
+
│ └── types.ts
|
|
394
|
+
├── FIT/ # FIT 模块
|
|
395
|
+
│ ├── decoder.ts
|
|
396
|
+
│ ├── encoder.ts
|
|
397
|
+
│ └── types.ts
|
|
398
|
+
├── TCX/ # TCX 模块
|
|
399
|
+
│ ├── decoder.ts
|
|
400
|
+
│ ├── encoder.ts
|
|
401
|
+
│ └── types.ts
|
|
402
|
+
├── sport/ # 统一转换模块
|
|
403
|
+
│ ├── processor.ts
|
|
404
|
+
│ ├── converters.ts
|
|
405
|
+
│ ├── encoders.ts
|
|
406
|
+
│ └── base.ts
|
|
407
|
+
└── types.ts # 公共类型定义
|
|
408
|
+
```
|
|
365
409
|
|
|
366
|
-
###
|
|
410
|
+
### 贡献指南
|
|
367
411
|
|
|
368
|
-
|
|
412
|
+
1. Fork 项目
|
|
413
|
+
2. 创建特性分支
|
|
414
|
+
3. 提交更改
|
|
415
|
+
4. 发起 Pull Request
|
|
369
416
|
|
|
370
|
-
|
|
417
|
+
## 📄 许可证
|
|
371
418
|
|
|
372
|
-
|
|
419
|
+
MIT License
|
|
373
420
|
|
|
374
|
-
##
|
|
421
|
+
## 🙏 致谢
|
|
375
422
|
|
|
376
|
-
|
|
423
|
+
感谢所有贡献者和开源社区的支持!
|
|
377
424
|
|
|
378
|
-
|
|
379
|
-
2. **按需扩展**: 只需要注册自定义转换器和中间件
|
|
380
|
-
3. **零配置**: 无需了解内部流水线和默认转换器
|
|
381
|
-
4. **完全可控**: 仍然支持完整的插件化定制能力
|
|
425
|
+
---
|
|
382
426
|
|
|
383
|
-
|
|
427
|
+
<div align="center">
|
|
428
|
+
<strong>TrackSwap - 让轨迹数据处理变得简单</strong><br>
|
|
429
|
+
🚀 高效 • 🔄 统一 • 🛡️ 安全 • 📈 可扩展
|
|
430
|
+
</div>
|