@glandais/vcyclist-engine 0.0.0 → 1.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 +182 -0
- package/kotlin-kotlin-stdlib.js +13 -13
- package/kotlin-kotlin-stdlib.js.map +1 -1
- package/kotlinx-atomicfu.js.map +1 -1
- package/kotlinx-browser.js.map +1 -1
- package/kotlinx-coroutines-core.js +2 -2
- package/kotlinx-coroutines-core.js.map +1 -1
- package/package.json +1 -1
- package/xmlutil-core.js +5 -5
- package/xmlutil-core.js.map +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# vcyclist
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@glandais/vcyclist-engine)
|
|
4
|
+
[](https://www.npmjs.com/package/@glandais/vcyclist-elevation)
|
|
5
|
+
[](https://central.sonatype.com/artifact/io.github.glandais/vcyclist-engine)
|
|
6
|
+
|
|
7
|
+
Kotlin Multiplatform port of [`@glandais/virtual-cyclist`](https://github.com/glandais/virtual-cyclist):
|
|
8
|
+
physics-based cycling simulator that turns a static GPS trace into a virtualized ride with
|
|
9
|
+
realistic speeds, times and power estimates. Inspired by [gpx2web](https://github.com/glandais/gpx2web)
|
|
10
|
+
(Java) for the physics model and the [`@glandais/elevation`](https://github.com/glandais/elevation)
|
|
11
|
+
TypeScript library for elevation data.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌──────────────┐
|
|
15
|
+
sample.gpx ────▶│ GpxParser │
|
|
16
|
+
└──────┬───────┘
|
|
17
|
+
▼
|
|
18
|
+
┌─────────────────────────────────────────┐
|
|
19
|
+
│ Enhancer (orchestrator) │
|
|
20
|
+
│ ├─ PointPerDistance(-1, 30) │
|
|
21
|
+
│ ├─ fixElevation (Terrarium tiles)* │
|
|
22
|
+
│ ├─ PointPerDistance(1, 2) │
|
|
23
|
+
│ ├─ smoothElevation (150 m kernel) │
|
|
24
|
+
│ ├─ MaxSpeedComputer (cornering+braking)│
|
|
25
|
+
│ ├─ VirtualizeService (1 Hz physics) │
|
|
26
|
+
│ ├─ PointPerSecond (uniform sampling) │
|
|
27
|
+
│ └─ PathSimplifier (Douglas-Peucker 3D) │
|
|
28
|
+
└──────────────────┬──────────────────────┘
|
|
29
|
+
▼
|
|
30
|
+
┌──────────────┐
|
|
31
|
+
│ GpxWriter │────▶ output.gpx
|
|
32
|
+
└──────────────┘
|
|
33
|
+
(*) optional — needs an ElevationProvider
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Modules
|
|
37
|
+
|
|
38
|
+
| Module | Purpose | Targets |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| **`: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 |
|
|
42
|
+
| **`:codegen`** | Tiny build-time helper that regenerates `GeneratedPath.kt` + `PointFieldAccessors.kt` from `PointField` (run only when the field list changes). | JVM only |
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
### npm (Kotlin/JS or Kotlin/Wasm consumers)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @glandais/vcyclist-engine # Kotlin/JS bundle
|
|
50
|
+
npm install @glandais/vcyclist-engine-wasm # Kotlin/Wasm bundle
|
|
51
|
+
npm install @glandais/vcyclist-elevation # Kotlin/JS bundle
|
|
52
|
+
npm install @glandais/vcyclist-elevation-wasm # Kotlin/Wasm bundle
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Gradle / Maven (JVM or KMP consumers)
|
|
56
|
+
|
|
57
|
+
```kotlin
|
|
58
|
+
// Gradle Kotlin DSL
|
|
59
|
+
dependencies {
|
|
60
|
+
implementation("io.github.glandais:vcyclist-engine:1.0.0") // pulls -jvm / -js / -wasm-js per target
|
|
61
|
+
implementation("io.github.glandais:vcyclist-elevation:1.0.0")
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Replace `1.0.0` by the latest version shown in the badges above. KMP consumers automatically
|
|
66
|
+
get the platform-specific variant (`-jvm`, `-js`, `-wasm-js`) for their target.
|
|
67
|
+
|
|
68
|
+
See [`docs/publishing.md`](docs/publishing.md) for the release process.
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
### Run the JVM CLI
|
|
73
|
+
|
|
74
|
+
```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"
|
|
77
|
+
```
|
|
78
|
+
|
|
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).
|
|
81
|
+
|
|
82
|
+
### Try the browser demos (elevation only)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Kotlin/Wasm demo
|
|
86
|
+
./gradlew :elevation:wasmJsBrowserDevelopmentRun
|
|
87
|
+
# Kotlin/JS demo (sibling, same UI)
|
|
88
|
+
./gradlew :elevation:jsBrowserDevelopmentRun
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Both demos share the [original TS demo](https://github.com/glandais/elevation) UI (Leaflet +
|
|
92
|
+
Chart.js + GPX upload). See [`elevation/README.md`](elevation/README.md) for details.
|
|
93
|
+
|
|
94
|
+
### Use from Kotlin
|
|
95
|
+
|
|
96
|
+
```kotlin
|
|
97
|
+
import io.github.glandais.engine.Enhancer
|
|
98
|
+
import io.github.glandais.engine.gpx.GpxParser
|
|
99
|
+
import io.github.glandais.engine.gpx.firstTrackAsPath
|
|
100
|
+
|
|
101
|
+
suspend fun virtualize(xml: String): String {
|
|
102
|
+
val path = GpxParser.parse(xml).firstTrackAsPath()
|
|
103
|
+
val out = Enhancer.enhanceCourseDefault(path) // pure physics, no HTTP
|
|
104
|
+
return io.github.glandais.engine.gpx.GpxWriter.write(
|
|
105
|
+
out.toGpxDocument(trackName = "virtualized")
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Use from JavaScript / TypeScript
|
|
111
|
+
|
|
112
|
+
`generateTypeScriptDefinitions()` is enabled on both `js(IR)` and `wasmJs`, so you get a
|
|
113
|
+
`.d.ts` next to the bundle in `build/dist/{js,wasmJs}/productionExecutable/vcyclist-engine.d.{ts,mts}`.
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { parseGpx, enhance, writeGpx, pathSize, pathTotalDistance } from './vcyclist-engine.mjs';
|
|
117
|
+
|
|
118
|
+
const handle = parseGpx(gpxXml);
|
|
119
|
+
console.log('input points:', pathSize(handle));
|
|
120
|
+
const out = await enhance(handle, null);
|
|
121
|
+
console.log('output:', pathSize(out), pathTotalDistance(out), 'm');
|
|
122
|
+
const xml = writeGpx(out);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Build & test
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
./gradlew check # full build + all tests on all targets
|
|
129
|
+
./gradlew :engine:allTests # engine tests across JVM / JS Node / JS browser / Wasm browser
|
|
130
|
+
./gradlew :elevation:allTests # elevation tests
|
|
131
|
+
./gradlew :elevation:jvmTest --tests '*Integration*' \
|
|
132
|
+
-PINTEGRATION=1 # live HTTP tests against tiles.mapterhorn.com
|
|
133
|
+
./gradlew ktlintCheck # lint
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Layout
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
vcyclist/
|
|
140
|
+
├── settings.gradle.kts # multi-module Gradle KMP project
|
|
141
|
+
├── gradle/libs.versions.toml # version catalog (Kotlin 2.3.21, coroutines 1.11, xmlutil 0.91, …)
|
|
142
|
+
├── docs/
|
|
143
|
+
│ ├── PLAN.md # task-by-task progress (Phases 1-2bis)
|
|
144
|
+
│ ├── parity.md # parity strategy vs the TS reference
|
|
145
|
+
│ ├── elevation-integration.md # how to run live HTTP integration tests
|
|
146
|
+
│ ├── kotlin-wasm-jvm-webp.md # Kotlin/Wasm ↔ JS interop guide
|
|
147
|
+
│ └── tasks/ # one Markdown per implementation task (00-31, + bonus demos)
|
|
148
|
+
├── elevation/ # :elevation KMP module
|
|
149
|
+
├── engine/ # :engine KMP module (depends on :elevation)
|
|
150
|
+
└── codegen/ # :codegen JVM helper for Path accessor generation
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Status
|
|
154
|
+
|
|
155
|
+
- ✅ **Phase 1** — `:elevation` module port (tasks 00-09) : Terrarium tiles, Haversine, ECEF,
|
|
156
|
+
Douglas-Peucker 3D, smoother, LRU cache + TileManager, `ElevationProvider`, live HTTP integration.
|
|
157
|
+
- ✅ **Phase 2** — `:engine` module port (tasks 10-28) : Path model, Cyclist/Bike/Course,
|
|
158
|
+
GPX I/O, full physics, simulation, post-processing, `Enhancer`, CLI, `@JsExport` façades.
|
|
159
|
+
- ✅ **Phase 2bis** — pipeline fidelity fixes (tasks 29-31) : `VirtualizeService` last-point
|
|
160
|
+
timestamp, `PointPerDistance` port, integration into `Enhancer`.
|
|
161
|
+
|
|
162
|
+
Total `:engine` test coverage : 32 test classes / ~326 commonTest cases / 4 targets =
|
|
163
|
+
~1300 green executions, plus JVM-only smoke tests for the CLI and the full pipeline.
|
|
164
|
+
|
|
165
|
+
End-to-end smoke (after Phase 2bis) : sample.gpx (3569 source points, 130 km, ~4550 m gain)
|
|
166
|
+
runs through the complete `Enhancer` pipeline in ~1.7 s on JVM, producing ~1000 simplified
|
|
167
|
+
output points covering ~128.6 km / ~5.3 h of simulated ride.
|
|
168
|
+
|
|
169
|
+
## Documentation
|
|
170
|
+
|
|
171
|
+
- [`docs/PLAN.md`](docs/PLAN.md) — task-by-task plan with commit hashes for every step.
|
|
172
|
+
- [`docs/tasks/`](docs/tasks/) — detailed Markdown spec for each task (00-31 + bonus demos).
|
|
173
|
+
- [`docs/parity.md`](docs/parity.md) — TS↔Kotlin parity approach and tolerances.
|
|
174
|
+
- [`docs/kotlin-wasm-jvm-webp.md`](docs/kotlin-wasm-jvm-webp.md) — Kotlin/Wasm ↔ JS interop
|
|
175
|
+
guide that underpins the `@JsExport` façades and the WebP tile decoding.
|
|
176
|
+
- [`elevation/README.md`](elevation/README.md) — `:elevation` module details + browser demos.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
Apache License 2.0, aligned with the upstream `gpx2web` project. See the Maven Central POM
|
|
181
|
+
metadata in `engine/build.gradle.kts` and `elevation/build.gradle.kts`. A top-level `LICENSE`
|
|
182
|
+
file will be added before the first public release.
|
package/kotlin-kotlin-stdlib.js
CHANGED
|
@@ -46,17 +46,6 @@ if (typeof Array.prototype.fill === 'undefined') {
|
|
|
46
46
|
Object.defineProperty(TypedArray.prototype, 'fill', {value: Array.prototype.fill});
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
|
-
if (typeof Math.clz32 === 'undefined') {
|
|
50
|
-
Math.clz32 = function (log, LN2) {
|
|
51
|
-
return function (x) {
|
|
52
|
-
var asUint = x >>> 0;
|
|
53
|
-
if (asUint === 0) {
|
|
54
|
-
return 32;
|
|
55
|
-
}
|
|
56
|
-
return 31 - (log(asUint) / LN2 | 0) | 0; // the "| 0" acts like math.floor
|
|
57
|
-
};
|
|
58
|
-
}(Math.log, Math.LN2);
|
|
59
|
-
}
|
|
60
49
|
if (typeof Math.hypot === 'undefined') {
|
|
61
50
|
Math.hypot = function () {
|
|
62
51
|
var y = 0;
|
|
@@ -70,6 +59,17 @@ if (typeof Math.hypot === 'undefined') {
|
|
|
70
59
|
return Math.sqrt(y);
|
|
71
60
|
};
|
|
72
61
|
}
|
|
62
|
+
if (typeof Math.clz32 === 'undefined') {
|
|
63
|
+
Math.clz32 = function (log, LN2) {
|
|
64
|
+
return function (x) {
|
|
65
|
+
var asUint = x >>> 0;
|
|
66
|
+
if (asUint === 0) {
|
|
67
|
+
return 32;
|
|
68
|
+
}
|
|
69
|
+
return 31 - (log(asUint) / LN2 | 0) | 0; // the "| 0" acts like math.floor
|
|
70
|
+
};
|
|
71
|
+
}(Math.log, Math.LN2);
|
|
72
|
+
}
|
|
73
73
|
//endregion
|
|
74
74
|
(function (factory) {
|
|
75
75
|
if (typeof define === 'function' && define.amd)
|
|
@@ -112,12 +112,12 @@ if (typeof Math.hypot === 'undefined') {
|
|
|
112
112
|
initMetadataForClass(AbstractCollection, 'AbstractCollection', VOID, VOID, [Collection]);
|
|
113
113
|
initMetadataForClass(AbstractMutableCollection, 'AbstractMutableCollection', VOID, AbstractCollection, [Collection]);
|
|
114
114
|
initMetadataForClass(IteratorImpl, 'IteratorImpl');
|
|
115
|
-
initMetadataForClass(AbstractMutableList, 'AbstractMutableList', VOID, AbstractMutableCollection, [
|
|
115
|
+
initMetadataForClass(AbstractMutableList, 'AbstractMutableList', VOID, AbstractMutableCollection, [KtList, Collection]);
|
|
116
116
|
initMetadataForClass(AbstractMap, 'AbstractMap', VOID, VOID, [KtMap]);
|
|
117
117
|
initMetadataForClass(AbstractMutableMap, 'AbstractMutableMap', VOID, AbstractMap, [KtMap]);
|
|
118
118
|
initMetadataForClass(AbstractMutableSet, 'AbstractMutableSet', VOID, AbstractMutableCollection, [KtSet, Collection]);
|
|
119
119
|
initMetadataForCompanion(Companion_2);
|
|
120
|
-
initMetadataForClass(ArrayList, 'ArrayList', ArrayList_init_$Create$, AbstractMutableList, [
|
|
120
|
+
initMetadataForClass(ArrayList, 'ArrayList', ArrayList_init_$Create$, AbstractMutableList, [KtList, Collection]);
|
|
121
121
|
initMetadataForClass(HashMap, 'HashMap', HashMap_init_$Create$, AbstractMutableMap, [KtMap]);
|
|
122
122
|
initMetadataForClass(HashMapKeys, 'HashMapKeys', VOID, AbstractMutableSet, [KtSet, Collection]);
|
|
123
123
|
initMetadataForClass(HashMapEntrySetBase, 'HashMapEntrySetBase', VOID, AbstractMutableSet, [KtSet, Collection]);
|