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