@glandais/vcyclist-engine 1.2.0 → 2.0.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 CHANGED
@@ -3,6 +3,9 @@
3
3
  [![npm engine](https://img.shields.io/npm/v/@glandais/vcyclist-engine?label=%40glandais%2Fvcyclist-engine)](https://www.npmjs.com/package/@glandais/vcyclist-engine)
4
4
  [![npm elevation](https://img.shields.io/npm/v/@glandais/vcyclist-elevation?label=%40glandais%2Fvcyclist-elevation)](https://www.npmjs.com/package/@glandais/vcyclist-elevation)
5
5
  [![Maven Central](https://img.shields.io/maven-central/v/io.github.glandais/vcyclist-engine?label=io.github.glandais%3Avcyclist-engine)](https://central.sonatype.com/artifact/io.github.glandais/vcyclist-engine)
6
+ [![Maven Central gpx](https://img.shields.io/maven-central/v/io.github.glandais/vcyclist-gpx?label=io.github.glandais%3Avcyclist-gpx)](https://central.sonatype.com/artifact/io.github.glandais/vcyclist-gpx)
7
+ [![Maven Central fit](https://img.shields.io/maven-central/v/io.github.glandais/vcyclist-fit?label=io.github.glandais%3Avcyclist-fit)](https://central.sonatype.com/artifact/io.github.glandais/vcyclist-fit)
8
+ [![Maven Central map](https://img.shields.io/maven-central/v/io.github.glandais/vcyclist-map?label=io.github.glandais%3Avcyclist-map)](https://central.sonatype.com/artifact/io.github.glandais/vcyclist-map)
6
9
 
7
10
  Kotlin Multiplatform port of [`@glandais/virtual-cyclist`](https://github.com/glandais/virtual-cyclist):
8
11
  physics-based cycling simulator that turns a static GPS trace into a virtualized ride with
@@ -38,9 +41,21 @@ TypeScript library for elevation data.
38
41
  | Module | Purpose | Targets |
39
42
  |---|---|---|
40
43
  | **`:elevation`** | Terrarium tile fetch + DEM lookup + Haversine + Douglas-Peucker 3D + triangular smoother. See [`elevation/README.md`](elevation/README.md). | JVM, JS Node, JS browser, Wasm browser |
41
- | **`:engine`** | Path model (36 fields × `DoubleArray`), physics (4 resistive `PowerProvider`s + cyclist input + `MaxSpeedComputer` + `VirtualizeService`), GPX I/O, `Enhancer` pipeline, JVM CLI. | JVM, JS Node, JS browser, Wasm browser |
44
+ | **`:gpx`** | Path model (36 fields × `DoubleArray`), resamplers, Douglas-Peucker simplifier, elevation steps, GPX I/O. Published to Maven Central; **not** published to npm — its JS output ships inside `@glandais/vcyclist-engine`. | JVM, JS Node, JS browser, Wasm browser |
45
+ | **`:engine`** | Physics (4 resistive `PowerProvider`s + cyclist input + `MaxSpeedComputer` + `VirtualizeService`), `Enhancer` pipeline, JVM CLI, JS/Wasm façades. Re-exports `:gpx` via `api`, so `io.github.glandais.engine.path.*` and `…engine.gpx.*` stay importable from `:engine`. | JVM, JS Node, JS browser, Wasm browser |
46
+ | **`:fit`** | Garmin FIT encoding. `FitCourse` model + unit conversions in commonMain; `expect object FitEncoder` with a JVM `actual` on `com.garmin:fit` and JS/Wasm `actual`s on `@garmin/fitsdk`. | JVM, JS Node, JS browser, Wasm browser |
47
+ | **`:map`** | Static map rendering: Web Mercator projection, image framing, tile download + cache, PNG output (`java.awt` / `ImageIO`). **JVM-only.** No default tile source — see [`map/README.md`](map/README.md) for the usage-policy obligations. | JVM only |
48
+ | **`:cli`** | Command-line tool (picocli). **JVM-only, not published as a library** — distributed as an executable jar. Replaces gpx2web's `gpxtools-cli`. | JVM only |
42
49
  | **`:codegen`** | Tiny build-time helper that regenerates `GeneratedPath.kt` + `PointFieldAccessors.kt` from `PointField` (run only when the field list changes). | JVM only |
43
50
 
51
+ ### Migrating from gpx2web
52
+
53
+ vcyclist replaces the `gpx` and `gpxtools-cli` modules of
54
+ [gpx2web](https://github.com/glandais/gpx2web).
55
+ [`docs/gpx2web-coverage.md`](docs/gpx2web-coverage.md) has one row per Java class — ported,
56
+ replaced, or not ported with the reason — plus the deliberate behavioural differences. For the
57
+ command-line options specifically, see [`cli/README.md`](cli/README.md).
58
+
44
59
  ## Install
45
60
 
46
61
  ### npm (Kotlin/JS or Kotlin/Wasm consumers)
@@ -64,20 +79,32 @@ dependencies {
64
79
 
65
80
  Replace `1.0.0` by the latest version shown in the badges above. KMP consumers automatically
66
81
  get the platform-specific variant (`-jvm`, `-js`, `-wasm-js`) for their target.
82
+ `vcyclist-gpx` (the `Path` model + GPX I/O) comes in transitively via `vcyclist-engine`, and
83
+ can also be depended on alone if you only need parsing and resampling.
67
84
 
68
85
  See [`docs/publishing.md`](docs/publishing.md) for the release process.
69
86
 
70
87
  ## Quick start
71
88
 
72
- ### Run the JVM CLI
89
+ ### Run the CLI
90
+
91
+ The CLI is **not** on Maven Central — it is an application, distributed as a self-contained jar
92
+ attached to each [GitHub release](https://github.com/glandais/vcyclist/releases). Download
93
+ `vcyclist-cli-<version>-all.jar` and run it with Java 21+, or build it yourself:
73
94
 
74
95
  ```bash
75
- # Enhance a GPX file with the default cyclist (80 kg / 280 W) and bike (Crr 0.004) :
76
- ./gradlew :engine:run -Pargs="enhance path/to/input.gpx -o /tmp/output.gpx"
96
+ # Build a self-contained jar, then run it from anywhere:
97
+ ./gradlew :cli:executableJar
98
+ java -jar cli/build/libs/vcyclist-cli-*-all.jar enhance route.gpx --gpx out.gpx --csv out.csv
99
+
100
+ # Or, during development:
101
+ ./gradlew :cli:run -Pargs="enhance route.gpx --gpx /tmp/out.gpx"
77
102
  ```
78
103
 
79
- The CLI runs the full enhancement pipeline (no elevation correction — no HTTP) and writes the
80
- simulated trace back to a GPX file. See [`engine/src/jvmMain/.../EngineCli.kt`](engine/src/jvmMain/kotlin/io/github/glandais/engine/EngineCli.kt).
104
+ `enhance` runs the physics pipeline; `export` produces maps, FIT, CSV and JSON from a file you
105
+ already have. Elevation correction is off unless you pass `--fix-elevation`, so nothing touches
106
+ the network by default. Full usage, exit codes and the migration table from gpx2web's
107
+ `gpxtools-cli` are in [`cli/README.md`](cli/README.md).
81
108
 
82
109
  ### Try the browser demos (elevation only)
83
110
 
@@ -115,10 +142,26 @@ suspend fun virtualize(xml: String): String {
115
142
  The Kotlin/JS variant (`@glandais/vcyclist-engine`, `@glandais/vcyclist-elevation`) runs
116
143
  **in both browser and Node.js / Bun**. The Wasm variants (`*-wasm`) are browser-only.
117
144
 
145
+ #### Getting at the exports
146
+
147
+ Kotlin/JS emits a **UMD bundle that preserves the package namespace**, so the module has
148
+ exactly one top-level export, `io`. Named imports do not work — `import { parseGpx } from
149
+ '@glandais/vcyclist-engine'` fails under Node ESM with *"Named export 'parseGpx' not found"*.
150
+ Unwrap the namespace once and destructure from it :
151
+
152
+ ```js
153
+ import * as engineRaw from '@glandais/vcyclist-engine'; // or: require('@glandais/vcyclist-engine')
154
+
155
+ const engine = engineRaw.io.github.glandais.engine;
156
+ ```
157
+
158
+ Every snippet below assumes that `engine`. `demo/src/engine-shim.ts` does the same unwrap and
159
+ adds TypeScript types for the DTOs, which Kotlin/JS emits as referenced names only.
160
+
118
161
  #### Browser
119
162
 
120
163
  ```js
121
- import { parseGpx, enhance, writeGpx, pathSize, pathTotalDistance } from '@glandais/vcyclist-engine';
164
+ const { parseGpx, enhance, writeGpx, pathSize, pathTotalDistance } = engine;
122
165
 
123
166
  const handle = parseGpx(gpxXml);
124
167
  console.log('input points:', pathSize(handle));
@@ -130,7 +173,7 @@ const xml = writeGpx(out);
130
173
  #### Node.js / Bun (with elevation correction)
131
174
 
132
175
  ```js
133
- import { parseGpx, enhance, writeGpx } from '@glandais/vcyclist-engine';
176
+ const { parseGpx, enhance, writeGpx } = engine;
134
177
 
135
178
  const handle = parseGpx(gpxXml);
136
179
  const out = await enhance(handle, { fixElevation: true }); // fetches DEM tiles, decodes WebP
@@ -141,6 +184,35 @@ const xml = writeGpx(out);
141
184
  (mapterhorn Terrarium tiles) and runs the full pipeline (densify → fix elevation → smooth →
142
185
  max speeds → virtualize → resample → simplify).
143
186
 
187
+ #### FIT export
188
+
189
+ ```js
190
+ const { parseGpx, enhance, pathToFit } = engine;
191
+
192
+ const out = await enhance(parseGpx(gpxXml), null);
193
+ const fit = pathToFit(out, 'My route', Date.parse('2026-08-01T08:00:00Z'));
194
+ // Kotlin/JS returns an Int8Array; the -wasm build returns a Uint8Array. Both are the same file.
195
+ ```
196
+
197
+ FIT has no relative clock, so the start instant is mandatory. The output is a **Course** file
198
+ (a route to follow), which is what a virtualized trace should be — not an Activity.
199
+
200
+ #### Multi-track GPX
201
+
202
+ `parseGpx` returns the **first** track, which is what most files contain. For documents with
203
+ several `<trk>` or several `<trkseg>` :
204
+
205
+ ```js
206
+ const { parseGpxTracks, parseGpxSegments, writeGpxTracks } = engine;
207
+
208
+ const tracks = parseGpxTracks(gpxXml); // one path per <trk>, segments concatenated
209
+ const segments = parseGpxSegments(gpxXml); // one path per <trkseg>, always continuous
210
+ const xml = writeGpxTracks(tracks); // one <trk> per path
211
+ ```
212
+
213
+ Concatenating segments folds the pause between them into `totalDistance` — a `<trkseg>`
214
+ boundary is a physical discontinuity. Use `parseGpxSegments` when that artefact matters.
215
+
144
216
  On Node.js / Bun, tile decoding uses [`@jsquash/webp`](https://www.npmjs.com/package/@jsquash/webp)
145
217
  (a pure-WASM WebP decoder, ~50 KB, listed as a runtime `dependency` of
146
218
  `@glandais/vcyclist-engine` and `@glandais/vcyclist-elevation`). It is loaded lazily via