trackswap 0.0.1

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.
@@ -0,0 +1,187 @@
1
+ import { Parser } from "htmlparser2";
2
+ /**
3
+ * 流水线阶段
4
+ */
5
+ export var PipelineStage;
6
+ (function (PipelineStage) {
7
+ PipelineStage["TOKENIZE"] = "tokenize";
8
+ PipelineStage["AST_GENERATE"] = "ast_generate";
9
+ PipelineStage["CONVERT"] = "convert";
10
+ PipelineStage["COMPLETE"] = "complete";
11
+ })(PipelineStage || (PipelineStage = {}));
12
+ /**
13
+ * Tokenize处理器
14
+ */
15
+ export class TokenizeProcessor {
16
+ constructor() {
17
+ this.stage = PipelineStage.TOKENIZE;
18
+ }
19
+ async process(context) {
20
+ const startTime = Date.now();
21
+ if (!context.xmlContent) {
22
+ throw new Error("XML内容不能为空");
23
+ }
24
+ const tokens = [];
25
+ const parser = new Parser({
26
+ onopentag(name, attribs) {
27
+ tokens.push({
28
+ type: "open",
29
+ tag: name,
30
+ attributes: attribs,
31
+ });
32
+ },
33
+ ontext(text) {
34
+ const filteredText = text.replace(/\n/g, "").trim();
35
+ if (filteredText) {
36
+ tokens.push({ type: "text", tag: "text", value: filteredText });
37
+ }
38
+ },
39
+ onclosetag(name) {
40
+ tokens.push({ type: "close", tag: name });
41
+ },
42
+ }, { xmlMode: true });
43
+ parser.write(context.xmlContent);
44
+ parser.end();
45
+ context.tokens = tokens;
46
+ context.performance.tokenizeTime = Date.now() - startTime;
47
+ return context;
48
+ }
49
+ }
50
+ /**
51
+ * AST生成处理器
52
+ */
53
+ export class AstGenerateProcessor {
54
+ constructor() {
55
+ this.stage = PipelineStage.AST_GENERATE;
56
+ }
57
+ async process(context) {
58
+ const startTime = Date.now();
59
+ if (!context.tokens) {
60
+ throw new Error("Tokens不能为空");
61
+ }
62
+ const stack = [];
63
+ let root = undefined;
64
+ let current = null;
65
+ for (const token of context.tokens) {
66
+ if (token.type === "open") {
67
+ const newElement = {
68
+ tag: token.tag,
69
+ attributes: token.attributes || {},
70
+ children: [],
71
+ };
72
+ if (!root) {
73
+ root = newElement;
74
+ }
75
+ else if (current) {
76
+ current.children?.push(newElement);
77
+ }
78
+ stack.push(newElement);
79
+ current = newElement;
80
+ }
81
+ else if (token.type === "text" && current) {
82
+ current.value = token.value;
83
+ }
84
+ else if (token.type === "close") {
85
+ stack.pop();
86
+ current = stack[stack.length - 1] || null;
87
+ }
88
+ }
89
+ context.ast = root;
90
+ context.performance.astTime = Date.now() - startTime;
91
+ return context;
92
+ }
93
+ }
94
+ /**
95
+ * 转换处理器
96
+ */
97
+ export class ConvertProcessor {
98
+ constructor(getConverter) {
99
+ this.getConverter = getConverter;
100
+ this.stage = PipelineStage.CONVERT;
101
+ }
102
+ async process(context) {
103
+ const startTime = Date.now();
104
+ if (!context.ast) {
105
+ throw new Error("AST不能为空");
106
+ }
107
+ const gpx = {};
108
+ // 处理根节点属性
109
+ if (context.ast.attributes) {
110
+ Object.entries(context.ast.attributes).forEach(([key, value]) => {
111
+ if (key.startsWith("xmlns:")) {
112
+ gpx[key] = value.replace(/'/g, "'");
113
+ }
114
+ else if (key === "xsi:schemaLocation") {
115
+ gpx[key] = value.replace(/'/g, "'");
116
+ }
117
+ else {
118
+ gpx[key] = value;
119
+ }
120
+ });
121
+ }
122
+ // 处理子节点
123
+ context.ast.children?.forEach((child) => {
124
+ const converter = this.getConverter(child.tag);
125
+ if (converter) {
126
+ try {
127
+ const result = converter.convert(child, context);
128
+ if (result) {
129
+ this.assignResult(gpx, child.tag, result);
130
+ }
131
+ }
132
+ catch (error) {
133
+ context.errors.push(error);
134
+ console.error(`转换器 ${converter.name} 处理标签 ${child.tag} 时出错:`, error);
135
+ }
136
+ }
137
+ });
138
+ context.result = gpx;
139
+ context.performance.convertTime = Date.now() - startTime;
140
+ return context;
141
+ }
142
+ assignResult(gpx, tag, result) {
143
+ switch (tag) {
144
+ case "metadata":
145
+ gpx.metadata = result;
146
+ break;
147
+ case "wpt":
148
+ if (!gpx.wpt)
149
+ gpx.wpt = [];
150
+ gpx.wpt.push(result);
151
+ break;
152
+ case "rte":
153
+ if (!gpx.rte)
154
+ gpx.rte = [];
155
+ gpx.rte.push(result);
156
+ break;
157
+ case "trk":
158
+ if (!gpx.trk)
159
+ gpx.trk = [];
160
+ gpx.trk.push(result);
161
+ break;
162
+ case "extensions":
163
+ gpx.extensions = result;
164
+ break;
165
+ }
166
+ }
167
+ }
168
+ /**
169
+ * 完成处理器
170
+ */
171
+ export class CompleteProcessor {
172
+ constructor() {
173
+ this.stage = PipelineStage.COMPLETE;
174
+ }
175
+ async process(context) {
176
+ context.performance.endTime = Date.now();
177
+ // 记录性能指标
178
+ const totalTime = context.performance.endTime - context.performance.startTime;
179
+ context.metadata.set("performance", {
180
+ totalTime,
181
+ tokenizeTime: context.performance.tokenizeTime,
182
+ astTime: context.performance.astTime,
183
+ convertTime: context.performance.convertTime,
184
+ });
185
+ return context;
186
+ }
187
+ }
@@ -0,0 +1,230 @@
1
+ import { DegreesType, ExtensionsType, LatitudeType, LongitudeType, Token, TokenAST } from "../types.js";
2
+ /**
3
+ * Two lat/lon pairs defining the extent of an element.
4
+ */
5
+ export interface BoundsType {
6
+ minlat?: LatitudeType;
7
+ minlon?: LongitudeType;
8
+ maxlat?: LatitudeType;
9
+ maxlon?: LongitudeType;
10
+ }
11
+ export interface PersonType {
12
+ name?: string;
13
+ email?: EmailType;
14
+ link?: LinkType;
15
+ }
16
+ export interface CopyrightType {
17
+ author?: string;
18
+ year?: string;
19
+ license?: string;
20
+ }
21
+ export interface EmailType {
22
+ id?: string;
23
+ domain?: string;
24
+ }
25
+ /**
26
+ * Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used
27
+ */
28
+ type FixType = "none" | "2d" | "3d" | "dgps" | "pps" | string;
29
+ export type LinkType = {
30
+ /**
31
+ * URL of hyperlink.
32
+ */
33
+ href?: string;
34
+ /**
35
+ * Text of hyperlink.
36
+ */
37
+ text?: string;
38
+ /**
39
+ * Mime type of content (image/jpeg)
40
+ */
41
+ type?: string;
42
+ };
43
+ /**
44
+ * GPX 1.1 version type
45
+ */
46
+ export interface GPX11Type {
47
+ version?: string;
48
+ creator?: string;
49
+ xmlns?: string;
50
+ "xmlns:gte"?: string;
51
+ "xmlns:xsi"?: string;
52
+ "xsi:schemaLocation"?: string;
53
+ metadata?: MetadataType;
54
+ wpt?: WptType[];
55
+ rte?: RteType[];
56
+ trk?: TrkType[];
57
+ extensions?: any;
58
+ [key: string]: any | undefined;
59
+ }
60
+ export interface MetadataType {
61
+ name?: string;
62
+ desc?: string;
63
+ /**
64
+ * The person or organization who created the GPX file.
65
+ */
66
+ author?: PersonType;
67
+ copyright?: CopyrightType;
68
+ link?: LinkType[];
69
+ time?: Date;
70
+ keywords?: string;
71
+ bounds?: BoundsType;
72
+ extensions?: ExtensionsType;
73
+ [key: string]: any;
74
+ }
75
+ /**
76
+ * A list of waypoints.
77
+ */
78
+ export interface WptType {
79
+ lat: LatitudeType;
80
+ lon: LongitudeType;
81
+ /**
82
+ * Elevation (in meters) of the point.
83
+ */
84
+ ele?: number;
85
+ /**
86
+ * Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs.
87
+ */
88
+ time?: Date;
89
+ /**
90
+ * Instantaneous speed at the point (meters per second)
91
+ */
92
+ speed?: number;
93
+ /**
94
+ * Instantaneous course at the point (degrees, true north)
95
+ */
96
+ course?: number;
97
+ /**
98
+ * Magnetic variation (in degrees) at the point
99
+ */
100
+ magvar?: DegreesType;
101
+ /**
102
+ * Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message.
103
+ */
104
+ geoidheight?: number;
105
+ /**
106
+ * The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS.
107
+ */
108
+ name?: string;
109
+ /**
110
+ * GPS waypoint comment. Sent to GPS as comment.
111
+ */
112
+ cmt?: string;
113
+ /**
114
+ * A text description of the element. Holds additional information about the element intended for the user, not the GPS.
115
+ */
116
+ desc?: string;
117
+ /**
118
+ * Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g.
119
+ */
120
+ src?: string;
121
+ /**
122
+ * Link to additional information about the waypoint.
123
+ */
124
+ link?: LinkType[];
125
+ /**
126
+ * Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out.
127
+ */
128
+ sym?: string;
129
+ /**
130
+ * Type (classification) of the waypoint.
131
+ */
132
+ type?: string;
133
+ fix?: FixType;
134
+ /**
135
+ * Number of satellites used to calculate the GPX fix.
136
+ */
137
+ sat?: string;
138
+ /**
139
+ * Horizontal dilution of precision.
140
+ */
141
+ hdop?: number;
142
+ /**
143
+ * Vertical dilution of precision.
144
+ */
145
+ vdop?: number;
146
+ /**
147
+ * Position dilution of precision.
148
+ */
149
+ pdop?: number;
150
+ /**
151
+ * Number of seconds since last DGPS update.
152
+ */
153
+ ageofdgpsdata?: number;
154
+ /**
155
+ * ID of DGPS station used in differential correction.
156
+ */
157
+ dgpsid?: DegreesType;
158
+ /**
159
+ * You can add extend GPX by adding your own elements from another schema here.
160
+ */
161
+ extensions?: ExtensionsType;
162
+ }
163
+ /**
164
+ * A list of route points.
165
+ */
166
+ type RteptType = WptType;
167
+ export interface RteType {
168
+ /**
169
+ * GPS name of route.
170
+ */
171
+ name?: string;
172
+ /**
173
+ * GPS comment for route.
174
+ */
175
+ cmt?: string;
176
+ /**
177
+ * Text description of route for user. Not sent to GPS.
178
+ */
179
+ desc?: string;
180
+ /**
181
+ * Source of data. Included to give user some idea of reliability and accuracy of data.
182
+ */
183
+ src?: string;
184
+ /**
185
+ * Links to external information about the route.
186
+ */
187
+ link?: LinkType[];
188
+ /**
189
+ * GPS route number.
190
+ */
191
+ number?: number;
192
+ /**
193
+ * Type (classification) of route.
194
+ */
195
+ type?: string;
196
+ extensions?: ExtensionsType;
197
+ rtept?: RteptType[];
198
+ }
199
+ export interface TrkType {
200
+ name?: string;
201
+ cmt?: string;
202
+ desc?: string;
203
+ src?: string;
204
+ link?: LinkType[];
205
+ number?: number;
206
+ type?: string;
207
+ extensions?: ExtensionsType;
208
+ trkseg?: TrksegType[];
209
+ }
210
+ export interface TrksegType {
211
+ trkpt?: WptType[];
212
+ extensions?: ExtensionsType;
213
+ }
214
+ export interface DecoderContext {
215
+ rawData?: Buffer;
216
+ xmlContent?: string;
217
+ tokens?: Token[];
218
+ ast?: TokenAST;
219
+ result?: GPX11Type;
220
+ metadata: Map<string, any>;
221
+ errors: Error[];
222
+ performance: {
223
+ startTime: number;
224
+ tokenizeTime?: number;
225
+ astTime?: number;
226
+ convertTime?: number;
227
+ endTime?: number;
228
+ };
229
+ }
230
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const GPXWPTOriginKey: string[];
@@ -0,0 +1,18 @@
1
+ export const GPXWPTOriginKey = [
2
+ 'magvar',
3
+ 'geoidheight',
4
+ 'name',
5
+ 'cmt',
6
+ 'src',
7
+ 'link',
8
+ 'sym',
9
+ 'type',
10
+ 'fix',
11
+ 'sat',
12
+ 'desc',
13
+ 'hdop',
14
+ 'vdop',
15
+ 'pdop',
16
+ 'ageofdgpsdata',
17
+ 'dgpsid',
18
+ ];
@@ -0,0 +1,47 @@
1
+ import { GPXDecoder, GPXEncoder } from "./GPX/index.js";
2
+ import { GPX11Type } from "./GPX/types.js";
3
+ /**
4
+ * GPX轨迹交换处理类
5
+ * 提供GPX文件的解析和编码功能
6
+ */
7
+ export declare class TrackSwap {
8
+ private decoder;
9
+ private encoder;
10
+ constructor();
11
+ /**
12
+ * 解析GPX文件Buffer为GPX对象
13
+ */
14
+ parseGPX(buffer: Buffer): Promise<GPX11Type | undefined>;
15
+ /**
16
+ * 解析GPX文件字符串为GPX对象
17
+ */
18
+ parseGPXString(xmlString: string): Promise<GPX11Type | undefined>;
19
+ /**
20
+ * 编码GPX对象为Buffer
21
+ */
22
+ encodeGPX(gpxData: GPX11Type): Promise<Buffer>;
23
+ /**
24
+ * 编码GPX对象为XML字符串
25
+ */
26
+ encodeGPXString(gpxData: GPX11Type): string;
27
+ /**
28
+ * 获取解码器实例
29
+ */
30
+ getDecoder(): GPXDecoder;
31
+ /**
32
+ * 获取编码器实例
33
+ */
34
+ getEncoder(): GPXEncoder;
35
+ /**
36
+ * 销毁实例,清理资源
37
+ */
38
+ destroy(): Promise<void>;
39
+ /**
40
+ * @deprecated 使用 encodeGPX() 替代
41
+ */
42
+ encoderGPX(gpx: GPX11Type): Promise<Buffer>;
43
+ }
44
+ declare const _default: {
45
+ TrackSwap: typeof TrackSwap;
46
+ };
47
+ export default _default;
@@ -0,0 +1,84 @@
1
+ import { GPXDecoder, GPXEncoder } from "./GPX/index.js";
2
+ /**
3
+ * GPX轨迹交换处理类
4
+ * 提供GPX文件的解析和编码功能
5
+ */
6
+ export class TrackSwap {
7
+ constructor() {
8
+ this.decoder = new GPXDecoder();
9
+ this.encoder = new GPXEncoder();
10
+ }
11
+ // ==================== 解析功能 ====================
12
+ /**
13
+ * 解析GPX文件Buffer为GPX对象
14
+ */
15
+ async parseGPX(buffer) {
16
+ try {
17
+ return await this.decoder.parserByBuffer(buffer);
18
+ }
19
+ catch (error) {
20
+ console.error("GPX解析失败:", error);
21
+ throw new Error(`GPX解析失败: ${error.message}`);
22
+ }
23
+ }
24
+ /**
25
+ * 解析GPX文件字符串为GPX对象
26
+ */
27
+ async parseGPXString(xmlString) {
28
+ const buffer = Buffer.from(xmlString, "utf-8");
29
+ return this.parseGPX(buffer);
30
+ }
31
+ // ==================== 编码功能 ====================
32
+ /**
33
+ * 编码GPX对象为Buffer
34
+ */
35
+ async encodeGPX(gpxData) {
36
+ try {
37
+ return await this.encoder.encode(gpxData);
38
+ }
39
+ catch (error) {
40
+ console.error("GPX编码失败:", error);
41
+ throw new Error(`GPX编码失败: ${error.message}`);
42
+ }
43
+ }
44
+ /**
45
+ * 编码GPX对象为XML字符串
46
+ */
47
+ encodeGPXString(gpxData) {
48
+ try {
49
+ return this.encoder.encodeToString(gpxData);
50
+ }
51
+ catch (error) {
52
+ console.error("GPX编码失败:", error);
53
+ throw new Error(`GPX编码失败: ${error.message}`);
54
+ }
55
+ }
56
+ // ==================== 实例管理 ====================
57
+ /**
58
+ * 获取解码器实例
59
+ */
60
+ getDecoder() {
61
+ return this.decoder;
62
+ }
63
+ /**
64
+ * 获取编码器实例
65
+ */
66
+ getEncoder() {
67
+ return this.encoder;
68
+ }
69
+ /**
70
+ * 销毁实例,清理资源
71
+ */
72
+ async destroy() {
73
+ await this.decoder.destroy();
74
+ }
75
+ // ==================== 向后兼容 ====================
76
+ /**
77
+ * @deprecated 使用 encodeGPX() 替代
78
+ */
79
+ async encoderGPX(gpx) {
80
+ return this.encodeGPX(gpx);
81
+ }
82
+ }
83
+ // 默认导出
84
+ export default { TrackSwap };
@@ -0,0 +1,6 @@
1
+ import { TrackSwap } from "./TrackSwap.js";
2
+ import GPXManager from "./GPX/index.js";
3
+ import { Token, TokenAST, AttributeType, LatitudeType, LongitudeType, DegreesType, ExtensionsType } from "./types.js";
4
+ export { TokenAST, AttributeType, LatitudeType, LongitudeType, DegreesType, ExtensionsType, Token, TrackSwap, };
5
+ export { GPXManager };
6
+ export default TrackSwap;
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { TrackSwap } from "./TrackSwap.js";
2
+ import GPXManager from "./GPX/index.js";
3
+ export { TrackSwap, };
4
+ export { GPXManager };
5
+ export default TrackSwap;
@@ -0,0 +1,34 @@
1
+ export interface Token {
2
+ type: "open" | "close" | "text";
3
+ tag: string;
4
+ attributes?: AttributeType;
5
+ value?: string | number;
6
+ }
7
+ export interface TokenAST {
8
+ tag: string;
9
+ attributes?: AttributeType;
10
+ children?: TokenAST[];
11
+ value?: string | number;
12
+ }
13
+ export interface AttributeType {
14
+ [key: string]: string;
15
+ }
16
+ /**
17
+ * The latitude of the point. Decimal degrees, WGS84 datum.
18
+ * @type {number} -90.0 <= value <= 90.0
19
+ */
20
+ export type LatitudeType = number;
21
+ /**
22
+ * The longitude of the point. Decimal degrees, WGS84 datum.
23
+ * @type {number} -180.0 <= value < 180.0
24
+ */
25
+ export type LongitudeType = number;
26
+ /**
27
+ * Used for bearing, heading, course. Units are decimal degrees, true (not magnetic).
28
+ * @type {number} 0.0 <= value < 360.0
29
+
30
+ */
31
+ export type DegreesType = number;
32
+ export interface ExtensionsType {
33
+ [key: string]: string | number | undefined | ExtensionsType;
34
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "trackswap",
3
+ "version": "0.0.1",
4
+ "description": "A powerful and flexible GPX parser and encoder library for Node.js and TypeScript. TrackSwap provides comprehensive support for GPX 1.1 format with advanced features like plugin system, middleware support, and optimized performance.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist/**/*",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "dev": "ts-node src/index.ts",
20
+ "build": "tsc",
21
+ "start": "node dist/index.js",
22
+ "test:gpx": "cd example/gpx && rm -f output* test_*.json *_report.json *_encoded.xml && node test.js",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "gpx",
27
+ "gps",
28
+ "parser",
29
+ "encoder",
30
+ "geolocation",
31
+ "track",
32
+ "waypoint",
33
+ "route",
34
+ "typescript",
35
+ "node",
36
+ "xml",
37
+ "geospatial",
38
+ "trackswap"
39
+ ],
40
+ "author": "buerber1016",
41
+ "license": "MIT",
42
+ "type": "module",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/buerber1016/trackswap.git"
46
+ },
47
+ "homepage": "https://github.com/buerber1016/trackswap#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/buerber1016/trackswap/issues"
50
+ },
51
+ "engines": {
52
+ "node": ">=14.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.15.24",
56
+ "ts-node": "^10.9.2",
57
+ "typescript": "^5.8.3"
58
+ },
59
+ "dependencies": {
60
+ "dayjs": "^1.11.13",
61
+ "htmlparser2": "^10.0.0"
62
+ }
63
+ }