A Python toolkit for analyzing and visualizing your Strava activities without paying for Strava Premium. Sync your activities, generate cool visualizations, and track your performance metrics over time. This repository is conceived as a starting point for building more advanced Strava data analysis tools. I will keep adding features and visualizations over time.
β οΈ Disclaimer: This project stores Strava data locally on your machine. It is the responsibility of each user to comply with Strava's API Agreement and their terms regarding data storage and usage. Please review Strava's policies before using this tool.
- Web Dashboard: Full-featured React frontend with FastAPI backend:
- π Calendar: Monthly calendar with activity display, training session planning, and weekly reports
- π Activities: Browsable activity list with detail views, stream charts, and map visualization
- π World Footprint: Interactive Leaflet map with all your routes, filterable by sport and year
- β‘ Year in Sport: Yearly stats with monthly charts, records, and sport breakdowns
- π€ Profile: Athlete profile with HR zones, subscription status, and personal info
- π Dark/Light Mode: Full theme toggle with persistent preference
- Activity Sync: Automatically sync and cache your Strava activities locally using Parquet files
- Cool Visualizations: Generate visualizations including:
- β‘ Thunderstorm Heatmap: Neon-style activity route visualization on dark backgrounds
- π Activity Clock: Polar scatter plot showing when you train (time vs distance)
- ποΈ HUD Dashboard: Cyberpunk-style histograms for distance, heart rate, and pace
- π Efficiency Factor: Track your aerobic efficiency (speed/HR) over time
- π Performance Frontier: Pareto frontier with Riegel's fatigue model fitting
- π Weekly Report: Instagram Story-sized weekly training summary with HR zones, sports breakdown, and accumulated training time
- π― Year in Sport: Instagram Story-sized summaries of your yearly training (main sport & totals)
- π Activity Plots: Neon-style individual activity visualization with elevation profile
- Map Matching: Match GPS tracks to OpenStreetMap road networks using HMM-based matching:
- πΊοΈ Street Coverage Map: Neon-glow visualization of all streets you've traversed in a city
- π Activity Match Plot: Per-activity visualization showing GPS track, matched OSM edges, and snap points
- π Coverage Stats: Track how many km of a city's street network you've covered
- Analytics: WIP
- GeoJSON Export: Export your activities as GeoJSON for use in mapping applications such as QGIS
- Telegram Bot: Automated scheduled delivery of weekly and monthly reports to your Telegram chat
- Smart Caching: Efficient local caching with incremental sync support to avoid redundant API calls
- Python 3.12+
- A Strava FREE account with API access
- Strava API credentials (Client ID and Client Secret)
# Clone the repository
git clone https://github.com/rsanchezmo/strava-intelligence.git
cd strava-intelligence
# Install dependencies with Poetry
poetry install
# Activate the virtual environment
poetry env activate# Clone the repository
git clone https://github.com/rsanchezmo/strava-intelligence.git
cd strava-intelligence
# Create a virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install the package
pip install -e .- Go to Strava API Settings
- Create a new application to get your Client ID and Client Secret
- Create a
.envfile in the project root:
STRAVA_CLIENT_ID=your_client_id
STRAVA_CLIENT_SECRET=your_client_secret- On first run, the app will open a browser for OAuth authorization. Follow the prompts to grant access.
You can optionally set up a Telegram bot to receive automated weekly reports (Sundays at 21:00) and monthly Year in Sport summaries (last day of month at 21:00).
- Create a Telegram bot via @BotFather and get your bot token
- Get your Telegram chat ID (send a message to your bot, then visit
https://api.telegram.org/bot<YourBOTToken>/getUpdates) - Add these to your
.envfile:
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id- Run the bot:
python telegram_bot.pyThe bot supports manual commands:
/weekly- Generate and send current week's report/monthly- Generate and send current year's report
Run the full web app (FastAPI backend + React frontend):
# Install dependencies
poetry install
cd frontend && npm install && cd ..
# Run both backend and frontend in development mode
python run_dev.pyThe app will be available at http://localhost:5173. The backend API runs on http://localhost:8000.
from strava.strava_intelligence import StravaIntelligence
from pathlib import Path
# Initialize (auto-syncs activities if cache is older than 12 hours)
strava = StravaIntelligence(workdir=Path("./strava_intelligence_workdir"))
# Generate a thunderstorm heatmap for your runs in Amsterdam
strava.strava_visualizer.thunderstorm_heatmap(
sport_types=['Run'],
location="amsterdam",
radius_km=20.0,
add_basemap=False
)
# Create an activity clock visualization
strava.strava_visualizer.activity_clock(sport_types=['Run'])
# Generate a HUD-style dashboard
strava.strava_visualizer.hud_dashboard(sport_types=['Run'])
# Plot efficiency factor trend
strava.strava_visualizer.plot_efficiency_factor(sport_types=['Run'])
# Plot performance frontier with fatigue model
strava.strava_visualizer.plot_performance_frontier(sport_types=['Run'])
# Generate Year in Sport summary (Instagram Story format)
strava.get_year_in_sport(year=2025, main_sport="Run", neon_color="#fc0101")
# Generate Year in Sport with comparison to previous year
strava.get_year_in_sport(
year=2025,
main_sport="Run",
neon_color="#fc0101",
comparison_year=2024,
comparison_neon_color="#00aaff"
)
# Generate Weekly Report (Instagram Story format)
strava.get_weekly_report(week_start_date="2026-01-12", neon_color="#fc0101")
# Export activities as GeoJSON
strava.save_geojson_activities()
# --- Map Matching & Street Coverage ---
from strava.strava_map_matching import StravaMapMatcher
from strava.strava_utils import get_activities_as_gdf_from_streams
# Initialize the map matcher for a city
map_matcher = StravaMapMatcher(
city_name="Amsterdam, Netherlands",
workdir=Path("./strava_intelligence_workdir"),
force_reload=False,
)
# Build a GeoDataFrame from high-res GPS streams
activities_gdf = get_activities_as_gdf_from_streams(
strava.strava_activities_cache.activities
)
# Match all activities to the OSM road network
matched_gdf, match_details = map_matcher.match(activities_gdf)
# Plot individual activity match results
for activity_id, result in match_details.items():
result.plot(save_path=f"map_match_{activity_id}.png")
# Generate a city-wide street coverage map
map_matcher.plot_coverage(match_details, save_path="amsterdam_coverage.png")A stunning neon visualization of your activity routes on a dark canvas. Perfect for showcasing your training coverage in a specific area.
| Thunderstorm Heatmap | Activity Clock |
|---|---|
![]() |
![]() |
| Neon-style route visualization on dark backgrounds | Polar plot showing training patterns by time of day |
| HUD Dashboard | Efficiency Factor | Performance Frontier |
|---|---|---|
![]() |
![]() |
![]() |
| Distance, HR & Pace distributions | Aerobic efficiency over time | Best performances with Riegel's model |
| Weekly Report | Bubble Map |
|---|---|
![]() |
![]() |
| Instagram Story-sized weekly summary with HR zones, sport breakdowns, and training progression | Geographic bubble visualization of activity locations |
Generate Instagram Story-sized (9:16) summaries of your yearly training with optional year comparison.
| Main Sport | All Sports | Activity Plot |
|---|---|---|
![]() |
![]() |
![]() |
| Stats, monthly chart & personal bests | Aggregated stats across all sports | Route map with elevation profile |
| Year Comparison β Run | Year Comparison β Totals |
|---|---|
![]() |
![]() |
| Side-by-side stats with grouped bar charts | Cross-sport comparison with highlighted differences |
Match your Strava activities to the OpenStreetMap road network using HMM-based map matching.
| Street Coverage Map | Activity Match Plot |
|---|---|
![]() |
![]() |
| Traversed streets glow in neon against the dim untraversed network | GPS track (red), matched OSM edges (blue), snap connections (white) |
Export your activities as GeoJSON for advanced spatial analysis in QGIS.
| All Activities | Activity Info |
|---|---|
![]() |
![]() |
strava-intelligence/
βββ main.py # Example usage (Python API)
βββ run_dev.py # Dev server launcher (backend + frontend)
βββ pyproject.toml # Poetry configuration
βββ README.md
βββ backend/ # FastAPI backend
β βββ app.py # FastAPI application
β βββ config.py # Settings
β βββ dependencies.py # Dependency injection
β βββ db.py # SQLite for calendar sessions
β βββ routers/ # API route handlers
β βββ activities.py
β βββ athlete.py
β βββ calendar.py
β βββ exports.py
β βββ stats.py
β βββ sync.py
βββ frontend/ # React + Vite frontend
β βββ src/
β β βββ App.tsx
β β βββ api/ # API client & React Query hooks
β β βββ components/ # Shared components (MapView, charts, layout)
β β βββ hooks/ # Custom hooks (useTheme)
β β βββ pages/ # Page components
β βββ ...
βββ strava/ # Core Python library
βββ constants.py # CRS constants
βββ strava_activities_cache.py # Activity caching logic
βββ strava_analytics.py # Analytics calculations
βββ strava_endpoint.py # Strava API client
βββ strava_intelligence.py # Main orchestrator class
βββ strava_map_matching.py # OSM map matching & coverage
βββ strava_user_cache.py # User data caching
βββ strava_utils.py # Utility functions
βββ strava_visualizer.py # Visualization generators
The main class that orchestrates all functionality.
StravaIntelligence(
workdir: Path, # Working directory for outputs
auto_sync: bool = True, # Auto-sync on initialization
sync_max_age_hours: int = 12 # Cache age threshold
)Current methods:
sync_activities(full_sync=False, include_streams=False)- Sync activities from Stravasave_geojson_activities()- Export activities as GeoJSONget_year_in_sport(year, main_sport, neon_color, comparison_year=None, comparison_neon_color="#00aaff")- Generate Year in Sport visualizations with optional year comparisonget_weekly_report(week_start_date=None, neon_color="#fc0101")- Generate weekly training report (defaults to current week)
Generates all visualizations.
Current methods:
thunderstorm_heatmap(location, sport_types, radius_km, add_basemap, neon_color, show_title)activity_clock(sport_types, neon_color, max_dist_km, show_title)hud_dashboard(sport_types, bins)plot_efficiency_factor(sport_types, window)plot_performance_frontier(sport_types)plot_weekly_report(weekly_report, folder, neon_color)plot_year_in_sport_main(year, year_in_sport, main_sport, folder, neon_color)plot_year_in_sport_totals(year, year_in_sport, folder, neon_color)plot_activity(activity_id, strava_endpoint, folder, title, neon_color)
HMM-based map matching of GPS tracks to OSM road networks.
StravaMapMatcher(
city_name: str, # City name for OSM network download
workdir: Path, # Working directory for cached maps
force_reload: bool = False # Force re-download of OSM data
)Current methods:
match(activities)- Map match a GeoDataFrame of activities, returns matched GeoDataFrame + per-activity MatchResult dictcoverage_stats(match_results)- Compute city-wide street coverage statistics (km traversed, % covered, unique roads)plot_coverage(match_results, save_path, neon_color, figsize)- Render a neon-glow coverage map of traversed vs untraversed streets
Provides analytics calculations.
Current methods:
get_rest_heart_rate()- Get estimated resting heart rateget_max_heart_rate()- Get maximum heart rate from zonesget_current_vo2_max()- Calculate VO2 Max estimate
- Telegram bot for automated weekly and monthly reports
- Web dashboard with React frontend and FastAPI backend
- Dark/light mode support
- Athlete profile page with HR zones
- Extend the analytics, use ML models to provide deeper insights, such as training load, fatigue estimation, and performance prediction
- Add more visualizations
- Create an mcp server to expose Strava data so you can access it from your LLM based agents
Contributions are welcome! Please feel free to submit a Pull Request.















