Peaktime - Cycling
The bike module uses a physics-based model to estimate cycling time. Instead of simple speed tables, it solves the power-speed equation accounting for gravity, rolling resistance, and aerodynamic drag.
Basic Usage
import * as bike from 'peaktime/bike';
import { parseGPXOrThrow } from 'peaktime';
const route = parseGPXOrThrow(gpxContent);
// Quick estimate without a GPX point array
const minutes = bike.quickEstimate(
50000, // distance in meters
1200, // elevation gain
800, // elevation loss
'trained',
'road',
'smooth_pavement',
75 // rider weight in kg
);Physics Model
The model solves for speed given power output and three resistance forces:
Gravity: F_gravity = m * g * sin(grade)
Rolling resistance: F_rolling = Crr * m * g * cos(grade)
Aerodynamic drag: F_aero = 0.5 * rho * CdA * v^2
On uphills where aerodynamic drag is negligible, speed is calculated directly. On flats and downhills, the full cubic equation is solved using Cardano's formula.
Cycling Parameters
The CyclingParams interface controls the physics model:
interface CyclingParams {
ftpWatts: number; // Functional Threshold Power
riderWeightKg: number; // Rider weight
bikeWeightKg: number; // Bike weight
crr: number; // Rolling resistance coefficient
cdA: number; // Drag coefficient * frontal area (m^2)
airDensity: number; // kg/m^3 (default: 1.225)
drivetrainEfficiency: number; // Power transfer (default: 0.97)
}You can build params from presets:
const params = bike.getDefaultCyclingParams('trained', 'road', 75);Fitness Levels
Fitness is based on FTP (Functional Threshold Power) per kilogram:
| Level | FTP/kg Range | Description |
|---|---|---|
casual |
Low | Weekend rider |
recreational |
Low-mid | Regular rider |
trained |
Mid | Structured training |
competitive |
Mid-high | Racing |
elite |
High | Top amateur |
pro |
Highest | Professional |
Bike Types
Each bike type has default weight, aerodynamic profile, and rolling resistance:
| Type | Description |
|---|---|
road |
Drop bars, narrow tires |
gravel |
Drop bars, wider tires |
mtb |
Flat bars, suspension, knobby tires |
tt |
Aero bars, time trial position |
ebike |
Pedal-assist electric |
Terrain
Terrain adjusts the rolling resistance coefficient:
| Terrain | Description |
|---|---|
smooth_pavement |
Fresh asphalt |
rough_pavement |
Older road surface |
gravel |
Packed gravel road |
hardpack |
Hard dirt |
singletrack |
Mountain bike trail |
mud |
Wet, soft surface |
Segment-Level Detail
For GPX routes, the model calculates per-segment power, speed, and time:
const estimate = bike.estimateCyclingTime(route.points, params);
console.log(estimate.movingTime); // Total minutes
console.log(estimate.averagePower); // Average watts
console.log(estimate.normalizedPower); // Normalized power
console.log(estimate.averageSpeed); // km/hEach segment includes grade, power output, and speed:
for (const seg of estimate.segments) {
console.log(`Grade: ${seg.gradePercent}% → ${seg.speedKmh} km/h at ${seg.powerWatts}W`);
}Planning a Ride
Use the bike planner to find the optimal start time:
const summary = bike.createBikePlanSummary(route, new Date('2026-06-21'), 'sunset');
console.log(summary.plan.startTime);
console.log(summary.plan.ridingDuration);
console.log(summary.plan.feasible);The planner finds the highest point on the route and calculates how long the ride takes to reach it, then works backward from the target sun event.