Professional cycling pacing optimizer using GPX data
Features โข Installation โข Quick Start โข Documentation โข Contributing
OptiRide is a powerful Python toolkit for optimizing cycling performance through intelligent pacing strategies. By analyzing GPX route data and combining it with rider characteristics, bike specifications, and weather conditions, OptiRide computes optimal power targets, estimates ride times, and generates comprehensive nutrition plans.
- Physics engine: Based on Martin et al. (1998) validated cycling power model
- Power management: Implements Critical Power (CP) and W' (W-prime) constraints
- Weather integration: Real-time weather data from Open-Meteo API
- Wind modeling: Accurate wind impact calculation based on bearing and relative airspeed
- ๐ฏ Point-by-point power targets optimized for terrain and conditions
- โฑ๏ธ Accurate time predictions using physics-based modeling
- ๐ Smart refueling recommendations with personalized FTP zones and fatigue tracking
- ๐ช W' balance modeling for real-time anaerobic capacity monitoring
- ๐บ๏ธ Interactive HTML maps with power zones, refueling points, and elevation profile
- ๐ค๏ธ Automatic weather integration with wind impact analysis
- ๐ Rich visualizations of elevation, power, and speed profiles
- ๐ Start time optimization to find the best weather window
- ๐ Multiple export formats (CSV, GPX with power extensions, JSON, HTML maps)
- ๐ฌ Type-safe & well-tested with comprehensive docstrings
pip install optiridepipx install optiridegit clone https://github.com/romainberthet/optiride.git
cd optiride
pip install -e ".[dev]"# Install with all development dependencies
pip install -e ".[dev,docs,jupyter]"
# Install pre-commit hooks
pre-commit installOptiRide now includes a comprehensive bike database with automatic CdA adjustment based on your height and weight!
Minimum required:
- Your GPX trace
- Your weight and FTP
Optional but recommended:
- Your height (for precise CdA estimation)
- Bike type (defaults to
aero_road) - Target power (auto-calculated from FTP if not specified)
# Basic usage - Everything calculated automatically from FTP!
optiride compute \
--gpx examples/sample.gpx \
--mass 72 \
--height 1.80 \
--ftp 260
# Or specify target power explicitly:
optiride compute \
--gpx examples/sample.gpx \
--mass 72 --height 1.80 --ftp 260 \
--power-flat 220
# Or use intensity factor (cleaner):
optiride compute \
--gpx examples/sample.gpx \
--mass 72 --height 1.80 --ftp 260 \
--intensity-factor 0.85optiride compute \
--gpx examples/sample.gpx \
--mass 72 --height 1.80 --ftp 260 \
--bike-type aero_road \
--position drops \
--wheels deep_section \
--intensity-factor 0.85Available bike types:
road_race- Lightweight racing bike (7.5kg, CdA 0.08)aero_road- Aerodynamic road bike (8.2kg, CdA 0.07) โก Defaulttime_trial- TT/Tri bike (9.0kg, CdA 0.06)gravel- Gravel/all-road bike (9.5kg, CdA 0.10)mountain- XC mountain bike (11.0kg, CdA 0.12)road_endurance- Comfort road bike (8.5kg, CdA 0.09)
Available positions:
upright- Hands on hoods, relaxed (CdA +0.35)drops- Hands in drops (CdA +0.28) โก Defaultaero_hoods- Elbows tucked (CdA +0.30)time_trial- On aerobars (CdA +0.22)super_tuck- Extreme aero/descending (CdA +0.18)
optiride compute \
--gpx examples/sample.gpx \
--mass 72 --ftp 260 \
--auto-weather --hour 9Generate a beautiful interactive HTML map with power zones visualization:
optiride compute \
--gpx examples/sample.gpx \
--mass 72 --height 1.80 --ftp 260 \
--export-mapFeatures of the interactive map:
- ๐จ Color-coded route by personalized FTP-based power zones
- ๐ Smart refueling markers with nutrition recommendations
- ๐ Elevation profile chart
- ๐ Ride statistics panel
- ๐ช Fatigue tracking using W' balance model
- ๐ Click on segments for detailed info
- ๐บ๏ธ Multiple map layers (terrain, satellite, etc.)
- ๐ฑ Responsive design works on mobile
Refueling features:
- Personalized power zones calculated from your FTP (Coggan 7-zone model)
- W' balance tracking for real-time fatigue estimation (Skiba model)
- Adaptive nutrition type (gels, bars, drinks) based on:
- Current fatigue level
- Ride intensity
- Time elapsed
- Detailed recommendations at each refueling point:
- Carbohydrates, fluids, sodium intake
- Fatigue index and W' balance
- Context-aware advice
Installation required:
pip install "optiride[maps]"
# or
pip install foliumFind the best time to start based on weather conditions:
optiride optimize-start \
--gpx examples/sample.gpx \
--mass 72 --ftp 260 \
--start-hour 6 --end-hour 20 \
--export-gpxFor experienced users who want precise control:
optiride compute \
--gpx examples/sample.gpx \
--mass 72 --ftp 260 \
--bike-type aero_road \
--cda 0.285 \
--crr 0.0032 \
--bike-mass 7.8 \
--power-flat 220outputs/summary.json: Comprehensive ride summary (time, kCal, nutrition plan)outputs/targets.csv: Detailed point-by-point targets (distance, slope, power, speed)outputs/plots/*.png: Elevation profile, power curve, speed profileoutputs/power_targets.gpx: GPX file with power extensions (optional,--export-gpx)outputs/interactive_map.html: Interactive map with power zones (optional,--export-map) ๐
import optiride as opr
from optiride.bike_library import get_bike_config
# Get complete bike configuration from library
# CdA is automatically adjusted for rider size!
bike_config = get_bike_config(
bike_type="aero_road",
position="drops",
wheels="deep_section",
rider_height_m=1.80, # Your height
rider_mass_kg=72.0 # Your weight
)
# Returns: {'mass_kg': 8.1, 'cda': 0.346, 'crr': 0.0032, 'drivetrain_efficiency': 0.977}
# CdA is scaled based on your anthropometry!
# Create rider with library values
rider = opr.RiderBike(
mass_rider_kg=72.0,
mass_bike_kg=bike_config["mass_kg"],
cda=bike_config["cda"], # Already adjusted for your size
crr=bike_config["crr"],
drivetrain_eff=bike_config["drivetrain_efficiency"],
ftp=260.0,
w_prime_j=20000.0
)
# Set environment
env = opr.Environment(
air_density=1.225,
wind_u_ms=0.0, # East wind (m/s)
wind_v_ms=0.0 # North wind (m/s)
)
# Calculate required power for given speed
power = opr.power_required(
v_ms=10.0, # 36 km/h
slope_tan=0.05, # 5% grade
bearing_deg=0.0, # Heading north
rb=rider,
env=env
)
print(f"Required power: {power:.1f} W")
# Example: Compare CdA for different rider sizes
small_rider = get_bike_config("aero_road", "drops", rider_height_m=1.65, rider_mass_kg=60.0)
large_rider = get_bike_config("aero_road", "drops", rider_height_m=1.95, rider_mass_kg=90.0)
print(f"Small rider CdA: {small_rider['cda']:.3f}") # ~0.31
print(f"Large rider CdA: {large_rider['cda']:.3f}") # ~0.37import optiride as opr
# Define rider and bike manually
rider = opr.RiderBike(
mass_rider_kg=72.0,
mass_bike_kg=8.0,
cda=0.30,
crr=0.0035,
ftp=260.0,
w_prime_j=20000.0
)
# Set environment
env = opr.Environment(
air_density=1.225,
wind_u_ms=0.0,
wind_v_ms=0.0
)
# Calculate required power for given speed
power = opr.power_required(
v_ms=10.0, # 36 km/h
slope_tan=0.05, # 5% grade
bearing_deg=0.0,
rb=rider,
env=env
)
print(f"Required power: {power:.1f} W")| Parameter | Description | Example |
|---|---|---|
--gpx |
Path to GPX file | route.gpx |
--mass |
Rider weight (kg) | 72 |
--ftp |
Functional Threshold Power (W) | 260 |
| Parameter | Description | Effect |
|---|---|---|
--height |
Rider height in meters | Automatically adjusts CdA for your size |
How it works: If you provide your height, OptiRide uses the DuBois formula to estimate your frontal area and scale the CdA accordingly. A taller/larger rider will have a proportionally higher CdA, while a smaller rider will have a lower CdA. This provides much more accurate predictions than using generic reference values!
| Parameter | Description | Default |
|---|---|---|
--bike-type |
Bike category (see list above) | aero_road |
--position |
Riding position (see list above) | drops |
--wheels |
Wheel type | mid_depth |
Tip: The bike library automatically configures mass, CdA, Crr, and efficiency based on your bike type!
| Parameter | Description | Typical Range |
|---|---|---|
--bike-mass |
Override bike weight (kg) | 6-10 |
--cda |
Override drag area (mยฒ) | 0.20-0.40 |
--crr |
Override rolling resistance | 0.002-0.005 |
--eff |
Override drivetrain efficiency | 0.95-0.98 |
--wprime |
Anaerobic capacity W' (J) | 15000-25000 |
--cp |
Critical Power (W) | 250-350 |
--age |
Rider age for HR calculations | 20-60 |
| Parameter | Description | Default |
|---|---|---|
--power-flat |
Target power on flat (W). If omitted, auto-calculated from FTP | Auto: 75% FTP |
--intensity-factor |
Intensity as fraction of FTP (0.0-1.0). Alternative to --power-flat | None |
--up-mult |
Power multiplier for climbs | 1.10 |
--down-mult |
Power multiplier for descents | 0.75 |
--max-delta |
Max power change between points (W) | 30.0 |
--step-m |
Resampling distance (m) | 20.0 |
Auto-calculation rules (when --power-flat not specified):
- < 1h ride: 92% FTP (short effort)
- 1-2h ride: 87% FTP (medium effort)
- 2-4h ride: 80% FTP (long effort)
-
4h ride: 70% FTP (very long effort)
- Unknown duration: 75% FTP (conservative default)
OptiRide can automatically fetch weather data from Open-Meteo (no API key required):
optiride compute --gpx route.gpx --auto-weather --hour 10This retrieves:
- Temperature
- Humidity
- Atmospheric pressure
- Wind speed and direction
Wind is projected onto the route bearing to calculate accurate aerodynamic drag.
# Run all tests
pytest
# Run with coverage
pytest --cov=optiride --cov-report=html
# Run only unit tests
pytest -m unitThis project uses modern Python tooling:
- Ruff: Lightning-fast linting and formatting
- MyPy: Static type checking
- Pytest: Comprehensive testing framework
- Pre-commit: Automated code quality checks
# Format code
ruff format .
# Lint code
ruff check . --fix
# Type checking
mypy optiride
# Run all pre-commit hooks
pre-commit run --all-filesContributions are welcome! Please see CONTRIBUTING.md for guidelines.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes with tests
- Run quality checks (
pre-commit run --all-files) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
For maintainers creating releases, see RELEASE.md for detailed instructions.
This project is licensed under the MIT License - see the LICENSE file for details.
Physics & Power Modeling:
- Martin et al. (1998) - "Validation of a Mathematical Model for Road Cycling Power" - Foundation for the cycling power equations used throughout OptiRide
Fatigue & Performance:
- Skiba et al. (2012) - "Modeling the Expenditure and Reconstitution of Work Capacity Above Critical Power" - W' balance differential model implementation
- Coggan (2003) - Power training zones model (7-zone system based on FTP)
Nutrition & Fueling:
- Jeukendrup (2014) - "A Step Towards Personalized Sports Nutrition: Carbohydrate Intake During Exercise" - Scientific guidelines for carbohydrate intake rates
- Thomas et al. (2016) - "American College of Sports Medicine Joint Position Statement: Nutrition and Athletic Performance" - Hydration and sodium recommendations
Aerodynamics & Body Composition:
- DuBois & DuBois (1916) - "A Formula to Estimate the Approximate Surface Area if Height and Weight Be Known" - Anthropometric scaling for CdA estimation
- Open-Meteo - Free weather API for real-time meteorological data
- Folium - Interactive mapping library built on Leaflet.js
- OpenTopoMap - Topographic map tiles for terrain visualization
Romain BERTHET - berthet.romain3@gmail.com
Project Link: https://github.com/romainberthet/optiride
โญ Star this repo if you find it useful! โญ