npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@glandais/vcyclist-engine-wasm

v2.0.0

Published

[![npm engine](https://img.shields.io/npm/v/@glandais/vcyclist-engine?label=%40glandais%2Fvcyclist-engine)](https://www.npmjs.com/package/@glandais/vcyclist-engine) [![npm elevation](https://img.shields.io/npm/v/@glandais/vcyclist-elevation?label=%40gland

Readme

vcyclist

npm engine npm elevation Maven Central Maven Central gpx Maven Central fit Maven Central map

Kotlin Multiplatform port of @glandais/virtual-cyclist: physics-based cycling simulator that turns a static GPS trace into a virtualized ride with realistic speeds, times and power estimates. Inspired by gpx2web (Java) for the physics model and the @glandais/elevation TypeScript library for elevation data.

                        ┌──────────────┐
        sample.gpx ────▶│ GpxParser    │
                        └──────┬───────┘
                               ▼
            ┌─────────────────────────────────────────┐
            │  Enhancer (orchestrator)                │
            │  ├─ PointPerDistance(-1, 30)            │
            │  ├─ fixElevation (Terrarium tiles)*     │
            │  ├─ PointPerDistance(1, 2)              │
            │  ├─ smoothElevation (150 m kernel)      │
            │  ├─ MaxSpeedComputer (cornering+braking)│
            │  ├─ VirtualizeService (1 Hz physics)    │
            │  ├─ PointPerSecond (uniform sampling)   │
            │  └─ PathSimplifier (Douglas-Peucker 3D) │
            └──────────────────┬──────────────────────┘
                               ▼
                        ┌──────────────┐
                        │ GpxWriter    │────▶ output.gpx
                        └──────────────┘
            (*) optional — needs an ElevationProvider

Modules

| Module | Purpose | Targets | |---|---|---| | :elevation | Terrarium tile fetch + DEM lookup + Haversine + Douglas-Peucker 3D + triangular smoother. See elevation/README.md. | JVM, JS Node, JS browser, Wasm browser | | :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 | | :engine | Physics (4 resistive PowerProviders + 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 | | :fit | Garmin FIT encoding. FitCourse model + unit conversions in commonMain; expect object FitEncoder with a JVM actual on com.garmin:fit and JS/Wasm actuals on @garmin/fitsdk. | JVM, JS Node, JS browser, Wasm browser | | :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 for the usage-policy obligations. | JVM only | | :cli | Command-line tool (picocli). JVM-only, not published as a library — distributed as an executable jar. Replaces gpx2web's gpxtools-cli. | JVM only | | :codegen | Tiny build-time helper that regenerates GeneratedPath.kt + PointFieldAccessors.kt from PointField (run only when the field list changes). | JVM only |

Migrating from gpx2web

vcyclist replaces the gpx and gpxtools-cli modules of gpx2web. docs/gpx2web-coverage.md has one row per Java class — ported, replaced, or not ported with the reason — plus the deliberate behavioural differences. For the command-line options specifically, see cli/README.md.

Install

npm (Kotlin/JS or Kotlin/Wasm consumers)

npm install @glandais/vcyclist-engine          # Kotlin/JS bundle
npm install @glandais/vcyclist-engine-wasm     # Kotlin/Wasm bundle
npm install @glandais/vcyclist-elevation       # Kotlin/JS bundle
npm install @glandais/vcyclist-elevation-wasm  # Kotlin/Wasm bundle

Gradle / Maven (JVM or KMP consumers)

// Gradle Kotlin DSL
dependencies {
    implementation("io.github.glandais:vcyclist-engine:1.0.0")    // pulls -jvm / -js / -wasm-js per target
    implementation("io.github.glandais:vcyclist-elevation:1.0.0")
}

Replace 1.0.0 by the latest version shown in the badges above. KMP consumers automatically get the platform-specific variant (-jvm, -js, -wasm-js) for their target. vcyclist-gpx (the Path model + GPX I/O) comes in transitively via vcyclist-engine, and can also be depended on alone if you only need parsing and resampling.

See docs/publishing.md for the release process.

Quick start

Run the CLI

The CLI is not on Maven Central — it is an application, distributed as a self-contained jar attached to each GitHub release. Download vcyclist-cli-<version>-all.jar and run it with Java 21+, or build it yourself:

# Build a self-contained jar, then run it from anywhere:
./gradlew :cli:executableJar
java -jar cli/build/libs/vcyclist-cli-*-all.jar enhance route.gpx --gpx out.gpx --csv out.csv

# Or, during development:
./gradlew :cli:run -Pargs="enhance route.gpx --gpx /tmp/out.gpx"

enhance runs the physics pipeline; export produces maps, FIT, CSV and JSON from a file you already have. Elevation correction is off unless you pass --fix-elevation, so nothing touches the network by default. Full usage, exit codes and the migration table from gpx2web's gpxtools-cli are in cli/README.md.

Try the browser demos (elevation only)

# Kotlin/Wasm demo
./gradlew :elevation:wasmJsBrowserDevelopmentRun
# Kotlin/JS demo (sibling, same UI)
./gradlew :elevation:jsBrowserDevelopmentRun

Both demos share the original TS demo UI (Leaflet + Chart.js + GPX upload). See elevation/README.md for details.

Use from Kotlin

import io.github.glandais.engine.Enhancer
import io.github.glandais.engine.gpx.GpxParser
import io.github.glandais.engine.gpx.firstTrackAsPath

suspend fun virtualize(xml: String): String {
    val path = GpxParser.parse(xml).firstTrackAsPath()
    val out = Enhancer.enhanceCourseDefault(path)  // pure physics, no HTTP
    return io.github.glandais.engine.gpx.GpxWriter.write(
        out.toGpxDocument(trackName = "virtualized")
    )
}

Use from JavaScript / TypeScript

generateTypeScriptDefinitions() is enabled on both js(IR) and wasmJs, so you get a .d.ts next to the bundle in build/dist/{js,wasmJs}/productionExecutable/vcyclist-engine.d.{ts,mts}.

The Kotlin/JS variant (@glandais/vcyclist-engine, @glandais/vcyclist-elevation) runs in both browser and Node.js / Bun. The Wasm variants (*-wasm) are browser-only.

Getting at the exports

Kotlin/JS emits a UMD bundle that preserves the package namespace, so the module has exactly one top-level export, io. Named imports do not work — import { parseGpx } from '@glandais/vcyclist-engine' fails under Node ESM with "Named export 'parseGpx' not found". Unwrap the namespace once and destructure from it :

import * as engineRaw from '@glandais/vcyclist-engine';   // or: require('@glandais/vcyclist-engine')

const engine = engineRaw.io.github.glandais.engine;

Every snippet below assumes that engine. demo/src/engine-shim.ts does the same unwrap and adds TypeScript types for the DTOs, which Kotlin/JS emits as referenced names only.

Browser

const { parseGpx, enhance, writeGpx, pathSize, pathTotalDistance } = engine;

const handle = parseGpx(gpxXml);
console.log('input points:', pathSize(handle));
const out = await enhance(handle, null);                    // physics only, no HTTP
console.log('output:', pathSize(out), pathTotalDistance(out), 'm');
const xml = writeGpx(out);

Node.js / Bun (with elevation correction)

const { parseGpx, enhance, writeGpx } = engine;

const handle = parseGpx(gpxXml);
const out = await enhance(handle, { fixElevation: true });  // fetches DEM tiles, decodes WebP
const xml = writeGpx(out);

enhance(..., { fixElevation: true }) auto-instantiates a default ElevationProvider (mapterhorn Terrarium tiles) and runs the full pipeline (densify → fix elevation → smooth → max speeds → virtualize → resample → simplify).

FIT export

const { parseGpx, enhance, pathToFit } = engine;

const out = await enhance(parseGpx(gpxXml), null);
const fit = pathToFit(out, 'My route', Date.parse('2026-08-01T08:00:00Z'));
// Kotlin/JS returns an Int8Array; the -wasm build returns a Uint8Array. Both are the same file.

FIT has no relative clock, so the start instant is mandatory. The output is a Course file (a route to follow), which is what a virtualized trace should be — not an Activity.

Multi-track GPX

parseGpx returns the first track, which is what most files contain. For documents with several <trk> or several <trkseg> :

const { parseGpxTracks, parseGpxSegments, writeGpxTracks } = engine;

const tracks = parseGpxTracks(gpxXml);       // one path per <trk>, segments concatenated
const segments = parseGpxSegments(gpxXml);   // one path per <trkseg>, always continuous
const xml = writeGpxTracks(tracks);          // one <trk> per path

Concatenating segments folds the pause between them into totalDistance — a <trkseg> boundary is a physical discontinuity. Use parseGpxSegments when that artefact matters.

On Node.js / Bun, tile decoding uses @jsquash/webp (a pure-WASM WebP decoder, ~50 KB, listed as a runtime dependency of @glandais/vcyclist-engine and @glandais/vcyclist-elevation). It is loaded lazily via eval('require'), so browser bundlers do not pull it into the browser build. Requires Node ≥ 18 (globalThis.fetch is built-in since Node 18 / Bun) ; Node 22+ recommended for ESM require() support.

Try the interactive demo

The demo/ module is a Vue 3 + Vite frontend that exercises the Kotlin/JS engine end-to-end in a browser (GPX upload, configurable cyclist / bike / wind / power, chart + map, hover sync).

./gradlew :demo:assemble
python -m http.server -d demo/dist 8000  # or any static file server

See demo/README.md for the dev workflow and architecture.

Build & test

./gradlew check                         # full build + all tests on all targets
./gradlew :engine:allTests              # engine tests across JVM / JS Node / JS browser / Wasm browser
./gradlew :elevation:allTests           # elevation tests
./gradlew :elevation:jvmTest --tests '*Integration*' \
          -PINTEGRATION=1               # live HTTP tests against tiles.mapterhorn.com
./gradlew ktlintCheck                   # lint

Layout

vcyclist/
├── settings.gradle.kts          # multi-module Gradle KMP project
├── gradle/libs.versions.toml    # version catalog (Kotlin 2.3.21, coroutines 1.11, xmlutil 0.91, …)
├── docs/
│   ├── PLAN.md                  # task-by-task progress (Phases 1-2bis)
│   ├── parity.md                # parity strategy vs the TS reference
│   ├── elevation-integration.md # how to run live HTTP integration tests
│   ├── kotlin-wasm-jvm-webp.md  # Kotlin/Wasm ↔ JS interop guide
│   └── tasks/                   # one Markdown per implementation task (00-31, + bonus demos)
├── elevation/                   # :elevation KMP module
├── engine/                      # :engine KMP module (depends on :elevation)
└── codegen/                     # :codegen JVM helper for Path accessor generation

Status

  • Phase 1:elevation module port (tasks 00-09) : Terrarium tiles, Haversine, ECEF, Douglas-Peucker 3D, smoother, LRU cache + TileManager, ElevationProvider, live HTTP integration.
  • Phase 2:engine module port (tasks 10-28) : Path model, Cyclist/Bike/Course, GPX I/O, full physics, simulation, post-processing, Enhancer, CLI, @JsExport façades.
  • Phase 2bis — pipeline fidelity fixes (tasks 29-31) : VirtualizeService last-point timestamp, PointPerDistance port, integration into Enhancer.
  • Phase 3 — Node.js / Bun support (tasks 32-33) : runtime-detection in TileFetcher.js.kt (browser path unchanged, Node path uses globalThis.fetch + @jsquash/webp WASM decoder loaded via lazy eval('require')), webpack externals to keep the browser bundle free of @jsquash/webp, ElevationProvider auto-instantiation in EngineJsApi.enhance when opts.fixElevation is true (JS + Wasm façades), 6 jsTest classes gated by INTEGRATION=1.

Total :engine test coverage : 32 test classes / ~326 commonTest cases / 4 targets = ~1300 green executions, plus JVM-only smoke tests for the CLI and the full pipeline.

End-to-end smoke (after Phase 2bis) : sample.gpx (3569 source points, 130 km, ~4550 m gain) runs through the complete Enhancer pipeline in ~1.7 s on JVM, producing ~1000 simplified output points covering ~128.6 km / ~5.3 h of simulated ride.

Documentation

Contributing

develop is the default and only long-lived branch — there is no main. Open PRs against develop using Conventional Commits : feat: triggers a minor release, fix: a patch, anything else is a no-op release-wise. Every push to develop runs the full multi-target test suite via .github/workflows/release.yml and, if green, lets semantic-release tag a new version, publish to Maven Central + npm, and commit the version bump back to develop with [skip ci]. See docs/publishing.md for the full flow.

License

Apache License 2.0, aligned with the upstream gpx2web project. See the Maven Central POM metadata in engine/build.gradle.kts and elevation/build.gradle.kts. A top-level LICENSE file will be added before the first public release.