gpx-interpolator 0.1.0
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 +279 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +175 -0
- package/dist/cli.js.map +1 -0
- package/dist/gpx-io.d.ts +10 -0
- package/dist/gpx-io.d.ts.map +1 -0
- package/dist/gpx-io.js +178 -0
- package/dist/gpx-io.js.map +1 -0
- package/dist/gpx-utils.d.ts +50 -0
- package/dist/gpx-utils.d.ts.map +1 -0
- package/dist/gpx-utils.js +287 -0
- package/dist/gpx-utils.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +16 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +216 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/pchip.d.ts +13 -0
- package/dist/pchip.d.ts.map +1 -0
- package/dist/pchip.js +85 -0
- package/dist/pchip.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# GPX Interpolate - TypeScript
|
|
2
|
+
|
|
3
|
+
TypeScript/Node.js implementation of GPX interpolation using piecewise cubic Hermite splines.
|
|
4
|
+
|
|
5
|
+
[中文文档](./README-zh.md) | [快速开始](./QUICKSTART-zh.md)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 📍 Insert intermediate points between GPX track points
|
|
10
|
+
- 🎯 Smooth interpolation using PCHIP (Piecewise Cubic Hermite Interpolating Polynomial)
|
|
11
|
+
- 📏 Distance-based interpolation resolution (specify meters between points)
|
|
12
|
+
- ⏱️ Preserve and interpolate timestamp information
|
|
13
|
+
- 📈 Optional speed calculation and saving
|
|
14
|
+
- 🔄 Automatically remove duplicate track points
|
|
15
|
+
- 🚫 Gap detection for GPS signal loss
|
|
16
|
+
- 📊 Track statistics (distance, duration, elevation gain, etc.)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install gpx-interpolator
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or for development:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone <repo>
|
|
28
|
+
cd gpx-interpolator
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## CLI Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Basic usage (default 1 meter resolution)
|
|
37
|
+
gpx-interpolator your-track.gpx
|
|
38
|
+
|
|
39
|
+
# Specify resolution in meters
|
|
40
|
+
gpx-interpolator -r 5 your-track.gpx
|
|
41
|
+
|
|
42
|
+
# Specify output point count
|
|
43
|
+
gpx-interpolator -n 1000 your-track.gpx
|
|
44
|
+
|
|
45
|
+
# Only interpolate segments > 100 meters
|
|
46
|
+
gpx-interpolator -r 5 -m 100 your-track.gpx
|
|
47
|
+
|
|
48
|
+
# Split track at GPS gaps > 500 meters
|
|
49
|
+
gpx-interpolator -r 5 -g 500 your-track.gpx
|
|
50
|
+
|
|
51
|
+
# Custom output file
|
|
52
|
+
gpx-interpolator -r 5 -o output.gpx your-track.gpx
|
|
53
|
+
|
|
54
|
+
# Verbose mode with statistics
|
|
55
|
+
gpx-interpolator -v your-track.gpx
|
|
56
|
+
|
|
57
|
+
# Show statistics only (no interpolation)
|
|
58
|
+
gpx-interpolator --stats-only your-track.gpx
|
|
59
|
+
|
|
60
|
+
# Dry run (preview without saving)
|
|
61
|
+
gpx-interpolator --dry-run -r 5 your-track.gpx
|
|
62
|
+
|
|
63
|
+
# Save speed information
|
|
64
|
+
gpx-interpolator -s your-track.gpx
|
|
65
|
+
|
|
66
|
+
# Process multiple files
|
|
67
|
+
gpx-interpolator *.gpx
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### CLI Options
|
|
71
|
+
|
|
72
|
+
| Option | Description |
|
|
73
|
+
| ----------------------------- | ------------------------------------------------------ |
|
|
74
|
+
| `-r, --resolution <meters>` | Interpolation resolution in meters (default: 1.0) |
|
|
75
|
+
| `-n, --num <count>` | Force exact point count in output |
|
|
76
|
+
| `-m, --min-distance <meters>` | Only interpolate segments longer than this |
|
|
77
|
+
| `-g, --max-gap <meters>` | Split track at gaps larger than this (GPS signal loss) |
|
|
78
|
+
| `-o, --output <file>` | Output file name (single file only) |
|
|
79
|
+
| `-s, --speed` | Save interpolated speed in output |
|
|
80
|
+
| `-v, --verbose` | Verbose output with statistics |
|
|
81
|
+
| `--dry-run` | Preview what would be done without writing files |
|
|
82
|
+
| `--stats-only` | Only show track statistics, don't interpolate |
|
|
83
|
+
|
|
84
|
+
## Module Usage
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import {
|
|
88
|
+
gpxRead,
|
|
89
|
+
gpxWrite,
|
|
90
|
+
gpxInterpolate,
|
|
91
|
+
interpolateTrack,
|
|
92
|
+
gpxCalculateStatistics,
|
|
93
|
+
gpxDataToPoints,
|
|
94
|
+
pointsToGpxData,
|
|
95
|
+
} from "gpx-interpolator";
|
|
96
|
+
|
|
97
|
+
// Read GPX file
|
|
98
|
+
const gpxData = gpxRead("input.gpx");
|
|
99
|
+
|
|
100
|
+
// Basic interpolation (1 meter resolution)
|
|
101
|
+
const interpolated = gpxInterpolate(gpxData, 1.0);
|
|
102
|
+
|
|
103
|
+
// Advanced interpolation with options
|
|
104
|
+
const result = interpolateTrack(gpxData, {
|
|
105
|
+
resolution: 5, // 5 meters between points
|
|
106
|
+
minDistance: 100, // Only interpolate segments > 100m
|
|
107
|
+
maxGap: 500, // Split at gaps > 500m
|
|
108
|
+
onProgress: (percent) => console.log(`${percent}%`),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Get statistics
|
|
112
|
+
const stats = gpxCalculateStatistics(gpxData);
|
|
113
|
+
console.log(`Distance: ${stats.totalDistance}m`);
|
|
114
|
+
console.log(`Duration: ${stats.totalDuration}s`);
|
|
115
|
+
console.log(`Elevation gain: ${stats.elevationGain}m`);
|
|
116
|
+
|
|
117
|
+
// Convert between formats
|
|
118
|
+
const points = gpxDataToPoints(gpxData); // GPXData -> GPXPoint[]
|
|
119
|
+
const data = pointsToGpxData(points); // GPXPoint[] -> GPXData
|
|
120
|
+
|
|
121
|
+
// Save result
|
|
122
|
+
gpxWrite("output.gpx", result);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## API Reference
|
|
126
|
+
|
|
127
|
+
### Core Functions
|
|
128
|
+
|
|
129
|
+
#### `gpxInterpolate(gpxData, options)`
|
|
130
|
+
|
|
131
|
+
Interpolate GPX data using piecewise cubic Hermite splines.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Simple usage (backward compatible)
|
|
135
|
+
gpxInterpolate(gpxData, 1.0); // 1 meter resolution
|
|
136
|
+
gpxInterpolate(gpxData, 5.0, 1000); // 5m resolution, force 1000 points
|
|
137
|
+
|
|
138
|
+
// With options object
|
|
139
|
+
gpxInterpolate(gpxData, {
|
|
140
|
+
resolution: 5, // meters between points (default: 1.0)
|
|
141
|
+
numPoints: null, // force exact point count (overrides resolution)
|
|
142
|
+
minDistance: 0, // only interpolate segments > this (default: 0)
|
|
143
|
+
maxGap: Infinity, // split at gaps > this (default: Infinity)
|
|
144
|
+
preserveOriginal: false, // keep original points (default: false)
|
|
145
|
+
onProgress: (p) => {}, // progress callback (0-100)
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `interpolateTrack(gpxData, options)`
|
|
150
|
+
|
|
151
|
+
Alias for `gpxInterpolate` with options object. Recommended for new code.
|
|
152
|
+
|
|
153
|
+
### I/O Functions
|
|
154
|
+
|
|
155
|
+
#### `gpxRead(filename)`
|
|
156
|
+
|
|
157
|
+
Read GPX data from file.
|
|
158
|
+
|
|
159
|
+
#### `gpxWrite(filename, gpxData, writeSpeed?)`
|
|
160
|
+
|
|
161
|
+
Write GPX data to file.
|
|
162
|
+
|
|
163
|
+
### Utility Functions
|
|
164
|
+
|
|
165
|
+
#### `gpxCalculateStatistics(gpxData, maxGap?)`
|
|
166
|
+
|
|
167
|
+
Calculate track statistics:
|
|
168
|
+
|
|
169
|
+
- `totalDistance` - Total distance in meters
|
|
170
|
+
- `totalDuration` - Duration in seconds (null if no timestamps)
|
|
171
|
+
- `pointCount` - Number of track points
|
|
172
|
+
- `avgSpeed` / `maxSpeed` - Speed in m/s
|
|
173
|
+
- `elevationGain` / `elevationLoss` - Elevation changes
|
|
174
|
+
- `minElevation` / `maxElevation` - Elevation range
|
|
175
|
+
- `bounds` - Bounding box (minLat, maxLat, minLon, maxLon)
|
|
176
|
+
- `segmentCount` - Number of segments (based on maxGap)
|
|
177
|
+
|
|
178
|
+
#### `gpxDataToPoints(gpxData)`
|
|
179
|
+
|
|
180
|
+
Convert internal GPXData format to user-friendly GPXPoint[] array.
|
|
181
|
+
|
|
182
|
+
#### `pointsToGpxData(points, tzinfo?)`
|
|
183
|
+
|
|
184
|
+
Convert GPXPoint[] array to internal GPXData format.
|
|
185
|
+
|
|
186
|
+
#### `gpxCalculateDistance(gpxData, useEle?)`
|
|
187
|
+
|
|
188
|
+
Calculate distance between consecutive points (Haversine formula).
|
|
189
|
+
|
|
190
|
+
#### `gpxCalculateSpeed(gpxData)`
|
|
191
|
+
|
|
192
|
+
Calculate speed between consecutive points.
|
|
193
|
+
|
|
194
|
+
#### `detectSegments(gpxData, maxGap)`
|
|
195
|
+
|
|
196
|
+
Detect track segments separated by gaps larger than maxGap.
|
|
197
|
+
|
|
198
|
+
#### `haversineDistance(lat1, lon1, lat2, lon2)`
|
|
199
|
+
|
|
200
|
+
Calculate distance between two coordinates in meters.
|
|
201
|
+
|
|
202
|
+
#### `formatDistance(meters)` / `formatDuration(seconds)`
|
|
203
|
+
|
|
204
|
+
Format values for display.
|
|
205
|
+
|
|
206
|
+
### Types
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
interface GPXData {
|
|
210
|
+
lat: number[]; // Latitude array
|
|
211
|
+
lon: number[]; // Longitude array
|
|
212
|
+
ele: number[] | null; // Elevation array (meters)
|
|
213
|
+
tstamp: number[] | null; // Unix timestamps (seconds)
|
|
214
|
+
tzinfo: string | null; // Timezone info
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface GPXPoint {
|
|
218
|
+
lat: number;
|
|
219
|
+
lon: number;
|
|
220
|
+
ele?: number;
|
|
221
|
+
time?: Date;
|
|
222
|
+
speed?: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface InterpolateOptions {
|
|
226
|
+
resolution?: number; // meters between points
|
|
227
|
+
numPoints?: number | null;
|
|
228
|
+
minDistance?: number;
|
|
229
|
+
maxGap?: number;
|
|
230
|
+
preserveOriginal?: boolean;
|
|
231
|
+
onProgress?: (percent: number) => void;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface TrackStatistics {
|
|
235
|
+
totalDistance: number;
|
|
236
|
+
totalDuration: number | null;
|
|
237
|
+
pointCount: number;
|
|
238
|
+
avgSpeed: number | null;
|
|
239
|
+
maxSpeed: number | null;
|
|
240
|
+
elevationGain: number | null;
|
|
241
|
+
elevationLoss: number | null;
|
|
242
|
+
minElevation: number | null;
|
|
243
|
+
maxElevation: number | null;
|
|
244
|
+
bounds: { minLat; maxLat; minLon; maxLon };
|
|
245
|
+
segmentCount: number;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## How It Works
|
|
250
|
+
|
|
251
|
+
1. **Read**: Parse GPX file and extract lat, lon, elevation, and timestamps
|
|
252
|
+
2. **Clean**: Remove duplicate track points
|
|
253
|
+
3. **Distance**: Calculate distances using Haversine formula (Earth curvature)
|
|
254
|
+
4. **Segment**: Detect gaps (GPS signal loss) and split track if needed
|
|
255
|
+
5. **Interpolate**: Use PCHIP algorithm to smoothly interpolate each dimension
|
|
256
|
+
6. **Generate**: Create new points based on resolution or point count
|
|
257
|
+
7. **Output**: Save to new GPX file or return data structure
|
|
258
|
+
|
|
259
|
+
## Project Structure
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
gpx-interpolator/
|
|
263
|
+
├── src/
|
|
264
|
+
│ ├── types.ts # TypeScript type definitions
|
|
265
|
+
│ ├── pchip.ts # PCHIP interpolation algorithm
|
|
266
|
+
│ ├── gpx-utils.ts # GPX utility functions
|
|
267
|
+
│ ├── gpx-io.ts # File I/O
|
|
268
|
+
│ ├── interpolate.ts # Main interpolation function
|
|
269
|
+
│ ├── index.ts # Module exports
|
|
270
|
+
│ └── cli.ts # CLI tool
|
|
271
|
+
├── dist/ # Compiled JavaScript
|
|
272
|
+
├── package.json
|
|
273
|
+
├── tsconfig.json
|
|
274
|
+
└── README.md
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const gpx_io_1 = require("./gpx-io");
|
|
40
|
+
const interpolate_1 = require("./interpolate");
|
|
41
|
+
const gpx_utils_1 = require("./gpx-utils");
|
|
42
|
+
const program = new commander_1.Command();
|
|
43
|
+
program
|
|
44
|
+
.name("gpx-interpolate")
|
|
45
|
+
.description("Interpolate GPX files using piecewise cubic Hermite splines")
|
|
46
|
+
.version("1.1.0")
|
|
47
|
+
.argument("<files...>", "GPX file(s) to interpolate")
|
|
48
|
+
.option("-r, --resolution <meters>", "interpolation resolution in meters (default: 1.0)", "1.0")
|
|
49
|
+
.option("-n, --num <count>", "force point count in output (overrides resolution)")
|
|
50
|
+
.option("-m, --min-distance <meters>", "only interpolate segments longer than this (default: 0)")
|
|
51
|
+
.option("-g, --max-gap <meters>", "split track at gaps larger than this (GPS signal loss)")
|
|
52
|
+
.option("-o, --output <file>", "output file name (only for single file input)")
|
|
53
|
+
.option("-s, --speed", "save interpolated speed", false)
|
|
54
|
+
.option("-v, --verbose", "verbose output with statistics", false)
|
|
55
|
+
.option("--dry-run", "show what would be done without writing files", false)
|
|
56
|
+
.option("--stats-only", "only show track statistics, do not interpolate", false)
|
|
57
|
+
.action((files, options) => {
|
|
58
|
+
const resolution = parseFloat(options.resolution);
|
|
59
|
+
const numPoints = options.num ? parseInt(options.num) : null;
|
|
60
|
+
const minDistance = options.minDistance
|
|
61
|
+
? parseFloat(options.minDistance)
|
|
62
|
+
: 0;
|
|
63
|
+
const maxGap = options.maxGap ? parseFloat(options.maxGap) : Infinity;
|
|
64
|
+
const writeSpeed = options.speed;
|
|
65
|
+
const verbose = options.verbose;
|
|
66
|
+
const dryRun = options.dryRun;
|
|
67
|
+
const statsOnly = options.statsOnly;
|
|
68
|
+
const outputFile = options.output;
|
|
69
|
+
// Validate output option
|
|
70
|
+
if (outputFile && files.length > 1) {
|
|
71
|
+
console.error("Error: --output option can only be used with a single input file");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
let totalInputPoints = 0;
|
|
75
|
+
let totalOutputPoints = 0;
|
|
76
|
+
for (const gpxFile of files) {
|
|
77
|
+
// Skip already interpolated files
|
|
78
|
+
if (gpxFile.endsWith("_interpolated.gpx")) {
|
|
79
|
+
if (verbose) {
|
|
80
|
+
console.log(`Skipping already interpolated file: ${gpxFile}`);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
console.log(`\n📍 Processing ${gpxFile}...`);
|
|
86
|
+
console.log("─".repeat(50));
|
|
87
|
+
// Read GPX file
|
|
88
|
+
const gpxData = (0, gpx_io_1.gpxRead)(gpxFile);
|
|
89
|
+
totalInputPoints += gpxData.lat.length;
|
|
90
|
+
if (verbose || statsOnly) {
|
|
91
|
+
console.log(`\n📊 Track Statistics:`);
|
|
92
|
+
const stats = (0, gpx_utils_1.gpxCalculateStatistics)(gpxData, maxGap);
|
|
93
|
+
console.log(` Points: ${stats.pointCount}`);
|
|
94
|
+
console.log(` Distance: ${(0, gpx_utils_1.formatDistance)(stats.totalDistance)}`);
|
|
95
|
+
if (stats.totalDuration !== null) {
|
|
96
|
+
console.log(` Duration: ${(0, gpx_utils_1.formatDuration)(stats.totalDuration)}`);
|
|
97
|
+
console.log(` Avg Speed: ${(stats.avgSpeed * 3.6).toFixed(1)} km/h`);
|
|
98
|
+
console.log(` Max Speed: ${(stats.maxSpeed * 3.6).toFixed(1)} km/h`);
|
|
99
|
+
}
|
|
100
|
+
if (stats.elevationGain !== null) {
|
|
101
|
+
console.log(` Elevation Gain: ${stats.elevationGain.toFixed(0)} m`);
|
|
102
|
+
console.log(` Elevation Loss: ${stats.elevationLoss.toFixed(0)} m`);
|
|
103
|
+
console.log(` Min/Max Elevation: ${stats.minElevation.toFixed(0)}m / ${stats.maxElevation.toFixed(0)}m`);
|
|
104
|
+
}
|
|
105
|
+
if (maxGap !== Infinity) {
|
|
106
|
+
console.log(` Segments (gap > ${(0, gpx_utils_1.formatDistance)(maxGap)}): ${stats.segmentCount}`);
|
|
107
|
+
}
|
|
108
|
+
console.log(` Bounds: [${stats.bounds.minLat.toFixed(4)}, ${stats.bounds.minLon.toFixed(4)}] to [${stats.bounds.maxLat.toFixed(4)}, ${stats.bounds.maxLon.toFixed(4)}]`);
|
|
109
|
+
}
|
|
110
|
+
if (statsOnly) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Remove duplicates
|
|
114
|
+
const gpxDataNodup = (0, gpx_utils_1.gpxRemoveDuplicates)(gpxData);
|
|
115
|
+
const duplicatesRemoved = gpxData.lat.length - gpxDataNodup.lat.length;
|
|
116
|
+
if (duplicatesRemoved > 0 && verbose) {
|
|
117
|
+
console.log(`\n🔄 Removed ${duplicatesRemoved} duplicate trackpoint(s)`);
|
|
118
|
+
}
|
|
119
|
+
// Build interpolation options
|
|
120
|
+
const interpolateOptions = {
|
|
121
|
+
resolution,
|
|
122
|
+
numPoints,
|
|
123
|
+
minDistance,
|
|
124
|
+
maxGap,
|
|
125
|
+
};
|
|
126
|
+
if (verbose) {
|
|
127
|
+
console.log(`\n⚙️ Interpolation Settings:`);
|
|
128
|
+
console.log(` Resolution: ${resolution} m`);
|
|
129
|
+
if (numPoints)
|
|
130
|
+
console.log(` Forced point count: ${numPoints}`);
|
|
131
|
+
if (minDistance > 0)
|
|
132
|
+
console.log(` Min distance threshold: ${(0, gpx_utils_1.formatDistance)(minDistance)}`);
|
|
133
|
+
if (maxGap !== Infinity)
|
|
134
|
+
console.log(` Max gap (split at): ${(0, gpx_utils_1.formatDistance)(maxGap)}`);
|
|
135
|
+
}
|
|
136
|
+
// Interpolate
|
|
137
|
+
const gpxDataInterp = (0, interpolate_1.gpxInterpolate)(gpxDataNodup, interpolateOptions);
|
|
138
|
+
totalOutputPoints += gpxDataInterp.lat.length;
|
|
139
|
+
// Generate output filename
|
|
140
|
+
const outFile = outputFile ||
|
|
141
|
+
(() => {
|
|
142
|
+
const ext = path.extname(gpxFile);
|
|
143
|
+
const base = gpxFile.slice(0, -ext.length);
|
|
144
|
+
return `${base}_interpolated${ext}`;
|
|
145
|
+
})();
|
|
146
|
+
if (dryRun) {
|
|
147
|
+
console.log(`\n🔍 Dry run - would save ${gpxDataInterp.lat.length} points to ${outFile}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Write output file
|
|
151
|
+
(0, gpx_io_1.gpxWrite)(outFile, gpxDataInterp, writeSpeed);
|
|
152
|
+
console.log(`\n✅ Saved ${gpxDataInterp.lat.length} trackpoints to ${outFile}`);
|
|
153
|
+
}
|
|
154
|
+
// Show compression/expansion ratio
|
|
155
|
+
const ratio = gpxDataInterp.lat.length / gpxDataNodup.lat.length;
|
|
156
|
+
if (ratio > 1) {
|
|
157
|
+
console.log(` (${ratio.toFixed(1)}x more points than original)`);
|
|
158
|
+
}
|
|
159
|
+
else if (ratio < 1) {
|
|
160
|
+
console.log(` (${(1 / ratio).toFixed(1)}x fewer points than original)`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error(`\n❌ Error processing ${gpxFile}:`, error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Summary for multiple files
|
|
169
|
+
if (files.length > 1 && !statsOnly) {
|
|
170
|
+
console.log("\n" + "═".repeat(50));
|
|
171
|
+
console.log(`📈 Summary: ${totalInputPoints} → ${totalOutputPoints} points across ${files.length} files`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
program.parse();
|
|
175
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/gpx-io.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GPXData } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Read GPX file and extract track data
|
|
4
|
+
*/
|
|
5
|
+
export declare function gpxRead(gpxFile: string): GPXData;
|
|
6
|
+
/**
|
|
7
|
+
* Write GPX file from track data
|
|
8
|
+
*/
|
|
9
|
+
export declare function gpxWrite(gpxFile: string, gpxData: GPXData, writeSpeed?: boolean): void;
|
|
10
|
+
//# sourceMappingURL=gpx-io.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpx-io.d.ts","sourceRoot":"","sources":["../src/gpx-io.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAY,MAAM,SAAS,CAAC;AAG5C;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAgFhD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,UAAU,GAAE,OAAe,GAC1B,IAAI,CAiFN"}
|
package/dist/gpx-io.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.gpxRead = gpxRead;
|
|
37
|
+
exports.gpxWrite = gpxWrite;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const xmldom_1 = require("@xmldom/xmldom");
|
|
40
|
+
const gpx_utils_1 = require("./gpx-utils");
|
|
41
|
+
/**
|
|
42
|
+
* Read GPX file and extract track data
|
|
43
|
+
*/
|
|
44
|
+
function gpxRead(gpxFile) {
|
|
45
|
+
const gpxData = {
|
|
46
|
+
lat: [],
|
|
47
|
+
lon: [],
|
|
48
|
+
ele: [],
|
|
49
|
+
tstamp: [],
|
|
50
|
+
tzinfo: null,
|
|
51
|
+
};
|
|
52
|
+
const content = fs.readFileSync(gpxFile, 'utf-8');
|
|
53
|
+
const parser = new xmldom_1.DOMParser();
|
|
54
|
+
const doc = parser.parseFromString(content, 'text/xml');
|
|
55
|
+
const tracks = doc.getElementsByTagName('trk');
|
|
56
|
+
const iLatlon = [];
|
|
57
|
+
const iTstamp = [];
|
|
58
|
+
let i = 0;
|
|
59
|
+
for (let t = 0; t < tracks.length; t++) {
|
|
60
|
+
const segments = tracks[t].getElementsByTagName('trkseg');
|
|
61
|
+
for (let s = 0; s < segments.length; s++) {
|
|
62
|
+
const points = segments[s].getElementsByTagName('trkpt');
|
|
63
|
+
for (let p = 0; p < points.length; p++) {
|
|
64
|
+
const point = points[p];
|
|
65
|
+
const lat = parseFloat(point.getAttribute('lat') || '0');
|
|
66
|
+
const lon = parseFloat(point.getAttribute('lon') || '0');
|
|
67
|
+
gpxData.lat.push(lat);
|
|
68
|
+
gpxData.lon.push(lon);
|
|
69
|
+
iLatlon.push(i);
|
|
70
|
+
// Elevation
|
|
71
|
+
const eleNodes = point.getElementsByTagName('ele');
|
|
72
|
+
if (eleNodes.length > 0) {
|
|
73
|
+
const ele = parseFloat(eleNodes[0].textContent || '0');
|
|
74
|
+
gpxData.ele.push(ele);
|
|
75
|
+
}
|
|
76
|
+
// Time
|
|
77
|
+
const timeNodes = point.getElementsByTagName('time');
|
|
78
|
+
if (timeNodes.length > 0) {
|
|
79
|
+
const timeStr = timeNodes[0].textContent;
|
|
80
|
+
if (timeStr) {
|
|
81
|
+
const date = new Date(timeStr);
|
|
82
|
+
gpxData.tstamp.push(date.getTime() / 1000); // Convert to seconds
|
|
83
|
+
// Store timezone info from first timestamp
|
|
84
|
+
if (!gpxData.tzinfo) {
|
|
85
|
+
// Extract timezone offset
|
|
86
|
+
const match = timeStr.match(/([+-]\d{2}:\d{2}|Z)$/);
|
|
87
|
+
gpxData.tzinfo = match ? match[0] : 'Z';
|
|
88
|
+
}
|
|
89
|
+
iTstamp.push(i);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Remove trackpoints without timestamp if some have timestamps
|
|
97
|
+
if (iTstamp.length > 0 && iLatlon.length !== iTstamp.length) {
|
|
98
|
+
gpxData.lat = iTstamp.map((idx) => gpxData.lat[idx]);
|
|
99
|
+
gpxData.lon = iTstamp.map((idx) => gpxData.lon[idx]);
|
|
100
|
+
gpxData.ele = gpxData.ele.length > 0
|
|
101
|
+
? iTstamp.map((idx) => gpxData.ele[idx])
|
|
102
|
+
: null;
|
|
103
|
+
gpxData.tstamp = iTstamp.map((idx) => gpxData.tstamp[idx]);
|
|
104
|
+
}
|
|
105
|
+
// Convert empty arrays to null
|
|
106
|
+
if (gpxData.ele.length === 0)
|
|
107
|
+
gpxData.ele = null;
|
|
108
|
+
if (gpxData.tstamp.length === 0)
|
|
109
|
+
gpxData.tstamp = null;
|
|
110
|
+
return gpxData;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Write GPX file from track data
|
|
114
|
+
*/
|
|
115
|
+
function gpxWrite(gpxFile, gpxData, writeSpeed = false) {
|
|
116
|
+
let gpxSpeed = null;
|
|
117
|
+
if (writeSpeed) {
|
|
118
|
+
if (!gpxData.tstamp) {
|
|
119
|
+
throw new Error('Timestamp data is missing from gpxData');
|
|
120
|
+
}
|
|
121
|
+
gpxSpeed = (0, gpx_utils_1.gpxCalculateSpeed)(gpxData);
|
|
122
|
+
}
|
|
123
|
+
// Create GPX XML structure
|
|
124
|
+
const parser = new xmldom_1.DOMParser();
|
|
125
|
+
const doc = parser.parseFromString('<?xml version="1.0" encoding="UTF-8"?><gpx></gpx>', 'text/xml');
|
|
126
|
+
const gpxElement = doc.documentElement;
|
|
127
|
+
gpxElement.setAttribute('version', writeSpeed ? '1.0' : '1.1');
|
|
128
|
+
gpxElement.setAttribute('creator', 'gpx-interpolate-ts');
|
|
129
|
+
gpxElement.setAttribute('xmlns', 'http://www.topografix.com/GPX/1/1');
|
|
130
|
+
if (!writeSpeed) {
|
|
131
|
+
gpxElement.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
|
|
132
|
+
gpxElement.setAttribute('xsi:schemaLocation', 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd');
|
|
133
|
+
}
|
|
134
|
+
const track = doc.createElement('trk');
|
|
135
|
+
const segment = doc.createElement('trkseg');
|
|
136
|
+
for (let i = 0; i < gpxData.lat.length; i++) {
|
|
137
|
+
const trkpt = doc.createElement('trkpt');
|
|
138
|
+
trkpt.setAttribute('lat', gpxData.lat[i].toString());
|
|
139
|
+
trkpt.setAttribute('lon', gpxData.lon[i].toString());
|
|
140
|
+
// Elevation
|
|
141
|
+
if (gpxData.ele) {
|
|
142
|
+
const ele = doc.createElement('ele');
|
|
143
|
+
ele.textContent = gpxData.ele[i].toString();
|
|
144
|
+
trkpt.appendChild(ele);
|
|
145
|
+
}
|
|
146
|
+
// Time
|
|
147
|
+
if (gpxData.tstamp) {
|
|
148
|
+
const time = doc.createElement('time');
|
|
149
|
+
const date = new Date(gpxData.tstamp[i] * 1000);
|
|
150
|
+
// Format ISO string with timezone
|
|
151
|
+
let timeStr = date.toISOString();
|
|
152
|
+
if (gpxData.tzinfo && gpxData.tzinfo !== 'Z') {
|
|
153
|
+
timeStr = timeStr.replace('Z', gpxData.tzinfo);
|
|
154
|
+
}
|
|
155
|
+
time.textContent = timeStr;
|
|
156
|
+
trkpt.appendChild(time);
|
|
157
|
+
}
|
|
158
|
+
// Speed (for GPX 1.0)
|
|
159
|
+
if (writeSpeed && gpxSpeed) {
|
|
160
|
+
const speed = doc.createElement('speed');
|
|
161
|
+
speed.textContent = gpxSpeed[i].toString();
|
|
162
|
+
trkpt.appendChild(speed);
|
|
163
|
+
}
|
|
164
|
+
segment.appendChild(trkpt);
|
|
165
|
+
}
|
|
166
|
+
track.appendChild(segment);
|
|
167
|
+
gpxElement.appendChild(track);
|
|
168
|
+
// Serialize and write to file
|
|
169
|
+
const serializer = new xmldom_1.XMLSerializer();
|
|
170
|
+
const xmlStr = serializer.serializeToString(doc);
|
|
171
|
+
try {
|
|
172
|
+
fs.writeFileSync(gpxFile, xmlStr, 'utf-8');
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
throw new Error(`Failed to save ${gpxFile}: ${error}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=gpx-io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpx-io.js","sourceRoot":"","sources":["../src/gpx-io.ts"],"names":[],"mappings":"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"}
|