diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 103 |
1 files changed, 54 insertions, 49 deletions
@@ -4,52 +4,29 @@ A Go toolkit for analyzing cycling rides from GPX exports: parse rides, detect c ## Features -- **GPX parsing** — reads elevation-tracked rides from Garmin/Strava GPX exports into a `Ride` model -- **Climb detection** — automatically finds climbs within a ride, each scored and categorized (Cat 4 … HC), requiring at least a 3.5% average slope (gentler stretches are re-segmented into their qualifying parts) -- **Difficulty score (Cotacol)** — scores any ride or climb by splitting it into 100 m segments and summing `0.1 km × slope²` per segment. This fixed step is a deliberate compromise: smaller steps increase sensitivity to elevation noise, while unconstrained variable steps maximize noise rather than meaningful difficulty. -- **Similar-climb matching** — finds the same climb across rides by matching start/end coordinates, so times can be compared -- **Mountain pass extraction** — extracts French mountain passes from an OpenStreetMap PBF export (nodes tagged `mountain_pass=yes` / `natural=mountain_pass`) into a SQLite database -- **Pass crossing detection** — flags which passes a ride crosses and names each climb after the pass it tops (e.g. "Col de Castellaras") -- **Interactive ride profiles** — explores elevation, climbs, and mountain-pass crossings in the web ride detail view -- **Official climb cards** — separates matched climbs from pending matches and expands each matched climb with its own elevation profile -- **Repeated official climbs** — matches the same official climb once per lap on looping rides, respecting direction -- **Web ride library** — starts a local web server by default, imports Strava rides over a date range, stores metadata in SQLite, and keeps their GPX files on disk -- **Materialized ride values** — computes Cotacol on import, stores its algorithm version in SQLite, and refreshes all computed values with `-backfill` -- **Strava stream metrics** — preserves heart rate, cadence, and power in Garmin-compatible GPX files and in-memory ride columns -- **Metadata-only ride imports** — stores Strava activity statistics for rides without GPS tracks, without Cotacol or route data -- **Ride table sorting** — sorts every ride-library column through clickable server-side headers and query parameters -- **Ride library filters** — filters rides by distance, moving duration, start date, Cotacol score, and route availability through shareable query URLs -- **Computed moving time** — computes moving time from the GPX track with a speed-threshold algorithm, persists it as the canonical value (keeping Strava's as `external_moving_time_s`), and benchmarks against that reference via `-backtest` -- **CLI progress bars** — shows a live progress bar for the long-running CLI processes (`-fetch-osm`, `-extract-osm`, `-backfill`, `-backtest`) -- **Verified OSM download** — verifies the France PBF against geofabrik's published MD5 after downloading (and on already-complete files), keeping a corrupt file for inspection instead of silently trusting its size -- **Ride detail maps** — opens a stored ride with its recorded route on an interactive OpenStreetMap map -- **Strava import progress** — streams live import progress with an aggregate progress bar and prevents overlapping syncs -- **Analytics second chart** — plots normalized difficulty (Cotacol per 100 km) against moving duration alongside speed vs. Cotacol -- **Ride categorisation** — mandatory bike type (road/mtb) and ride type (training/indoor/race/transport) per ride, bulk-categorised via a data-quality todo, filtered on library and analytics, and benchmarked excluding indoor/transport -- **Ride type multiselect filter** — pick multiple ride types at once via a checkbox group on the library and analytics, defaulting to all except transport +**Data import** - Sync rides from Strava, geo, time, heart rate, power, cadence. +Rate limit aware and visible progress. -## Getting started +**Climb detection** - Auto detect climbs with difficulty. Save official climbs for rematching. +Climb profile by 100m step. -```bash -mise run build -``` +**Mountain pass** - Match climbs with mountain pass from OSM, and display on map. -## Usage +**Rides** - Display rides forever, with filtering and sorting. Categorise all rides. -```bash -# Start the web server on http://localhost:8080 -mise run dev-watch -``` +**Analytics** - Plot speed vs difficulty, difficulty vs duration. Color points by type or season. -## Options / Configuration +## Getting started -| Flag | Description | Default | -| --- | --- | --- | -| `-fetch-osm` | Download the France OSM PBF (resumable) | `false` | -| `-extract-osm` | Extract mountain passes from a France OSM PBF | `""` | -| `-backfill` | Recompute and persist all ride computed values | `false` | -| `-backtest` | Benchmark computed vs stored ride values | `false` | -| `-pprof` | Expose pprof endpoints at `/debug/pprof` | `false` | +```bash +mise run build +mise run dev-watch # Start the web server on http://localhost:8080 +go run . -fetch-osm # Download the France OSM PBF (resumable) +go run . -extract-osm france-latest.osm.pbf # Extract mountain passes from a France OSM PBF +go run . -backfill # Recompute and persist all ride computed values +go run . -backtest # Benchmark computed vs stored ride values +go run . -pprof # Expose pprof endpoints at `/debug/pprof` +``` Configuration is stored in `config.yaml`. Start from `config.example.yaml` and set the Strava credentials before launching the server. @@ -68,34 +45,62 @@ Standard apps are rate-limited to 100 calls per 15 minutes and 1,000 per day. ## Architecture - Go 1.27; SQLite via `mattn/go-sqlite3` -- `ride` — GPX parsing, climb detection, difficulty scores (KOM + Cotacol), and SQLite persistence for imported ride metadata and versioned computed values -- `mountain_pass` — OSM PBF extraction (`mountain_pass=yes` / `natural=mountain_pass` nodes), pass storage and querying, and crossings/climb matching +- `geo` — coordinate distance, nearest-point, and shared `Interval` primitives; dependency-free +- `climb` — climb detection and scoring. A `Route` is the geometry-only trace (distances, elevations, coords), and `Detect` returns `[]DetectedClimb` (a climb plus its ride-relative start index). Provides slope and Cotacol difficulty scoring. Deliberately decoupled from `ride` +- `ride` — GPX parsing and the ride value (embeds `climb.Route` for geometry, adding timestamps and telemetry), moving-time metrics, and SQLite persistence for imported ride metadata and versioned computed values +- `official_climb` — matches official climbs against a route by endpoint coordinates +- `mountain_pass` — OSM PBF extraction (`mountain_pass=yes` / `natural=mountain_pass` nodes), pass storage and querying, and crossings/climb matching on `climb.Climb` +- `rideanalysis` — orchestrates a ride into analyzed climbs and mountain-pass crossings - `strava` — OAuth2 client returning activity metadata and GPX data - `config` — typed YAML configuration and atomic persistence - `web` — HTTP server, OAuth callback, sync orchestration, and templ pages - `cli` — command handlers for data import, backfill, and benchmarking +- test builders `internal/routetest`, `internal/ridetest`, `internal/dbtest` ```mermaid flowchart TB %% Arrow X --> Y means: X depends on Y main["main (bootstrap: main.go)"] - - ride["ride (GPX parsing, climbs, Cotacol, persistence)"] - mpass["mountain_pass (OSM PBF, crossings)"] + cli["cli (import, backfill, benchmark)"] + web["web (HTTP + templ, sync)"] + rideanalysis["rideanalysis (climbs + crossings)"] + ride["ride (GPX parsing, Ride value, metrics, persistence)"] + climb["climb (Route, detection, Cotacol)"] + official_climb["official_climb (official climb matching)"] + mpass["mountain_pass (OSM PBF, crossings, matching)"] + geo["geo (distance, nearest, Interval)"] strava["strava (OAuth, List, Get)"] config["config (YAML)"] - web["web (HTTP + templ)"] - cli["cli (import, backfill, benchmark)"] main --> cli + main --> config + cli --> web cli --> ride cli --> mpass - cli --> web - mpass --> ride + cli --> config + + web --> rideanalysis web --> strava web --> ride + web --> mpass + web --> official_climb web --> config + web --> climb + + rideanalysis --> ride + rideanalysis --> climb + rideanalysis --> mpass + rideanalysis --> official_climb + + ride --> climb + + mpass --> climb + mpass --> geo + + official_climb --> geo + + config --> official_climb ``` ## Research |
