Skip to content

glandais/gpx2web

Repository files navigation

GPX cycling tools

Java libraries available here :

<repositories>
    <repository>
        <id>gpx2web</id>
        <url>https://repo.repsy.io/mvn/glandais/gpx2web</url>
    </repository>
</repositories>

GPX Stuff (gpx module)

  • Read GPX files

  • Write GPX files

  • Write FIT file (single path)

  • Fix elevation

  • Smooth elevation, compute total elevation

  • Detect climbs

  • Simplify GPX file (both on location and elevation)

  • Produce static map with map tiles

  • Produce static map with elevation

Power

References:

Wheel bearings

The wheel bearings power loss is calculated as a quadratic function of speed:

Formula: -speed * (91 + 8.7 * speed) / 1000.0

Implementation: WheelBearingsPowerProvider (io.github.glandais.gpx.virtual.power.rolling:22)

  • Returns negative power (resistance/loss)
  • Linear and quadratic speed components
  • Constants: 91 (linear coefficient) + 8.7 (quadratic coefficient) from Martin et al. (1998)
  • Result in Watts

Verification: ✅ Formula matches academic literature for wheel and bearing resistance

Rolling resistance

Rolling resistance accounts for tire deformation and surface friction, adjusted for road grade:

Formula: -cos(atan(grade)) * mass * g * speed * crr

Implementation: RollingResistancePowerProvider (io.github.glandais.gpx.virtual.power.rolling:29)

final double mKg = course.getCyclist().getMKg();     // Total mass (cyclist + bike)
final double crr = course.getBike().getCrr();        // Rolling resistance coefficient
final double grade = location.getGrade();           // Road gradient

double coef = Math.cos(Math.atan(grade));           // Grade adjustment factor
double powerRollingResistance = -coef * mKg * Constants.G * location.getSpeed() * crr;

Parameters:

  • mKg: Total system mass (kg)
  • crr: Rolling resistance coefficient (bike-specific)
  • grade: Road gradient (rise/run)
  • Constants.G: Gravitational acceleration (9.8 m/s²)
  • Returns negative power (resistance/loss) in Watts

Verification: ✅ Formula correctly implements standard rolling resistance physics

  • The cos(atan(grade)) adjustment is mathematically valid for inclined surfaces
  • Formula matches F_rolling = g × cos(arctan(grade)) × mass × speed × crr
  • Grade adjustment accounts for reduced normal force on inclines

Gravity

Gravitational power accounts for the energy required to climb (or gained when descending) on inclined surfaces:

Formula: -sin(atan(grade)) * mass * g * speed

Implementation: GravPowerProvider (io.github.glandais.gpx.virtual.power.grav:26)

final double mKg = course.getCyclist().getMKg();     // Total mass (cyclist + bike)
double grade = location.getGrade();                  // Road gradient (rise/run)
double coef = Math.sin(Math.atan(grade));           // Grade component factor
double powerGravity = -mKg * Constants.G * location.getSpeed() * coef;

Parameters:

  • mKg: Total system mass (kg)
  • grade: Road gradient (rise/run ratio)
  • coef: sin(atan(grade)) - converts grade to force component parallel to slope
  • Constants.G: Gravitational acceleration (9.8 m/s²)
  • Returns negative power for climbs (energy required), positive for descents

Verification: ✅ Formula correctly implements standard gravitational physics for inclines

  • The sin(atan(grade)) converts percentage grade to gravitational force component
  • Formula matches F_gravity = g × sin(arctan(grade)) × mass × speed from Martin et al. (1998)
  • Accounts for energy required to overcome gravity when climbing or energy gained when descending

Aerodynamic drag

Aerodynamic power represents the largest resistance component at higher cycling speeds, following a cubic relationship with speed:

Part 1: No Wind Conditions

Formula: -aeroCoef * speed³

Implementation: AeroPowerProvider (io.github.glandais.gpx.virtual.power.aero:29)

if (wind.windSpeed() == 0) {
    double speed = location.getSpeed();
    p_air = -aeroCoef * speed * speed * speed;
}

Aerodynamic coefficient calculation:

// From AeroProviderConstant
aeroCoef = cyclist.getCd() * cyclist.getA() * course.getRho() / 2

Parameters:

  • aeroCoef: Combined aerodynamic coefficient (Cd × A × ρ / 2)
  • Cd: Drag coefficient (cyclist-specific, typically 0.8-1.2)
  • A: Frontal area (m², typically 0.3-0.5)
  • ρ: Air density (kg/m³, typically 1.225 at sea level)
  • speed: Cyclist velocity (m/s)

Verification: ✅ Standard aerodynamic drag formula P = 0.5 × ρ × Cd × A × v³

Part 2: With Wind Conditions

Formula: Complex wind-adjusted calculation using relative velocity

Implementation: AeroPowerProvider.computePAirWithWind() (io.github.glandais.gpx.virtual.power.aero:61)

double alpha = windDirectionAsBearing - bearing;           // Wind angle relative to travel direction
double l1 = speed + windSpeed * Math.cos(alpha);          // Relative velocity component
double l2 = Math.pow(l1, 2);                             // Squared relative velocity
double l3 = speed * speed + windSpeed * windSpeed +       // Total relative velocity squared
           2 * speed * windSpeed * Math.cos(alpha);
double l4 = l2 / l3;                                      // Velocity ratio
double mu = 1.2;                                          // Correction factor
double lambda = l4 + mu * (1 - l4);                      // Combined correction factor

return -aeroCoef * lambda * Math.sqrt(l3) * l1 * speed;

Wind parameters:

  • alpha: Angle between wind direction and travel bearing (radians)
  • windSpeed: Wind velocity (m/s)
  • windDirection: Wind direction (radians, 0=North, π/2=East)
  • bearing: Cyclist travel direction (radians)
  • mu: Empirical correction factor (1.2)
  • lambda: Combined velocity/correction factor

Verification: ✅ Formula validated against Isvan (2011) "Power Management for Lightweight Vehicles"

Academic Source: The wind formula implements Equations 21, 22, 24, and 28 from:

Mathematical Derivation:

  1. Apparent wind calculation (Equations 21-22):

    • l3 = u² + v² + 2uvcos(α) → apparent wind speed squared: w² = u² + v² + 2uvcos(α)
    • l1 = u + vcos(α) → component: u + vcos(α)
  2. Aerodynamic aspect ratio correction (Equation 24):

    • l4 = l2/l3 = (u + vcos(α))²/(u² + v² + 2uvcos(α))
    • λ = l4 + μ(1-l4) where μ = 1.2 (crosswind to headwind drag ratio for cyclist)
    • This accounts for effective drag area changes with wind angle
  3. Final power calculation (Equation 28 adapted):

    • P = -aeroCoef × λ × √(w²) × (u + vcos(α)) × u
    • P = -aeroCoef × λ × √(l3) × l1 × speed

Physical meaning:

  • Handles headwind/tailwind via cos(α) component
  • Applies crosswind correction factor λ based on aerodynamic aspect ratio
  • μ = 1.2 represents measured drag ratio: crosswind vs headwind for cyclist shape
  • Formula accounts for effective drag area changes with wind angle

Default Parameters

The system uses academically validated default parameters for virtual cyclist simulation:

Bicycle Parameters (Constants.java)

Parameter Value Unit Description Academic Source
DEFAULT_CRR 0.004 - Rolling resistance coefficient bicyclerollingresistance.com research
DEFAULT_INERTIA_FRONT 0.05 kg⋅m² Front wheel rotational inertia Physics education materials
DEFAULT_INERTIA_REAR 0.07 kg⋅m² Rear wheel rotational inertia Physics education materials
DEFAULT_WHEEL_RADIUS 0.7 m Wheel radius (700c + tire) Standard wheel sizing
DEFAULT_DRIVETRAIN_EFFICIENCY 0.976 - Mechanical efficiency Typical modern road bike
DEFAULT_AIR_DENSITY 1.225 kg/m³ Air density at sea level ISO Standard Atmosphere (15°C, 1 atm)

Cyclist Parameters (Constants.java)

Parameter Value Unit Description Academic Source
DEFAULT_CYCLIST_MASS_KG 80 kg Total system mass (cyclist + bike) Typical recreational cyclist
DEFAULT_CYCLIST_POWER_W 280 W Sustained power output (FTP) ~3.5 W/kg intermediate level
DEFAULT_MAX_BRAKE_G 0.6 g Maximum braking deceleration SAE 2020-01-0876 research
DEFAULT_DRAG_COEFFICIENT 0.7 - Aerodynamic drag coefficient "Aerodynamic drag in cycling" studies
DEFAULT_FRONTAL_AREA 0.5 Cyclist frontal area Professional cycling aerodynamics
DEFAULT_MAX_LEAN_ANGLE_DEG 35 ° Maximum cornering lean angle Brandt cycling physics
DEFAULT_MAX_SPEED_KMH 100 km/h Maximum speed capability Reasonable road cycling limit

Parameter Validation:

  • All values researched against academic cycling literature
  • Rolling resistance: Modern road tire typical range 0.003-0.005
  • Power output: Represents competitive recreational cyclist (Cat 3-4 racing level)
  • Braking: 0.6g provides safety margin below ~0.67g bicycle limit
  • Aerodynamics: CdA = 0.35 m² matches published cycling research
  • Air density: ISO standard used in cycling aerodynamics research (Martin et al.)
  • Lean angle: Practical limit on crowned roads per cycling dynamics analysis

PowerComputer: Core Physics Engine

The PowerComputer class implements the fundamental physics equations for virtual cyclist power calculations, handling acceleration, deceleration, and energy conservation.

Core Equations

1. Power Balance Equation

Formula: P_total = P_cyclist - P_resistances

Implementation: getNewPower() method (line 21)

double pSum = 0;
for (PowerProvider provider : providers.getPowerProviders()) {
    double w = provider.getPowerW(course, current);
    pSum = pSum + w;  // Sums all resistance powers (negative values)
}

Resistances included:

  • Rolling resistance: -cos(atan(grade)) * mass * g * speed * crr
  • Wheel bearings: -speed * (91 + 8.7 * speed) / 1000
  • Gravity: -sin(atan(grade)) * mass * g * speed
  • Aerodynamic drag: -aeroCoef * speed³ (no wind) or complex wind model

2. Kinetic Energy Power Equation

Formula: P = 0.5 * M_eq * (v₂² - v₁²) / Δt

Implementation: getTotPower() method (line 75)

return 0.5 * equivalentMass * (s2 * s2 - s1 * s1) / dt;

Physical meaning:

  • Represents power needed to change kinetic energy
  • Accounts for both translational and rotational energy
  • M_eq includes wheel rotational inertia effects

3. Equivalent Mass Calculation

Formula: M_eq = m + I_total / r²

Implementation: getEquivalentMass() method (line 83)

double mKg = course.getCyclist().getMKg();                    // Total mass (cyclist + bike)
double inertiaFront = course.getBike().getInertiaFront();    // Front wheel inertia
double inertiaRear = course.getBike().getInertiaRear();      // Rear wheel inertia  
double wheelRadius = course.getBike().getWheelRadius();      // Wheel radius
double inertia = inertiaFront + inertiaRear;                 // Combined wheel inertia
return mKg + (inertia / (wheelRadius * wheelRadius));       // Equivalent mass

Physical meaning:

  • Converts rotational inertia to equivalent linear mass
  • Based on kinetic energy equivalence: KE_total = ½mv² + ½Iω²
  • Since v = ωr, rotational part becomes: ½I(v/r)² = ½(I/r²)v²
  • Total: KE = ½(m + I/r²)v² → equivalent mass = m + I/r²

4. Speed Integration from Power

Formula: v_new = √(2ΔtP/(M_eq) + v_old²)

Implementation: getDx() method (line 32)

// From: P = 0.5 * M_eq * (v_new² - v_old²) / Δt
// Solving for v_new: v_new² = 2ΔtP/M_eq + v_old²
double newSpeed = Math.max(
    Math.sqrt(dt * pSum / (0.5 * equivalentMass) + currentSpeed * currentSpeed), 
    Constants.MINIMAL_SPEED);  // 2 km/h minimum
return (newSpeed + currentSpeed) * dt / 2;  // Distance via trapezoidal integration

5. Time Integration (Inverse Problem)

Implementation: getDt() method using binary search (line 40)

// Given: distance dx, power P, current speed
// Find: time dt such that getDx(P, M_eq, v_current, dt) = dx
// Uses binary search with tolerance dx/10,000,000

6. Cyclist Power Calculation

Implementation: computeCyclistPower() method (line 58)

double power = getNewPower(course, p1, false);           // Sum all resistances  
double tot_power = getTotPower(equivalentMass, s1, s2, dt); // Power from acceleration
double cyclistPower = tot_power - power;                 // Net cyclist power
cyclistPower = Math.max(0.0, cyclistPower);             // No negative power
cyclistPower = cyclistPower / course.getBike().getEfficiency(); // Account for drivetrain losses

Academic Validation

Sources:

  • Martin et al. (1998) "Validation of a mathematical model for road cycling power" - R² = 0.97 validation
  • Standard physics: Newton's 2nd law, kinetic energy theorem, rotational dynamics
  • Energy conservation: P_input = P_output + dE/dt

Key validation points: ✅ Equivalent mass formula matches rotational dynamics theory
✅ Power-velocity relationship validated by Martin et al. (3% accuracy)
✅ Energy conservation maintained throughout calculations
✅ Numerical methods ensure physical constraints (minimum speed, positive power)

Constants:

  • MINIMAL_SPEED = 2 km/h (0.556 m/s) - prevents division by zero
  • DT = 1.0 s - default time step for calculations

Cyclist

Virtual cyclist

Simulates a virtual cyclist on a give path.

Steps :

  • Fix elevation
  • Compute max speeds given abilities
  • Simulate a course given power
  • Compute one point per second
  • Simplify course

Fix elevation

Uses Mapterhorn terrain-RGB tiles (Terrarium-encoded WebP) to compute elevation by bilinear interpolation across 4 surrounding pixels at zoom 12 (~10 m horizontal resolution).

Tiles are fetched from https://tiles.mapterhorn.com/{z}/{x}/{y}.webp and cached on disk under <cache>/mapterhorn/{z}/{x}/{y}.webp. The base URL, zoom level, tile size, in-memory LRU capacity, and User-Agent are all configurable via MapterhornConfig.

Attribution

Elevation data shown by gpx2web is © Mapterhorn and its upstream sources. Mapterhorn is built from many national elevation datasets, each with its own license (mostly CC BY 4.0). When you display, redistribute, or publish maps or analyses produced with this tool, you must credit Mapterhorn and the underlying data providers as listed in the canonical attribution document.

If you self-host Mapterhorn tiles or point gpx2web at a private mirror via MapterhornConfig.tileUrlTemplate, you remain responsible for honouring the upstream licenses of every source dataset you use.

MaxSpeedComputer: Cornering and Braking Limits

The MaxSpeedComputer calculates safe maximum speeds considering cornering physics and braking constraints through a two-pass algorithm.

Two-Pass Algorithm

Pass 1 (Forward): Cornering Speed Limits

Implementation: firstPass() and computeMaxSpeedByIncline() methods

Algorithm:

  1. Three-point curvature analysis (lines 65-74):

    Vector circleCenter = getCircleCenter(pointBefore, currentPoint, pointAfter);
    double radius = Math.hypot(circleCenter.x(), circleCenter.y());
    radius = radius + 2; // Add 2m safety margin for trajectory
  2. Bicycle leaning dynamics (lines 79-81):

    // https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics#Leaning
    double vmax = Math.sqrt(Constants.G * radius * cyclist.getTanMaxAngle());
    p.setSpeedMax(Math.min(cyclist.getMaxSpeedMs(), vmax));

Physics:

  • Finds circle through 3 consecutive GPS points to determine curve radius
  • Applies bicycle leaning physics: v_max = √(g × r × tan(θ_max))
  • Where θ_max = 35° (maximum lean angle), g = 9.8 m/s²
  • Limits speed to prevent exceeding lean angle or absolute maximum

Pass 2 (Backward): Braking Constraints

Implementation: secondPass() and computeMaxSpeedByBraking() methods

Algorithm:

  1. Braking distance calculation (lines 89-96):

    double t = (vf - v0) / a;           // Time to brake from v0 to vf
    double dBrake = v0 * t + (a * t * t) / 2;  // Distance needed to brake
  2. Speed adjustment (lines 97-103):

    if (dBrake > dist) {
        // Not enough distance to brake safely
        double newMaxSpeedPrevious = Math.sqrt(vf * vf - 2 * a * dist);
        pm1.setSpeedMax(newMaxSpeedPrevious);
    }

Physics:

  • Uses kinematic equation: v² = u² + 2as
  • Braking deceleration: a = -0.6g = -5.88 m/s² (from cyclist parameters)
  • Works backward from destination, reducing speeds if braking distance insufficient

Mathematical Components

Circle Center Calculation

Implementation: getCircleCenter() method (lines 106-128)

// Solves system of equations for circle through three points
double G = 2 * (A * (cy - by) - B * (cx - bx));
if (Math.abs(G) < 0.001) return null; // Collinear points
double px = (D * E - B * F) / G;
double py = (A * F - C * E) / G;

Handles edge cases:

  • Collinear points (straight line): Uses maximum cyclist speed
  • First point: Uses absolute maximum speed
  • Last point: Sets target speed of 2 m/s (stop condition)

Coordinate Transformation

Implementation: transform() method (lines 130-136)

double x = (lon / (2 * Math.PI)) * Constants.CIRC * Math.cos(pRef.getLat());
double y = (lat / (2 * Math.PI)) * Constants.CIRC;

Purpose: Converts GPS coordinates (lat/lon) to local Cartesian coordinates (meters) for geometric calculations.

Academic Validation

Sources:

Key physics equations:

  • Cornering speed: v = √(g × r × tan(θ)) - maximum speed without exceeding lean angle
  • Braking distance: s = (v² - u²) / 2a - minimum distance needed to decelerate
  • Circle through 3 points: Analytical geometry for curvature determination

Safety margins:

  • +2m radius safety margin for trajectory imperfection
  • Conservative braking coefficient (0.6g vs theoretical 0.67g limit)
  • Minimum final speed (2 m/s) prevents complete stops mid-course

VirtualizeService: Complete Course Simulation

The VirtualizeService orchestrates the complete virtual cyclist simulation, integrating power calculations, speed constraints, and temporal progression to generate a realistic ride profile.

Core Algorithm: Time-Stepping Integration

Implementation: virtualizeTrack() method combining all physics components

Initialization Phase (lines 33-53)

OptimalSpeeds optimalSpeeds = new OptimalSpeeds(optimalSpeedService, course);
double equivalentMass = powerComputer.getEquivalentMass(course);

Point current = input.get(0).copy();
current.setSpeed(Constants.MINIMAL_SPEED);  // Start at 2 km/h

Setup:

  • Computes optimal speed profile using MaxSpeedComputer (cornering + braking limits)
  • Calculates equivalent mass: M_eq = m + I_total/r² (includes wheel inertia)
  • Initializes simulation at first GPS point with minimal speed

Main Integration Loop (lines 55-93)

Time-stepping algorithm with adaptive step size:

  1. Power calculation (line 60):

    double pSum = powerComputer.getNewPower(course, current, true);
    • Sums all resistance forces: rolling, bearings, gravity, aerodynamic
    • Includes cyclist power in balance
  2. Distance prediction (line 61):

    double dx = powerComputer.getDx(pSum, equivalentMass, currentSpeed, DT);
    • Uses kinetic energy equation: dx = (v_new + v_old) * dt / 2
    • Where v_new = √(2ΔtP/M_eq + v_old²)
  3. Adaptive step handling (lines 66-79):

    if (index != newIndex) {
        // Step crosses GPS waypoint - adjust to exact waypoint
        dxToNext = input.get(newIndex).getDist() - current.getDist();
        dtToNext = powerComputer.getDt(pSum, equivalentMass, currentSpeed, dxToNext);
        current = input.get(newIndex).copy();
    } else {
        // Regular time step
        dxToNext = dx;
        dtToNext = DT;
        current = Point.interpolate(input.get(index), input.get(index + 1), coef);
    }
  4. Speed integration with constraints (lines 81-85):

    double speedNew = 2 * (dxToNext / dtToNext) - currentSpeed;  // Trapezoidal rule
    if (speedNew > current.getSpeedMax()) {
        speedNew = current.getSpeedMax();  // Apply speed limits
    }
    dtToNext = 2 * dxToNext / (currentSpeed + speedNew);  // Adjust time
  5. Temporal progression (lines 89-92):

    Duration dtToNextDuration = getDuration(dtToNext);
    now = now.plus(dtToNextDuration);
    current.setInstant(start, now);

Post-Processing Phase (lines 95-99)

for (int i = 0; i < newPoints.size() - 1; i++) {
    double cyclistPower = powerComputer.computeCyclistPower(course, equivalentMass, 
                                                           newPoints.get(i), newPoints.get(i + 1));
    newPoints.get(i).setPower(cyclistPower);
}

Cyclist power calculation:

  • P_cyclist = P_total - P_resistances + dE/dt
  • Accounts for drivetrain efficiency losses
  • Ensures non-negative power output

Numerical Methods Validation

Primary integration method: Modified trapezoidal rule

  • Speed integration: v_new = 2(dx/dt) - v_old (trapezoidal velocity)
  • Distance integration: dx = (v_new + v_old) * dt / 2 (trapezoidal area)
  • Time adjustment: dt = 2dx / (v_new + v_old) (ensures consistency)

Academic validation:

  • Trapezoidal rule: Second-order accuracy, A-stable numerical method
  • Energy conservation: Power balance maintained throughout integration
  • Physical constraints: Speed limits from MaxSpeedComputer enforced
  • Adaptive stepping: Handles variable distance steps along GPS route

Advanced Features

Binary Search Distance Lookup (lines 117-135)

private int getIndex(double[] dists, int distsLength, double dist) {
    // O(log n) search for GPS point index at given distance
    while (left <= right) {
        int mid = left + (right - left) / 2;
        // Binary search implementation...
    }
}

GPS Point Interpolation

  • Linear interpolation between GPS waypoints when simulation step doesn't align
  • Preserves elevation, grade, and geographic accuracy
  • Maintains smooth trajectory between known points

Boundary Conditions

  • Start: Minimal speed (2 km/h) to prevent division by zero
  • End: Continues until reaching final GPS distance
  • Constraints: Speed never exceeds MaxSpeedComputer limits

Academic Foundation

Numerical integration theory:

  • Trapezoidal rule provides 2nd-order accuracy vs 1st-order Euler method
  • A-stable method prevents numerical oscillations
  • Energy-conserving approach maintains physical consistency

Cycling simulation research:

  • Follows established power-speed modeling (Martin et al. 1998)
  • Multi-body dynamics approach (Runge-Kutta methods used in academic bicycle simulators)
  • Real-time capable algorithm suitable for interactive simulation

Output: Complete time-series cycling simulation with:

  • Speed profile respecting cornering/braking limits
  • Power output accounting for all resistances
  • Temporal progression with realistic accelerations
  • Geographic fidelity to original GPS route

Heart Rate Simulation: Exercise Physiology Modeling

The HRSimulator generates realistic heart rate data using machine learning trained on real cycling data, incorporating temporal dynamics and physiological constraints.

Core Architecture

Implementation: Linear regression model using SMILE machine learning library

Training Phase (lines 43-56)

public void train(final List<GPXPath> samples) {
    List<DataPoint> dataPoints = new ArrayList<>();
    for (GPXPath sample : samples) {
        getDataPoints(dataPoints, sample);  // Extract features every 5 seconds
    }
    this.linearModel = OLS.fit(Formula.lhs("hr"), DataFrame.of(dataPoints));
    // Model serialized to resources/hrmodel
}

Training features:

  • Samples every 5 seconds from real cycling data
  • Builds comprehensive feature set with temporal dependencies
  • Uses Ordinary Least Squares (OLS) linear regression

Feature Engineering: Temporal Heart Rate and Power

DataPoint features (DataPoint.java:11-22):

// Heart rate features - temporal lookback
new Field("hr", PropertyKeys.heartRate, 0.0),      // Current HR
new Field("hr5", PropertyKeys.heartRate, 5.0),     // HR 5 seconds ago  
new Field("hr10", PropertyKeys.heartRate, 10.0),   // HR 10 seconds ago
new Field("hr30", PropertyKeys.heartRate, 30.0),   // HR 30 seconds ago

// Power features - moving averages  
new Field("p5", PropertyKeys.power, 0.0, 5.0),     // 5-second power average
new Field("p10", PropertyKeys.power, 0.0, 10.0),   // 10-second power average
new Field("p60", PropertyKeys.power, 0.0, 60.0),   // 60-second power average

Feature extraction (Field.java:23-25):

double getValue(GPXPath gpxPath, double t) {
    double average = gpxPath.getAverage(t - start, t - end, propertyKey);
    return hr ? clamp(average) : average;  // HR clamped to 60-220 bpm
}

Simulation Phase (lines 58-70)

public void simulateHeartRate(final GPXPath gpxPath) {
    for (int i = 0; i < points.size() - 1; i++) {
        double t = points.get(i).getElapsedSeconds();
        DataPoint dataPoint = getDataPoint(gpxPath, t);      // Extract features
        double hr = linearModel.predict(dataPoint);         // ML prediction
        points.get(i).setHeartRate(hr);
    }
    smoothService.smoothHr(gpxPath);  // Post-processing smoothing
}

Exercise Physiology Validation

Academic foundation:

  • Linear power-HR relationship: Validated in exercise physiology literature
  • Temporal dependencies: HR exhibits lag response to power changes (15-30 seconds)
  • Moving averages: 2-60 second windows common in cycling analysis
  • Physiological constraints: 60-220 bpm range appropriate for exercise

Key research findings:

  1. Power-HR linearity: Linear at low-submaximal intensities, validated by multiple studies
  2. Temporal dynamics: HR remains elevated ~15 minutes post-effort, captured by lookback features
  3. Individual variation: Linear models capture person-specific fitness adaptations
  4. Smoothing techniques: Post-processing smoothing standard in cycling performance analysis

Model Architecture Analysis

Strengths of Current Approach:

  • Temporal modeling: Includes HR history (5-30s lookback) capturing physiological lag
  • Multi-scale power: Uses 5s, 10s, 60s power averages reflecting different energy systems
  • Physiological bounds: HR clamped to realistic exercise range (60-220 bpm)
  • Real data training: Model learned from actual cycling datasets
  • Computational efficiency: Linear regression enables real-time prediction

Limitations and Future Improvements:

  • Linear assumption: Real HR-power relationship becomes non-linear at high intensities
  • Individual differences: Single model doesn't capture personal fitness variations
  • Environmental factors: No consideration of temperature, altitude, fatigue state
  • Limited features: Could benefit from grade, speed, cadence, duration

Random Forest Improvement Potential

Why Random Forest would improve results:

  1. Non-linear relationships: Captures curvilinear HR response at high intensities
  2. Feature interactions: Models complex interactions between power, time, and environmental factors
  3. Individual patterns: Better handles person-specific physiological responses
  4. Robustness: Less sensitive to outliers and data quality issues
  5. Feature importance: Provides insight into which factors most influence HR

Expected improvements:

  • Better prediction accuracy, especially at threshold and VO2max intensities
  • Capture of individual physiological profiles and fitness adaptations
  • Incorporation of environmental and contextual factors
  • More realistic response to interval training and variable power scenarios

Academic References

Exercise physiology sources:

  • Linear power-HR relationships validated in cycling performance studies
  • Temporal dynamics: ~15-second lag in HR response to power changes
  • Moving averages: 2-second to 60-second windows standard in performance analysis
  • Heart rate variability research supports multi-temporal feature approaches

Current implementation: Simple but physiologically grounded approach suitable for basic simulation. Random forest upgrade would provide significantly more realistic and personalized heart rate modeling.

GPXEnhancer: Complete Virtualization Pipeline

The GPXEnhancer orchestrates the complete virtual cyclist simulation pipeline, transforming raw GPS tracks into realistic cycling experiences with proper data preprocessing and post-processing.

Pipeline Architecture

Main method: virtualize(Course course, boolean filter) (lines 67-78)

Step-by-Step Pipeline Analysis:

1. Distance-based Resampling (line 69)

gpxPerDistance.computeOnePointPerDistance(gpxPath, 10.0);
  • Purpose: Standardize GPS point spacing to 10-meter intervals
  • Why essential: Raw GPS tracks have irregular point spacing (1-20m gaps)
  • Benefits: Uniform distance intervals improve numerical stability in physics calculations
  • Academic validation: Standard preprocessing in GPS track analysis and cycling performance studies

2. Elevation Correction (line 70)

gpxElevationFixer.fixElevation(gpxPath);
  • Purpose: Replace GPS elevation with accurate terrain elevation
  • Why critical: GPS elevation accuracy ±10-15m, Mapterhorn provides sub-metre accuracy in many regions (depending on the underlying national dataset)
  • Impact: Accurate grade calculation essential for gravity and rolling resistance power
  • Data source: Mapterhorn terrain-RGB tiles — see the Attribution section for credit requirements.

3. Speed Constraint Calculation (line 71)

maxSpeedComputer.computeMaxSpeeds(course);
  • Purpose: Compute safe maximum speeds for cornering and braking
  • Algorithm: Two-pass (forward cornering limits, backward braking constraints)
  • Physics: Bicycle leaning dynamics + kinematic braking equations
  • Output: Speed limits enforced in subsequent virtual simulation

4. Physics-based Simulation (line 72)

virtualizeService.virtualizeTrack(course);
  • Purpose: Core virtual cyclist simulation with power-speed integration
  • Algorithm: Time-stepping numerical integration using modified trapezoidal rule
  • Physics: Energy conservation, equivalent mass, all resistance forces
  • Output: Realistic speed/power profiles respecting physical constraints

5. Heart Rate Modeling (line 73)

hrSimulator.simulateHeartRate(gpxPath);
  • Purpose: Generate physiologically realistic heart rate data
  • Algorithm: Linear regression trained on real cycling data
  • Features: Temporal HR/power dependencies with 5-60 second moving averages
  • Output: Heart rate time series with post-processing smoothing

6. Temporal Resampling (line 74)

gpxPerSecond.computeOnePointPerSecond(gpxPath);
  • Purpose: Standardize output to 1-second intervals for compatibility
  • Why needed: Virtual simulation produces variable time steps
  • Benefits: Consistent format for export, visualization, and analysis tools
  • Method: Linear interpolation between simulation points

7. Optional Track Simplification (lines 75-77)

if (filter) {
    GPXFilter.filterPointsDouglasPeucker(gpxPath);
}
  • Purpose: Reduce track complexity while preserving essential features
  • Algorithm: Douglas-Peucker line simplification algorithm
  • Benefits: Smaller file sizes, improved rendering performance
  • Academic validation: Standard algorithm for cartographic generalization

Pipeline Validation

Academic foundations:

  • Distance resampling: Standard in GPS track processing and cycling analysis
  • Mapterhorn elevation: Sub-metre accuracy in covered regions (see Attribution)
  • Physics simulation: Based on validated cycling power models (Martin et al.)
  • Douglas-Peucker: Established algorithm for curve simplification (O(n²) complexity)
  • Pipeline ordering: Logical sequence from raw data → physics → output formatting

Step sequencing rationale:

  1. Distance resampling first: Establishes consistent spatial intervals before physics
  2. Elevation fixing early: Required for accurate grade calculation throughout pipeline
  3. Max speeds before simulation: Provides constraints for physics engine
  4. Heart rate after simulation: Depends on power output from virtualization
  5. Temporal resampling last: Formats physics output for standard compatibility
  6. Filtering optional: Final step to optimize output size without affecting physics

Course Construction (lines 49-64)

Default configuration setup:

Cyclist cyclist = Cyclist.getDefault();        // Standard cyclist parameters
Bike bike = Bike.getDefault();                 // Standard bike parameters
Course course = new Course(
    gpxPath, instant, cyclist, bike,
    new PowerProviderConstant(),               // Constant power capability
    new WindProviderNone(),                    // No wind conditions  
    new AeroProviderConstant()                 // Constant aerodynamics
);

Multi-track support (lines 39-43):

  • Processes multiple GPX tracks with sequential day offsets
  • Each track gets independent virtualization with same pipeline
  • Maintains temporal consistency across multi-day routes

Performance and Scalability

Computational complexity:

  • Distance/temporal resampling: O(n log n) for point lookups
  • Elevation fixing: O(n) Mapterhorn tile queries with LRU + on-disk caching
  • Max speed computation: O(n) with circle geometry calculations
  • Physics simulation: O(n) time-stepping integration
  • Douglas-Peucker filtering: O(n²) worst case, typically O(n log n)

Memory efficiency:

  • In-place processing where possible
  • Streaming approach for large GPS tracks
  • Mapterhorn tile caching (in-memory LRU + on-disk) reduces network requests

Quality Assurance

Data validation at each step:

  • Point spacing validation after distance resampling
  • Elevation sanity checks against GPS bounds
  • Speed constraint verification against theoretical limits
  • Power output reasonableness checks
  • Heart rate physiological bounds enforcement

Error handling:

  • Mapterhorn fetch errors propagate as UncheckedIOException (no silent fallback to zero)
  • Default parameters if customization unavailable

The GPXEnhancer pipeline represents a comprehensive approach to virtual cyclist simulation, combining established GPS processing techniques with validated cycling physics models to produce realistic and accurate virtual cycling experiences.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Packages

 
 
 

Contributors